├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .prettierrc ├── .storybook ├── addons.js ├── config.js └── webpack.config.js ├── LICENSE ├── README.md ├── babel.config.js ├── docs └── stories │ └── _redirects ├── examples └── vite │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ └── vite.svg │ ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── fixture.html ├── logo.png ├── package.json ├── pnpm-lock.yaml ├── renovate.json ├── screenshot.gif ├── scripts ├── deploy-minor.sh ├── deploy-patch.sh ├── prod.common.js ├── prod.es5.js └── prod.js ├── src ├── index.js.flow ├── index.test.tsx └── index.tsx ├── stories ├── bare │ └── bare.tsx ├── basic │ ├── controlled.tsx │ ├── multi-controlled.tsx │ ├── multi-uncontrolled.tsx │ └── uncontrolled.tsx ├── bounds-and-offset.js ├── bounds │ ├── body-controlled.tsx │ ├── element-controlled.tsx │ ├── element-uncontrolled.tsx │ ├── parent-controlled.tsx │ ├── parent-uncontrolled.tsx │ ├── selector-controlled.tsx │ ├── selector-uncontrolled.tsx │ └── window-controlled.tsx ├── callback │ └── callbacks.tsx ├── cancel │ └── cancel.tsx ├── customization │ └── resizeHandleComponent.tsx ├── dragAxis │ ├── dragAxisNone.tsx │ ├── dragAxisX.tsx │ └── dragAxisY.tsx ├── grid │ ├── both.tsx │ ├── drag.tsx │ └── resize.tsx ├── index.tsx ├── lock-aspect-ratio │ └── basic.tsx ├── max-size-with-percent.js ├── min │ └── uncontrolled.tsx ├── multiple.js ├── sandbox.js ├── sandbox │ ├── bodysize-to-maxwidth.tsx │ ├── issue-#622.tsx │ └── lock-aspect-ratio-with-bounds.tsx ├── scale │ ├── body-uncontrolled-x0-5.tsx │ ├── body-uncontrolled-x1-5.tsx │ ├── parent-uncontrolled.tsx │ ├── selector-controlled.tsx │ ├── selector-uncontrolled.tsx │ └── window-uncontrolled.tsx ├── size-and-position.js ├── size-percentage.js ├── size │ ├── size-percent-controlled.tsx │ └── size-percent-uncontrolled.tsx ├── styles.css └── styles.ts ├── tsconfig.json └── tslint.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [bokuweb] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ### Overview of the problem 7 | 8 | 9 | I'm using react-rnd **version** [x.x.x] 10 | 11 | My **browser** is: 12 | 13 | I am sure this issue is **not a duplicate**? 14 | 15 | ### Reproduced project 16 | 17 | 18 | https://codesandbox.io/s/y3997qply9 19 | 20 | 21 | ### Description 22 | 23 | 24 | 25 | ### Steps to Reproduce 26 | 27 | 1. First Step 28 | 2. Second Step 29 | 3. and so on... 30 | 31 | ### Expected behavior 32 | 33 | 34 | 35 | ### Actual behavior 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ### Proposed solution 5 | 6 | 7 | 8 | ### Tradeoffs 9 | 10 | 11 | 12 | 13 | ### Testing Done 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | - uses: actions/setup-node@master 10 | with: 11 | node-version: 20 12 | - uses: pnpm/action-setup@v2 13 | with: 14 | version: 9 15 | - name: Install dependencies 16 | run: pnpm i --frozen-lockfile 17 | - name: lint 18 | run: pnpm lint 19 | 20 | tsc: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@master 24 | - uses: actions/setup-node@master 25 | with: 26 | node-version: 20 27 | - uses: pnpm/action-setup@v2 28 | with: 29 | version: 9 30 | - name: Install dependencies 31 | run: pnpm i --frozen-lockfile 32 | - name: tsc 33 | run: pnpm tsc 34 | 35 | # test: 36 | # runs-on: ubuntu-latest 37 | # steps: 38 | # - uses: actions/checkout@master 39 | # - uses: actions/setup-node@master 40 | # with: 41 | # node-version: 20 42 | # - name: yarn 43 | # run: yarn --frozen-lockfile 44 | # - name: test 45 | # run: | 46 | # export DISPLAY=:99 47 | # Xvfb -ac :99 -screen 0 2080x1024x24 >/dev/null 2>&1 & 48 | # yarn run test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | example/dist/ 3 | lib/ 4 | npm-debug.log 5 | .rpt2_cache 6 | yarn-error.log 7 | docs 8 | .idea/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | useTabs: false 2 | printWidth: 120 3 | tabWidth: 2 4 | singleQuote: false 5 | trailingComma: "all" 6 | jsxBracketSameLine: false 7 | parser: "typescript" 8 | semi: true -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | 2 | /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ 3 | 4 | import { configure } from '@storybook/react'; 5 | 6 | function loadStories() { 7 | require('../stories'); 8 | } 9 | 10 | configure(loadStories, module); -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = async ({ config, mode }) => { 4 | config.module.rules.push({ 5 | test: /.*\.(ts|tsx|js|jsx)$/, 6 | loader: require.resolve('babel-loader'), 7 | }); 8 | 9 | config.resolve.extensions.push('.ts', '.tsx', '.js', '.jsx'); 10 | 11 | return config; 12 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 @bokuweb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

A resizable and draggable component for React.

4 | 5 |

Build Status 6 | 7 | Build Status 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | 16 | ## Table of Contents 17 | 18 | * [Screenshot](#Screenshot) 19 | * [Live Demo](#live-demo) 20 | * [Storybook](#storybook) 21 | * [CodeSandbox](#codesandbox) 22 | * [Install](#install) 23 | * [Usage](#usage) 24 | * [Props](#props) 25 | * [Instance API](#instance-api) 26 | * [updateSize(size: { width: number | string, height: number | string }): void](#updateSize-void) 27 | * [updatePosition({ x: number, y: number }): void](#updatePosition-void) 28 | * [Test](#test) 29 | * [Related](#related) 30 | * [Changelog](#changelog) 31 | * [License](#license) 32 | 33 | 34 | ## Screenshot 35 | 36 |

37 | 38 |

39 | 40 | https://codesandbox.io/s/xpm699v4lp 41 | 42 | ## Live Demo 43 | 44 | ### Storybook 45 | 46 | [Storybook](http://bokuweb.github.io/react-rnd/stories) 47 | 48 | ### CodeSandbox 49 | 50 | [![Edit y3997qply9](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/y3997qply9) 51 | [CodeSandbox(with default)](https://codesandbox.io/s/y3997qply9) 52 | [CodeSandbox(with size and position)](https://codesandbox.io/s/my4kjly94x) 53 | [CodeSandbox(with typescript)](https://codesandbox.io/s/j1vvkpo9wv) 54 | [CodeSandbox(with hooks)](https://codesandbox.io/p/sandbox/pensive-babycat-8kxhnr) 55 | 56 | 57 | ## Install 58 | 59 | - use npm 60 | 61 | ```sh 62 | npm i -S react-rnd 63 | ``` 64 | 65 | - use yarn 66 | 67 | ```sh 68 | yarn add react-rnd 69 | ``` 70 | 71 | ## Usage 72 | 73 | ### Example with `default` 74 | 75 | ``` javascript 76 | 84 | Rnd 85 | 86 | ``` 87 | 88 | ### Example with `position` and `size` 89 | 90 | ``` javascript 91 | { this.setState({ x: d.x, y: d.y }) }} 95 | onResizeStop={(e, direction, ref, delta, position) => { 96 | this.setState({ 97 | width: ref.style.width, 98 | height: ref.style.height, 99 | ...position, 100 | }); 101 | }} 102 | > 103 | 001 104 | 105 | ``` 106 | 107 | ## Props 108 | 109 | #### `default: { x: number; y: number; width?: number | string; height?: number | string; };` 110 | 111 | The `width` and `height` property is used to set the default size of the component. 112 | For example, you can set `300`, `'300px'`, `50%`. 113 | If omitted, set `'auto'`. 114 | 115 | The `x` and `y` property is used to set the default position of the component. 116 | 117 | #### `size?: { width: (number | string), height: (number | string) };` 118 | 119 | The `size` property is used to set size of the component. 120 | For example, you can set 300, '300px', 50%. 121 | 122 | Use `size` if you need to control size state by yourself. 123 | 124 | #### `position?: { x: number, y: number };` 125 | 126 | The `position` property is used to set position of the component. 127 | Use `position` if you need to control size state by yourself. 128 | 129 | see, following example. 130 | 131 | ``` javascript 132 | { this.setState({ x: d.x, y: d.y }) }} 136 | onResize={(e, direction, ref, delta, position) => { 137 | this.setState({ 138 | width: ref.offsetWidth, 139 | height: ref.offsetHeight, 140 | ...position, 141 | }); 142 | }} 143 | > 144 | 001 145 | 146 | ``` 147 | 148 | #### `className?: string;` 149 | 150 | The `className` property is used to set the custom `className` of the component. 151 | 152 | #### `style?: { [key: string]: string };` 153 | 154 | The `style` property is used to set the custom `style` of the component. 155 | 156 | #### `minWidth?: number | string;` 157 | 158 | The `minWidth` property is used to set the minimum width of the component. 159 | For example, you can set `300`, `'300px'`, `50%`. 160 | 161 | #### `minHeight?: number | string;` 162 | 163 | The `minHeight` property is used to set the minimum height of the component. 164 | For example, you can set `300`, `'300px'`, `50%`. 165 | 166 | #### `maxWidth?: number | string;` 167 | 168 | The `maxWidth` property is used to set the maximum width of the component. 169 | For example, you can set `300`, `'300px'`, `50%`. 170 | 171 | #### `maxHeight?: number | string`; 172 | 173 | The `maxHeight` property is used to set the maximum height of the component. 174 | For example, you can set `300`, `'300px'`, `50%`. 175 | 176 | #### `resizeGrid?: [number, number];` 177 | 178 | The `resizeGrid` property is used to specify the increments that resizing should snap to. Defaults to `[1, 1]`. 179 | 180 | #### `dragGrid?: [number, number];` 181 | 182 | The `dragGrid` property is used to specify the increments that moving should snap to. Defaults to `[1, 1]`. 183 | 184 | #### `lockAspectRatio?: boolean | number;` 185 | 186 | The `lockAspectRatio` property is used to lock aspect ratio. 187 | Set to `true` to lock the aspect ratio based on the initial size. 188 | Set to a numeric value to lock a specific aspect ratio (such as `16/9`). 189 | If set to numeric, make sure to set initial height/width to values with correct aspect ratio. 190 | If omitted, set `false`. 191 | 192 | #### `lockAspectRatioExtraWidth?: number;` 193 | 194 | The `lockAspectRatioExtraWidth` property enables a resizable component to maintain an aspect ratio plus extra width. 195 | For instance, a video could be displayed 16:9 with a 50px side bar. 196 | If omitted, set `0`. 197 | 198 | #### `scale?: number;` 199 | 200 | Specifies the scale of the canvas you are dragging or resizing this element on. This allows 201 | you to, for example, get the correct drag / resize deltas while you are zoomed in or out via 202 | a transform or matrix in the parent of this element. 203 | If omitted, set `1`. 204 | 205 | #### `lockAspectRatioExtraHeight?: number;` 206 | 207 | The `lockAspectRatioExtraHeight` property enables a resizable component to maintain an aspect ratio plus extra height. 208 | For instance, a video could be displayed 16:9 with a 50px header bar. 209 | If omitted, set `0`. 210 | 211 | #### `dragHandleClassName?: string;` 212 | 213 | Specifies a selector to be used as the handle that initiates drag. 214 | Example: `handle`. 215 | 216 | #### `resizeHandleStyles?: HandleStyles;` 217 | 218 | The `resizeHandleStyles` property is used to override the style of one or more resize handles. 219 | Only the axis you specify will have its handle style replaced. 220 | If you specify a value for `right` it will completely replace the styles for the `right` resize handle, 221 | but other handle will still use the default styles. 222 | 223 | ``` javascript 224 | 225 | export type HandleStyles = { 226 | bottom?: React.CSSProperties, 227 | bottomLeft?: React.CSSProperties, 228 | bottomRight?: React.CSSProperties, 229 | left?: React.CSSProperties, 230 | right?: React.CSSProperties, 231 | top?: React.CSSProperties, 232 | topLeft?: React.CSSProperties, 233 | topRight?: React.CSSProperties 234 | } 235 | ``` 236 | 237 | #### `resizeHandleClasses?: HandleClasses;` 238 | 239 | The `resizeHandleClasses` property is used to set the className of one or more resize handles. 240 | 241 | ``` javascript 242 | type HandleClasses = { 243 | bottom?: string; 244 | bottomLeft?: string; 245 | bottomRight?: string; 246 | left?: string; 247 | right?: string; 248 | top?: string; 249 | topLeft?: string; 250 | topRight?: string; 251 | } 252 | ``` 253 | 254 | #### `resizeHandleComponent?`: HandleCompoent;` 255 | 256 | The `resizeHandleComponent` allows you to pass a custom React component as the resize handle. 257 | 258 | ``` javascript 259 | type HandleComponent = { 260 | top?: React.ReactElement; 261 | right?: React.ReactElement; 262 | bottom?: React.ReactElement; 263 | left?: React.ReactElement; 264 | topRight?: React.ReactElement; 265 | bottomRight?: React.ReactElement; 266 | bottomLeft?: React.ReactElement; 267 | topLeft?: React.ReactElement; 268 | } 269 | ``` 270 | 271 | #### `resizeHandleWrapperClass?: string;` 272 | 273 | The `resizeHandleWrapperClass` property is used to set css class name of resize handle wrapper(`span`) element. 274 | 275 | #### `resizeHandleWrapperStyle?: Style;` 276 | 277 | The `resizeHandleWrapperStyle` property is used to set css class name of resize handle wrapper(`span`) element. 278 | 279 | #### `enableResizing?: ?Enable;` 280 | 281 | The `enableResizing` property is used to set the resizable permission of the component. 282 | 283 | The permission of `top`, `right`, `bottom`, `left`, `topRight`, `bottomRight`, `bottomLeft`, `topLeft` direction resizing. 284 | If omitted, all resizer are enabled. 285 | If you want to permit only right direction resizing, set `{ top:false, right:true, bottom:false, left:false, topRight:false, bottomRight:false, bottomLeft:false, topLeft:false }`. 286 | 287 | ``` javascript 288 | export type Enable = { 289 | bottom?: boolean; 290 | bottomLeft?: boolean; 291 | bottomRight?: boolean; 292 | left?: boolean; 293 | right?: boolean; 294 | top?: boolean; 295 | topLeft?: boolean; 296 | topRight?: boolean; 297 | } | boolean 298 | ``` 299 | 300 | #### `disableDragging?: boolean;` 301 | 302 | The `disableDragging` property disables dragging completely. 303 | 304 | #### `cancel?: string;` 305 | 306 | The `cancel` property disables specifies a selector to be used to prevent drag initialization (e.g. `.body`). 307 | 308 | #### `dragAxis?: 'x' | 'y' | 'both' | 'none'` 309 | 310 | The direction of allowed movement (dragging) allowed ('x','y','both','none'). 311 | 312 | #### `bounds?: string; | Element` 313 | 314 | Specifies movement boundaries. Accepted values: 315 | - `parent` restricts movement within the node's offsetParent 316 | (nearest node with position relative or absolute) 317 | - `window`, `body`, Selector like `.fooClassName` or 318 | - `Element`. 319 | 320 | 321 | #### `enableUserSelectHack?: boolean;` 322 | 323 | By default, we add 'user-select:none' attributes to the document body 324 | to prevent ugly text selection during drag. If this is causing problems 325 | for your app, set this to `false`. 326 | 327 | #### `scale?: number;` 328 | 329 | Specifies the scale of the canvas your are resizing and dragging this element on. This allows 330 | you to, for example, get the correct resize and drag deltas while you are zoomed in or out via 331 | a transform or matrix in the parent of this element. 332 | If omitted, set `1`. 333 | 334 | ## Callback 335 | 336 | #### `onResizeStart?: RndResizeStartCallback;` 337 | 338 | `RndResizeStartCallback` type is below. 339 | 340 | ``` javascript 341 | export type RndResizeStartCallback = ( 342 | e: SyntheticMouseEvent | SyntheticTouchEvent, 343 | dir: ResizeDirection, 344 | refToElement: React.ElementRef<'div'>, 345 | ) => void; 346 | ``` 347 | 348 | Calls when resizable component resize start. 349 | 350 | #### `onResize?: RndResizeCallback;` 351 | 352 | `RndResizeCallback` type is below. 353 | 354 | ``` javascript 355 | export type RndResizeCallback = ( 356 | e: MouseEvent | TouchEvent, 357 | dir: ResizeDirection, 358 | refToElement: React.ElementRef<'div'>, 359 | delta: ResizableDelta, 360 | position: Position, 361 | ) => void; 362 | ``` 363 | 364 | Calls when resizable component resizing. 365 | 366 | #### `onResizeStop?: RndResizeCallback;` 367 | 368 | `RndResizeCallback` type is below. 369 | 370 | ``` javascript 371 | export type RndResizeCallback = ( 372 | e: MouseEvent | TouchEvent, 373 | dir: ResizeDirection, 374 | refToElement: React.ElementRef<'div'>, 375 | delta: ResizableDelta, 376 | position: Position, 377 | ) => void; 378 | ``` 379 | 380 | Calls when resizable component resize stop. 381 | 382 | #### `onDragStart: DraggableEventHandler;` 383 | 384 | Callback called on dragging start. 385 | 386 | ``` javascript 387 | type DraggableData = { 388 | node: HTMLElement, 389 | x: number, 390 | y: number, 391 | deltaX: number, deltaY: number, 392 | lastX: number, lastY: number 393 | }; 394 | 395 | type DraggableEventHandler = ( 396 | e: SyntheticMouseEvent | SyntheticTouchEvent, data: DraggableData, 397 | ) => void | false; 398 | ``` 399 | 400 | #### `onDrag: DraggableEventHandler;` 401 | 402 | `onDrag` called with the following parameters: 403 | 404 | ``` javascript 405 | type DraggableData = { 406 | node: HTMLElement, 407 | x: number, 408 | y: number, 409 | deltaX: number, deltaY: number, 410 | lastX: number, lastY: number 411 | }; 412 | 413 | type DraggableEventHandler = ( 414 | e: SyntheticMouseEvent | SyntheticTouchEvent, data: DraggableData, 415 | ) => void | false; 416 | ``` 417 | 418 | #### `onDragStop: DraggableEventHandler;` 419 | 420 | `onDragStop` called on dragging stop. 421 | 422 | 423 | ``` javascript 424 | type DraggableData = { 425 | node: HTMLElement, 426 | x: number, 427 | y: number, 428 | deltaX: number, deltaY: number, 429 | lastX: number, lastY: number 430 | }; 431 | 432 | type DraggableEventHandler = ( 433 | e: SyntheticMouseEvent | SyntheticTouchEvent, data: DraggableData, 434 | ) => void | false; 435 | ``` 436 | 437 | ## Instance API 438 | 439 | 440 | #### `updateSize(size: { width: string | number, height: string | number })` 441 | 442 | Update component size. 443 | For example, you can set `300`, `'300px'`, `50%`. 444 | 445 | - for example 446 | 447 | ``` js 448 | class YourComponent extends Component { 449 | 450 | ... 451 | 452 | update() { 453 | this.rnd.updateSize({ width: 200, height: 300 }); 454 | } 455 | 456 | render() { 457 | return ( 458 | { this.rnd = c; }} ...rest > 459 | example 460 | 461 | ); 462 | } 463 | ... 464 | } 465 | ``` 466 | 467 | #### `updatePosition({ x: number, y: number }): void` 468 | 469 | Update component position. 470 | `grid` `bounds` props is ignored, when this method called. 471 | 472 | - for example 473 | 474 | ``` js 475 | class YourComponent extends Component { 476 | 477 | ... 478 | 479 | update() { 480 | this.rnd.updatePosition({ x: 200, y: 300 }); 481 | } 482 | 483 | render() { 484 | return ( 485 | { this.rnd = c; }} ...rest > 486 | example 487 | 488 | ); 489 | } 490 | 491 | ... 492 | } 493 | ``` 494 | 495 | #### `allowAnyClick?: boolean` 496 | 497 | If set to `true`, will allow dragging on non left-button clicks. 498 | 499 | ## Test 500 | 501 | ``` sh 502 | npm t 503 | ``` 504 | 505 | ## Contribute 506 | 507 | If you have a feature request, please add it as an issue or make a pull request. 508 | 509 | If you have a bug to report, please reproduce the bug in [CodeSandbox](https://codesandbox.io/s/y3997qply9) to help us easily isolate it. 510 | 511 | ## Changelog 512 | 513 | #### v10.5.1 514 | 515 | - Upgrade `re-resizable` to `6.11.0` 516 | - Add missing position offset prop 517 | 518 | #### v10.4.14 519 | 520 | - Upgrade `re-resizable` to `6.10.3` 521 | 522 | #### v10.4.13 523 | 524 | - Upgrade `re-resizable` to `6.10.0` 525 | 526 | #### v10.4.12 527 | 528 | - Fixes $945, When using vite and resizing from other than right and bottom - the element is shaking weirdly. 529 | 530 | - Upgrade `re-resizable` to `6.9.17` 531 | - Fixes #942, define callback refs inline to work with latest versions of Next.js / React. 532 | 533 | #### v10.4.10 534 | 535 | - Upgrade `re-resizable` to `6.9.14` 536 | 537 | #### v10.4.7 538 | 539 | - Fixed a bug, `maxHeight` does not work with `%` #914 540 | 541 | #### v10.4.6 542 | 543 | - Upgrade `re-resizable` to `6.9.11` 544 | - Upgrade `react-draggable` to `4.4.6` 545 | - Fixed a bug, wrong position in `onDrag` #910 546 | 547 | #### v10.4.1 548 | 549 | - Support Element for bounds. 550 | 551 | #### v10.3.7 552 | 553 | - Upgrade `re-resizable` to `6.9.6` 554 | - Add peer deps. 555 | 556 | 557 | #### v10.3.6 558 | 559 | - Upgrade `re-resizable` to `6.9.2` 560 | - Upgrade `react-draggable` to v4.4.4 561 | 562 | #### v10.3.5 563 | 564 | - Upgrade `re-resizable` to `6.9.1` 565 | 566 | #### v10.3.4 567 | 568 | - Fixed a bound check with locked aspect ratio (fully fixes #209) 569 | 570 | #### v10.3.1, v10.3.2 571 | 572 | - Fixed a bug, top and left resize issue, caused by "position" #792 573 | 574 | #### v10.3.0 575 | 576 | - Fixed a callback position when dragAxis specified 577 | 578 | #### v10.2.5 579 | 580 | - Fixed a glitch when dragAxis is enabled and component is being resized #780 581 | 582 | #### v10.2.3 583 | 584 | - Fixed a bug, if set minWidth or minHeight with `px`, reize dowes not work. #739 585 | 586 | #### v10.2.0 587 | 588 | - Upgrade `react-draggable` to v4.4.3 589 | - Add `allowAnyClick` props. 590 | - Add `nodeRef` props. 591 | 592 | #### v10.1.10 593 | 594 | - Downgrade `react-draggable` to v4.2.0 #690 595 | 596 | #### v10.1.9 597 | 598 | - Update `react-draggable` to v4.3.1 599 | 600 | #### v10.1.8 601 | 602 | - Update `re-resizable` to v6.3.2 603 | 604 | #### v10.1.7 605 | 606 | - A minor fix for a bug with forwarding of cancelling indication of an onDrag event. (#667) 607 | 608 | #### v10.1.6 609 | 610 | - Fixes #641 without causing other issues with typing. 611 | 612 | #### v10.1.5 613 | 614 | - Fixed a bug, react-draggable not bundling with rollup #641 615 | 616 | #### v10.1.4 617 | 618 | - Fixed a bug, box moves when resized #622 619 | 620 | #### v10.1.3 621 | 622 | - Fixed a bug, position is wrong when onResize #618 623 | 624 | #### v10.1.2 625 | 626 | - Upgrade re-resizable to 6.1.1 627 | - Upgrade react-draggable to 4.1.0 628 | 629 | #### v10.1.1 630 | 631 | - Upgrade re-resizable to 6.1.0 632 | 633 | #### v10.1.0 634 | 635 | - Implement resizeHandleComponent #591 636 | - Update dependency react-draggable to v4 637 | 638 | #### v10.0.0 639 | 640 | - Fix: Fix #526 641 | - Feat: Add `onMouseUp` callback. 642 | - Feat: Use `React.pureComponent` 643 | 644 | #### v9.2.0 645 | 646 | - Chore: Use `re-resizablev5` 647 | 648 | #### v9.1.2 649 | 650 | - Fix: Fixes memory leak #499 651 | 652 | #### v9.1.1 653 | 654 | - Fix: Add `scale` props to index.js.flow. 655 | 656 | #### v9.1.0 657 | 658 | - Feat: Add `scale` props. #482 659 | - Feat: Upgrade deps. 660 | 661 | #### v9.0.4 662 | 663 | - Fix: cursor style #469 664 | 665 | #### v9.0.3 666 | 667 | - update dependency re-resizable to v4.9.3 #444 668 | 669 | #### v9.0.2 670 | 671 | - fix: resizeHandleWrapperClass warning shown in console #428 672 | 673 | #### v9.0.1 674 | 675 | - fix: Allow additional props in typescript. 676 | 677 | #### v9.0.0 678 | 679 | - fix: change `default export` to `export` #405 680 | 681 | #### v8.0.2 682 | 683 | - fix: fixed a bug, `bounds` is ignored when lock aspect ratio set. 684 | - feat: add `body` to bounds props. 685 | 686 | #### v8.0.1 687 | 688 | - fix: [#221] fixed a bug, maxwidth / height not applied. 689 | 690 | #### v8.0.0 691 | 692 | - fix: fixed some position and resizing bug. 693 | - fix: [#209] bounds `window`. you can check [here](http://bokuweb.github.io/react-rnd/stories/?selectedKind=bounds&selectedStory=window%20controlled&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel). 694 | - fix: [#317] add onMouseDown. i.e) `` 695 | - [BREAKING] fix: [#335] add . to `dragHandleClassName` automatically, Please pass string (i.e `handle`. 696 | - [BREAKING] fix: remove `extendsProps`. Please add extends props directly. i.e) `` 697 | - [BREAKING] fix: remove `z` props. Please add `zIndex` via `style` props. i.e) `` 698 | 699 | #### v8.0.0-beta.2 700 | 701 | - fix: Upgrade `re-resizable` to fix percentage size and bare behavior. 702 | 703 | #### v8.0.0-beta.1 704 | 705 | - fix: Fixed a bug, controlled position does not work correctly. 706 | - feat: Use `typescript` instead of `flowype`. 707 | 708 | #### v8.0.0-beta.0 709 | 710 | - fix: Remove dummy `
`, `isMounted` state and `setParentPosition()`. 711 | 712 | #### v7.4.3 713 | 714 | - fix: Add `props,children` to dummy `
` to render children in first. 715 | 716 | #### v7.4.2 (unpublished) 717 | 718 | fix: `isMounted` and `(!this.state.isMounted) return
` line #356 719 | 720 | #### v7.4.1 721 | 722 | - fix: Fixed Array.from error in IE11 723 | 724 | #### v7.4.0 725 | 726 | - fix: add `enableUserSelectHack?: boolean;`. 727 | 728 | #### v7.3.1 729 | 730 | - chore(deps): upgrade deps 731 | - chore(deps): upgrade lint and remove unused state 732 | - chore(deps): install prettier 733 | 734 | #### v7.3.0 735 | 736 | - chore(deps): upgrade re-resizable 737 | 738 | #### v7.2.0 739 | 740 | - Support for cancel feature of react-draggable #206 741 | 742 | #### v7.1.5 743 | 744 | - Fixed a issue #199 Add enableUserSelectHack props to react-draggable 745 | 746 | #### v7.1.4 747 | 748 | - Fixed a issue #188 maxWidth and maxHeight props don't respect after resize 749 | 750 | #### v7.1.3 751 | 752 | - Fixed a bug, `extendProps` is not passed correctly. 753 | - Fixed a bug, `bounds` is not work correctly. (#162) 754 | 755 | #### v7.1.1 / v7.1.2 756 | 757 | - Add internal props. 758 | 759 | #### v7.1.0 760 | 761 | - Add `size`. 762 | - Add `position`. 763 | 764 | #### v7.0.0 765 | 766 | - Add `default` instead of `x`, `y`, `width`, `height`. 767 | - Add `resizeHandleWrapperClass` and `resizeHandleWrapperStyle`. 768 | 769 | #### v6.0.1 770 | 771 | - Remove unnecessary types. 772 | 773 | #### v6.0.0 774 | 775 | - Use rollup. 776 | - Support % min/max size. 777 | - Change props, remove `default` and add `x`, `y`, `width`, `height`. 778 | - Rename `dragHandlersXXXX` and `resizeHandlersXXXX` props to `dragHandleXXXX` and `resizeHandleXXXX`. 779 | 780 | #### v5.1.3 781 | 782 | - Fix cursor style, set `normal` to cursor style when `dragHandlerClassName` is not empty. 783 | 784 | #### v5.1.2 785 | 786 | - Add position `relative` when component will update. 787 | 788 | #### v5.1.1 789 | 790 | - Add `top: 0`, `left: 0`. 791 | - Add position `relative` when parent position equals `static`. 792 | 793 | #### v5.1.0 794 | 795 | - Update dependencies(`react-draggable v3`, `flow-bin v0.53`, and other...) 796 | 797 | #### v5.0.9 798 | 799 | - Fix bug new `z` props is not applied to state. 800 | 801 | #### v5.0.8 802 | 803 | - Add `extendsProps`. #129 804 | 805 | #### v5.0.7 806 | 807 | - Add `disableDragging` props. 808 | 809 | #### v5.0.6 810 | 811 | - Fix flow error. 812 | 813 | #### v5.0.5 814 | 815 | - Add index.js.flow 816 | 817 | #### v5.0.4 818 | 819 | - Fix Issue #117. 820 | 821 | #### v5.0.3 822 | 823 | - Fix `updateZIndex`. 824 | - Fix `updateSize`. 825 | - Fix left and top bounds. 826 | 827 | #### v5.0.2 828 | 829 | - Fix argument events #100 830 | 831 | #### v5.0.1 832 | 833 | - Fix example 834 | - Update README 835 | 836 | #### v5.0.0 837 | 838 | - Fix resize bounds. 839 | - Modify API. 840 | - Use original `react-draggable`. 841 | 842 | 843 | #### v4.2.1 844 | 845 | - Added `updateZIndex`, method to updated component `zIndex` state. 846 | 847 | #### v4.2.0 848 | 849 | - Pass the new position in the onResizeStop callback #60 850 | 851 | 852 | #### v4.1.0 853 | 854 | - Pass the new position along in the resize callback #55 855 | 856 | 857 | #### v4.0.1 858 | 859 | - Fix style props to applt zIndex chaned. 860 | 861 | #### v4.0.0 862 | 863 | - Rename `react-rnd`. 864 | - Remove `canUpdatePositionByParent` property. 865 | - Remove `canUpdateSizeByParent` property. 866 | - Remove `initiAsResizing` property. 867 | - Change `x`, `y`, `width` and `height` property to `initial`. 868 | - Add `updateSize`, `updatePosition`, method to updated conponent state. 869 | - Add `lockAspectRatio` property to lock aspect ratio when resizing. 870 | 871 | #### v3.0.0 872 | 873 | - Add `canUpdatePositionByParent` property. 874 | 875 | #### v2.0.0 876 | 877 | - Fix bug, resize and grid not work properly. 878 | 879 | #### v1.2.0 880 | 881 | - Add `grid` props to snap grid. (thanks @paulyoung) 882 | - Fix bug, moveAxis not work properly. 883 | 884 | 885 | #### v1.1.3 886 | 887 | - Fix situations when on dragStop you wanted to revert to 0,0 position #39 888 | - Add `canUpdateSizeByParent` props #38 889 | 890 | #### v1.1.2 891 | 892 | - Add object.assign transform 893 | 894 | #### v1.1.0 895 | 896 | - Add add module exports plugin for `require` 897 | 898 | #### v1.0.1 899 | 900 | - Bug fix 901 | 902 | #### v1.0.0 903 | 904 | - Support react v15.x 905 | - Support left, top resizer 906 | - Remove start props, use width, height, x, and y. 907 | 908 | #### v0.5.3 909 | 910 | - Add handle selector 911 | 912 | ## License 913 | 914 | The MIT License (MIT) 915 | 916 | Copyright (c) 2018 bokuweb 917 | 918 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 919 | 920 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 921 | 922 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 923 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@babel/preset-typescript", 4 | '@babel/preset-env', 5 | "@babel/preset-react" 6 | ], 7 | plugins: [ 8 | "@babel/plugin-transform-modules-commonjs", 9 | ["@babel/plugin-proposal-class-properties", { "loose": true }] 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /docs/stories/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /examples/vite/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vite/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json', './tsconfig.app.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.3.3", 18 | "@types/react-dom": "^18.3.0", 19 | "@typescript-eslint/eslint-plugin": "^7.15.0", 20 | "@typescript-eslint/parser": "^7.15.0", 21 | "@vitejs/plugin-react-swc": "^3.5.0", 22 | "eslint": "^8.57.0", 23 | "eslint-plugin-react-hooks": "^4.6.2", 24 | "eslint-plugin-react-refresh": "^0.4.7", 25 | "typescript": "^5.2.2", 26 | "vite": "^5.3.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vite/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /examples/vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { useState } from "react"; 3 | 4 | import { Rnd } from "../../../src"; 5 | 6 | const style = { 7 | display: "flex", 8 | alignItems: "center", 9 | justifyContent: "center", 10 | border: "solid 1px #ddd", 11 | background: "#f0f0f0", 12 | } as const; 13 | 14 | const App = () => { 15 | const [size, updateSize] = useState({ width: 200, height: 200 }); 16 | const [position, updatePosition] = useState({ x: 0, y: 0 }); 17 | return ( 18 | { 23 | updatePosition({ x: d.x, y: d.y }); 24 | }} 25 | onResizeStop={(e, direction, ref, delta, position) => { 26 | updateSize({ 27 | width: Number(ref.style.width), 28 | height: Number(ref.style.height), 29 | }); 30 | updatePosition(position) 31 | }} 32 | > 33 | Rnd 34 | 35 | ); 36 | }; 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /examples/vite/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vite/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /examples/vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vite/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /examples/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /examples/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /fixture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/react-rnd/34ccbfa162775b305b0c84836c8082e8caee9500/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-rnd", 3 | "version": "10.5.2", 4 | "description": "A draggable and resizable React Component", 5 | "title": "react-rnd", 6 | "main": "./lib/index.es5.js", 7 | "module": "./lib/index.js", 8 | "jsnext:main": "./lib/index.js", 9 | "types": "./lib/index.d.ts", 10 | "keywords": [ 11 | "react", 12 | "resize", 13 | "resizable", 14 | "draggable", 15 | "component" 16 | ], 17 | "scripts": { 18 | "lint": "tslint -c tslint.json 'src/index.tsx'", 19 | "flow": "flow", 20 | "tsc": "tsc -p tsconfig.json --skipLibCheck", 21 | "build:prod:main": "rollup -c scripts/prod.js", 22 | "build:prod:es5": "rollup -c scripts/prod.es5.js", 23 | "build": "npm-run-all --serial build:prod:* copy:flow", 24 | "test": "cross-env NODE_ENV='test' npm run tsc && avaron lib/index.test.js --renderer", 25 | "copy:flow": "cpy src/index.js.flow lib && cpy src/index.js.flow lib --rename index.es5.js.flow", 26 | "test:ci": "npm run flow && npm run build", 27 | "prepare": "npm run build", 28 | "storybook": "start-storybook -p 6016", 29 | "build-storybook": "build-storybook -o docs/stories", 30 | "deploy": "npm run build-storybook && gh-pages -d docs" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/bokuweb/react-rnd.git" 35 | }, 36 | "author": "bokuweb", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/bokuweb/react-rnd/issues" 40 | }, 41 | "homepage": "https://github.com/bokuweb/react-rnd", 42 | "devDependencies": { 43 | "@babel/cli": "7.17.6", 44 | "@babel/core": "7.17.5", 45 | "@babel/plugin-proposal-class-properties": "7.16.7", 46 | "@babel/plugin-transform-modules-commonjs": "7.16.8", 47 | "@babel/preset-react": "7.16.7", 48 | "@babel/preset-typescript": "7.16.7", 49 | "@babel/traverse": "7.17.3", 50 | "@babel/types": "7.17.0", 51 | "@emotion/core": "11.0.0", 52 | "@storybook/addon-actions": "6.5.16", 53 | "@storybook/addon-info": "5.3.21", 54 | "@storybook/addon-links": "6.5.16", 55 | "@storybook/addon-options": "5.3.21", 56 | "@storybook/react": "6.5.16", 57 | "@types/enzyme": "3.1.16", 58 | "@types/enzyme-adapter-react-16": "1.0.6", 59 | "@types/node": "16.18.96", 60 | "@types/react": "17.0.80", 61 | "@types/react-dom": "17.0.25", 62 | "@types/sinon": "10.0.11", 63 | "@types/storybook__addon-actions": "5.2.1", 64 | "@types/storybook__react": "5.2.1", 65 | "babel-core": "7.0.0-bridge.0", 66 | "babel-eslint": "10.1.0", 67 | "babel-loader": "8.2.3", 68 | "cpy-cli": "4.2.0", 69 | "cross-env": "7.0.3", 70 | "enzyme": "3.11.0", 71 | "enzyme-adapter-react-16": "1.9.1", 72 | "gh-pages": "3.2.3", 73 | "light-ts-loader": "1.1.2", 74 | "npm-run-all2": "5.0.2", 75 | "prettier": "2.6.2", 76 | "react": "16.14.0", 77 | "react-dom": "16.14.0", 78 | "react-test-renderer": "16.14.0", 79 | "rollup": "1.32.1", 80 | "@rollup/plugin-babel": "5.0.0", 81 | "rollup-plugin-commonjs": "10.1.0", 82 | "rollup-plugin-node-globals": "1.4.0", 83 | "@rollup/plugin-node-resolve": "6.0.0", 84 | "rollup-plugin-replace": "2.2.0", 85 | "rollup-plugin-typescript2": "0.22.0", 86 | "rollup-watch": "4.3.1", 87 | "sinon": "13.0.1", 88 | "tslint": "6.1.3", 89 | "tslint-eslint-rules": "5.4.0", 90 | "tslint-plugin-prettier": "2.3.0", 91 | "tslint-react": "5.0.0", 92 | "typescript": "5.4.5" 93 | }, 94 | "files": [ 95 | "lib" 96 | ], 97 | "avaron": { 98 | "fixture": "./fixture.html" 99 | }, 100 | "dependencies": { 101 | "re-resizable": "6.11.2", 102 | "react-draggable": "4.4.6", 103 | "tslib": "2.6.2" 104 | }, 105 | "peerDependencies": { 106 | "react": ">=16.3.0", 107 | "react-dom": ">=16.3.0" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/react-rnd/34ccbfa162775b305b0c84836c8082e8caee9500/screenshot.gif -------------------------------------------------------------------------------- /scripts/deploy-minor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git config --global -l 4 | git config --global user.email bokuweb12@gmail.com 5 | git config --global user.name bokuweb 6 | git remote --v 7 | git reset --hard HEAD 8 | npm version minor 9 | git push origin master 10 | git push --tags 11 | npm publish 12 | -------------------------------------------------------------------------------- /scripts/deploy-patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git config --global -l 4 | git config --global user.email bokuweb12@gmail.com 5 | git config --global user.name bokuweb 6 | git remote --v 7 | git reset --hard HEAD 8 | npm version patch 9 | git push origin master 10 | git push --tags 11 | npm publish 12 | -------------------------------------------------------------------------------- /scripts/prod.common.js: -------------------------------------------------------------------------------- 1 | import replace from 'rollup-plugin-replace'; 2 | import typescript from 'rollup-plugin-typescript2'; 3 | 4 | export default { 5 | input: 'src/index.tsx', 6 | plugins: [ 7 | typescript({ 8 | tsconfig: 'tsconfig.json', 9 | exclude: ['*.d.ts', 'stories'], 10 | }), 11 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), 12 | ], 13 | output: { 14 | sourcemap: true, 15 | exports: 'named', 16 | name: 'react-sortable-pane', 17 | globals: { 18 | react: 'React', 19 | 're-resizable': 'Resizable', 20 | 'react-draggable': 'Draggable', 21 | }, 22 | }, 23 | external: ['react', 're-resizable', 'react-draggable'], 24 | }; 25 | -------------------------------------------------------------------------------- /scripts/prod.es5.js: -------------------------------------------------------------------------------- 1 | import common from './prod.common'; 2 | 3 | export default Object.assign({}, common, { 4 | output: { 5 | file: 'lib/index.es5.js', 6 | format: 'cjs', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /scripts/prod.js: -------------------------------------------------------------------------------- 1 | import common from './prod.common'; 2 | 3 | export default Object.assign({}, common, { 4 | output: { 5 | file: 'lib/index.js', 6 | format: 'es', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /src/index.js.flow: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import type { ResizeDirection, ResizeCallback, ResizeStartCallback } from 're-resizable'; 5 | 6 | export type Grid = [number, number]; 7 | 8 | export type Position = { 9 | x: number, 10 | y: number, 11 | }; 12 | 13 | export type DraggableData = { 14 | node: HTMLElement, 15 | deltaX: number, 16 | deltaY: number, 17 | lastX: number, 18 | lastY: number, 19 | } & Position; 20 | 21 | export type RndDragCallback = (e: Event, data: DraggableData) => void | false; 22 | 23 | export type RndResizeStartCallback = ( 24 | e: SyntheticMouseEvent | SyntheticTouchEvent, 25 | dir: ResizeDirection, 26 | refToElement: React.ElementRef<'div'>, 27 | ) => void; 28 | 29 | export type ResizableDelta = { 30 | width: number, 31 | height: number, 32 | }; 33 | 34 | export type RndResizeCallback = ( 35 | e: MouseEvent | TouchEvent, 36 | dir: ResizeDirection, 37 | refToElement: React.ElementRef<'div'>, 38 | delta: ResizableDelta, 39 | position: Position, 40 | ) => void; 41 | 42 | type Size = { 43 | width: string | number, 44 | height: string | number, 45 | }; 46 | 47 | type State = { 48 | original: Position, 49 | bounds: { 50 | top: number, 51 | right: number, 52 | bottom: number, 53 | left: number, 54 | }, 55 | maxWidth?: number | string, 56 | maxHeight?: number | string, 57 | }; 58 | 59 | export type ResizeEnable = { 60 | bottom?: boolean, 61 | bottomLeft?: boolean, 62 | bottomRight?: boolean, 63 | left?: boolean, 64 | right?: boolean, 65 | top?: boolean, 66 | topLeft?: boolean, 67 | topRight?: boolean, 68 | }; 69 | 70 | export type HandleClasses = { 71 | bottom?: string, 72 | bottomLeft?: string, 73 | bottomRight?: string, 74 | left?: string, 75 | right?: string, 76 | top?: string, 77 | topLeft?: string, 78 | topRight?: string, 79 | }; 80 | 81 | type Style = { 82 | [key: string]: string | number, 83 | }; 84 | 85 | export type HandleStyles = { 86 | bottom?: Style, 87 | bottomLeft?: Style, 88 | bottomRight?: Style, 89 | left?: Style, 90 | right?: Style, 91 | top?: Style, 92 | topLeft?: Style, 93 | topRight?: Style, 94 | }; 95 | 96 | export type HandleComponent = { 97 | top?: React.ReactElement; 98 | right?: React.ReactElement; 99 | bottom?: React.ReactElement; 100 | left?: React.ReactElement; 101 | topRight?: React.ReactElement; 102 | bottomRight?: React.ReactElement; 103 | bottomLeft?: React.ReactElement; 104 | topLeft?: React.ReactElement; 105 | } 106 | 107 | export type Props = { 108 | dragGrid?: Grid, 109 | default?: { 110 | x: number, 111 | y: number, 112 | } & Size, 113 | position?: { 114 | x: number, 115 | y: number, 116 | }, 117 | size?: Size, 118 | resizeGrid?: Grid, 119 | bounds?: string, 120 | onResizeStart?: RndResizeStartCallback, 121 | onResize?: RndResizeCallback, 122 | onResizeStop?: RndResizeCallback, 123 | onDragStart?: RndDragCallback, 124 | onDrag?: RndDragCallback, 125 | onDragStop?: RndDragCallback, 126 | className?: string, 127 | style?: Style, 128 | children?: React.Node, 129 | enableResizing?: ResizeEnable, 130 | extendsProps?: { [key: string]: any }, 131 | resizeHandleClasses?: HandleClasses, 132 | resizeHandleStyles?: HandleStyles, 133 | resizeHandleComponent?: HandleComponent, 134 | resizeHandleWrapperClass?: string, 135 | resizeHandleWrapperStyle?: Style, 136 | lockAspectRatio?: boolean | number, 137 | lockAspectRatioExtraWidth?: number, 138 | lockAspectRatioExtraHeight?: number, 139 | maxHeight?: number | string, 140 | maxWidth?: number | string, 141 | minHeight?: number | string, 142 | minWidth?: number | string, 143 | dragAxis?: 'x' | 'y' | 'both' | 'none', 144 | dragHandleClassName?: string, 145 | disableDragging?: boolean, 146 | cancel?: boolean, 147 | enableUserSelectHack?: boolean, 148 | scale?: number, 149 | }; 150 | 151 | export class Rnd extends React.Component { 152 | static defaultProps = { 153 | maxWidth: Number.MAX_SAFE_INTEGER, 154 | maxHeight: Number.MAX_SAFE_INTEGER, 155 | onResizeStart: () => {}, 156 | onResize: () => {}, 157 | onResizeStop: () => {}, 158 | onDragStart: () => {}, 159 | onDrag: () => {}, 160 | onDragStop: () => {}, 161 | scale: 1, 162 | }; 163 | 164 | updateSize(size: { width: number | string, height: number | string }) { 165 | } 166 | 167 | updatePosition(position: Position) { 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/index.test.tsx: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | /* TODO: mask for now 4 | import test from "ava"; 5 | import * as React from "react"; 6 | import { spy } from "sinon"; 7 | import Enzyme, { mount } from "enzyme"; 8 | import Adapter from "enzyme-adapter-react-16"; 9 | import { Rnd } from "./"; 10 | 11 | Enzyme.configure({ adapter: new Adapter() }); 12 | 13 | const mouseMove = (x: number, y: number) => { 14 | const event = document.createEvent("MouseEvents"); 15 | event.initMouseEvent("mousemove", true, true, window, 0, 0, 0, x, y, false, false, false, false, 0, null); 16 | document.dispatchEvent(event); 17 | return event; 18 | }; 19 | 20 | const mouseUp = (x: number, y: number) => { 21 | const event = document.createEvent("MouseEvents"); 22 | event.initMouseEvent("mouseup", true, true, window, 0, 0, 0, x, y, false, false, false, false, 0, null); 23 | document.dispatchEvent(event); 24 | return event; 25 | }; 26 | 27 | test.beforeEach(async (t) => { 28 | const div = document.createElement("div"); 29 | document.body.appendChild(div); 30 | }); 31 | 32 | test("should mount without error", async (t) => { 33 | const rnd = mount(); 34 | t.truthy(!!rnd); 35 | }); 36 | 37 | test("Should custom class name be applied to box", async (t) => { 38 | const rnd = mount(); 39 | t.truthy(rnd.getDOMNode().classList.contains("custom-class-name")); 40 | }); 41 | 42 | test("Should render custom components", async (t) => { 43 | const CustomComponent = () =>
; 44 | 45 | const rnd = mount( 46 | , 50 | right: , 51 | bottom: , 52 | left: , 53 | topRight: , 54 | bottomRight: , 55 | bottomLeft: , 56 | topLeft: , 57 | }} 58 | />, 59 | ); 60 | const customComponents = rnd.find(".custom-component"); 61 | t.is(customComponents.length, 8); 62 | }); 63 | 64 | test("Should set handler className", async (t) => { 65 | const rnd = mount( 66 | , 79 | ); 80 | const handlers = rnd.find(".handler"); 81 | // FIXME: Is it a enzyme 3.x bug ? I can not understand why handlers.length equals 16. 82 | // When use enzyme v2.x this test is passed... 83 | // t.is(handlers.length, 8); 84 | t.is(handlers.length, 16); 85 | }); 86 | 87 | test("Should not render resizer when enable props all false", async (t) => { 88 | const rnd = mount( 89 | , 112 | ); 113 | const handlers = rnd.find(".handler"); 114 | t.is(handlers.length, 0); 115 | }); 116 | 117 | test("should call onMouseDown when mouse downed", async (t) => { 118 | const onMouseDown = spy(); 119 | const rnd = mount(); 120 | rnd.find("div").at(0).simulate("mousedown"); 121 | t.is(onMouseDown.callCount, 1); 122 | t.is(onMouseDown.firstCall.args[0].type, "mousedown"); 123 | }); 124 | 125 | test("should call onDragStart when start dragging", async (t) => { 126 | const onDragStart = spy(); 127 | const rnd = mount(); 128 | rnd.find("div").at(0).simulate("mousedown"); 129 | t.is(onDragStart.callCount, 1); 130 | t.is(onDragStart.firstCall.args[0].type, "mousedown"); 131 | t.is(onDragStart.firstCall.args[1].x, 200); 132 | t.is(onDragStart.firstCall.args[1].y, 200); 133 | }); 134 | 135 | test("should call onDrag when dragging", async (t) => { 136 | const onDrag = spy(); 137 | const rnd = mount(); 138 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 139 | mouseMove(200, 220); 140 | t.is(onDrag.callCount, 1); 141 | t.is(onDrag.firstCall.args[1].x, 500); 142 | t.is(onDrag.firstCall.args[1].y, 520); 143 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(400px, 420px)"), -1); 144 | }); 145 | 146 | test("should call onDragStop when drag stop", async (t) => { 147 | const onDragStop = spy(); 148 | const rnd = mount(); 149 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 150 | mouseMove(200, 220); 151 | mouseUp(100, 120); 152 | t.is(onDragStop.callCount, 1); 153 | t.is(onDragStop.firstCall.args[1].x, 200); 154 | t.is(onDragStop.firstCall.args[1].y, 220); 155 | }); 156 | 157 | test("should dragging disabled when axis equals none", async (t) => { 158 | const onDrag = spy(); 159 | const rnd = mount(, { 160 | attachTo: document.querySelector("div"), 161 | }); 162 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 163 | mouseMove(200, 220); 164 | t.is(onDrag.callCount, 0); 165 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 100px)"), -1); 166 | }); 167 | 168 | test("should enable dragging only x when axis equals x", async (t) => { 169 | const onDrag = spy(); 170 | const rnd = mount(, { 171 | attachTo: document.querySelector("div"), 172 | }); 173 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 174 | mouseMove(200, 220); 175 | t.is(onDrag.callCount, 1); 176 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(300px, 100px)"), -1); 177 | }); 178 | 179 | test("xdragging only y when axis equals y", async (t) => { 180 | const onDrag = spy(); 181 | const rnd = mount(, { 182 | attachTo: document.querySelector("div"), 183 | }); 184 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 185 | mouseMove(200, 220); 186 | t.is(onDrag.callCount, 1); 187 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 320px)"), -1); 188 | }); 189 | 190 | test("should enable dragging both x & y when axis equals both", async (t) => { 191 | const onDrag = spy(); 192 | const rnd = mount(, { 193 | attachTo: document.querySelector("div"), 194 | }); 195 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 196 | mouseMove(200, 220); 197 | t.is(onDrag.callCount, 1); 198 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(300px, 320px)"), -1); 199 | }); 200 | 201 | test("should snap when dragging smaller than threshold", async (t) => { 202 | const rnd = mount(, { 203 | attachTo: document.querySelector("div"), 204 | }); 205 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 206 | mouseMove(14, 49); 207 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 100px)"), -1); 208 | }); 209 | 210 | test("should snap when dragging larger than threshold", async (t) => { 211 | const rnd = mount(, { 212 | attachTo: document.querySelector("div"), 213 | }); 214 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 215 | mouseMove(15, 50); 216 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(130px, 200px)"), -1); 217 | }); 218 | 219 | test("should limit position by parent bounds", async (t) => { 220 | const rnd = mount( 221 |
222 | 223 |
, 224 | { attachTo: document.querySelector("div") }, 225 | ); 226 | rnd.find("div").at(0).childAt(0).simulate("mousedown", { clientX: 50, clientY: 50 }); 227 | mouseMove(1000, 1000); 228 | t.not( 229 | (rnd.find("div").at(0).childAt(0).getDOMNode().getAttribute("style") || "").indexOf( 230 | "transform: translate(700px, 500px)", 231 | ), 232 | -1, 233 | ); 234 | }); 235 | 236 | test("should limit position by selector bounds", async (t) => { 237 | const rnd = mount( 238 |
239 |
240 | 241 |
242 |
, 243 | { attachTo: document.querySelector("div") }, 244 | ); 245 | 246 | rnd.find("div").at(0).childAt(0).childAt(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 247 | mouseMove(2000, 2000); 248 | t.not( 249 | (rnd.find("div").at(0).childAt(0).childAt(0).getDOMNode().getAttribute("style") || "").indexOf( 250 | "translate(900px, 700px)", 251 | ), 252 | -1, 253 | ); 254 | }); 255 | 256 | test("Should box width and height equal 100px", async (t) => { 257 | const rnd = mount( 258 | , 271 | { attachTo: document.querySelector("div") }, 272 | ); 273 | t.is((rnd.getDOMNode() as HTMLElement).style.width, "100px"); 274 | t.is((rnd.getDOMNode() as HTMLElement).style.height, "100px"); 275 | }); 276 | 277 | test("Should call onResizeStart when mousedown", async (t) => { 278 | const onResizeStart = spy(); 279 | const rnd = mount( 280 | , 304 | { attachTo: document.querySelector("div") }, 305 | ); 306 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 307 | t.is(onResizeStart.callCount, 1); 308 | t.is(onResizeStart.getCall(0).args[1], "right"); 309 | }); 310 | 311 | test("should call onResize with expected args when resize direction right", async (t) => { 312 | const onResize = spy(); 313 | const onResizeStart = spy(); 314 | const rnd = mount( 315 | , 340 | { attachTo: document.querySelector("div") }, 341 | ); 342 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 343 | mouseMove(200, 220); 344 | t.is(onResize.callCount, 1); 345 | t.truthy(onResize.getCall(0).args[0] instanceof Event); 346 | t.is(onResize.getCall(0).args[1], "right"); 347 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 300); 348 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100); 349 | t.deepEqual(onResize.getCall(0).args[3], { width: 200, height: 0 }); 350 | }); 351 | 352 | test("should call onResizeStop with expected args when resize direction right", async (t) => { 353 | const onResize = spy(); 354 | const onResizeStop = spy(); 355 | const rnd = mount( 356 | , 381 | { attachTo: document.querySelector("div") }, 382 | ); 383 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 384 | mouseMove(200, 220); 385 | mouseUp(200, 220); 386 | t.is(onResizeStop.callCount, 1); 387 | t.truthy(onResize.getCall(0).args[0] instanceof Event); 388 | t.is(onResize.getCall(0).args[1], "right"); 389 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 300); 390 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100); 391 | t.deepEqual(onResize.getCall(0).args[3], { width: 200, height: 0 }); 392 | }); 393 | 394 | test("should move x when resizing left", async (t) => { 395 | const onResize = spy(); 396 | const onResizeStart = spy(); 397 | const rnd = mount( 398 | , 423 | { attachTo: document.querySelector("div") }, 424 | ); 425 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 426 | mouseMove(-50, 0); 427 | t.is(onResize.callCount, 1); 428 | t.is(onResize.getCall(0).args[1], "left"); 429 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 150); 430 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100); 431 | t.deepEqual(onResize.getCall(0).args[3], { width: 50, height: 0 }); 432 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(50px, 100px)"), -1); 433 | }); 434 | 435 | test("should move y when resizing top", async (t) => { 436 | const onResize = spy(); 437 | const onResizeStart = spy(); 438 | const rnd = mount( 439 | , 464 | { attachTo: document.querySelector("div") }, 465 | ); 466 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 467 | mouseMove(0, -50); 468 | t.is(onResize.callCount, 1); 469 | t.is(onResize.getCall(0).args[1], "top"); 470 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 100); 471 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 150); 472 | t.deepEqual(onResize.getCall(0).args[3], { width: 0, height: 50 }); 473 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 50px)"), -1); 474 | }); 475 | 476 | test("should snapped by original grid when x axis resizing smaller then threshold", async (t) => { 477 | const onResize = spy(); 478 | const rnd = mount( 479 | , 504 | { attachTo: document.querySelector("div") }, 505 | ); 506 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 507 | mouseMove(9, 0); 508 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 100); 509 | }); 510 | 511 | test("should snapped by original grid when x axis resizing larger then threshold", async (t) => { 512 | const onResize = spy(); 513 | const rnd = mount( 514 | , 539 | { attachTo: document.querySelector("div") }, 540 | ); 541 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 542 | mouseMove(10, 0); 543 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 120); 544 | }); 545 | 546 | test("should snapped by original grid when y axis resizing smaller then threshold", async (t) => { 547 | const onResize = spy(); 548 | const rnd = mount( 549 | , 574 | { attachTo: document.querySelector("div") }, 575 | ); 576 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 577 | mouseMove(0, 9); 578 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100); 579 | }); 580 | 581 | test("should snapped by original grid when y axis resizing larger then threshold", async (t) => { 582 | const onResize = spy(); 583 | const rnd = mount( 584 | , 609 | { attachTo: document.querySelector("div") }, 610 | ); 611 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 612 | mouseMove(0, 10); 613 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 120); 614 | }); 615 | 616 | test("should snapped by original grid when y axis resizing larger then threshold", async (t) => { 617 | const onResize = spy(); 618 | const rnd = mount( 619 | , 644 | { attachTo: document.querySelector("div") }, 645 | ); 646 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 647 | mouseMove(20, 10); 648 | // TODO: It'a resizable-box grid bug?? 649 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 120); 650 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 120); 651 | }); 652 | 653 | test("should clamped by parent size", async (t) => { 654 | const rnd = mount( 655 |
656 | 680 |
, 681 | { attachTo: document.querySelector("div") }, 682 | ); 683 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 684 | mouseMove(1200, 1200); 685 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.width, "800px"); 686 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.height, "600px"); 687 | }); 688 | 689 | test("should clamped by selector size", async (t) => { 690 | const rnd = mount( 691 |
692 |
693 | 717 |
718 |
, 719 | { attachTo: document.querySelector("div") }, 720 | ); 721 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 722 | mouseMove(2000, 2000); 723 | t.is((rnd.childAt(0).childAt(0).getDOMNode() as HTMLElement).style.width, "1000px"); 724 | t.is((rnd.childAt(0).childAt(0).getDOMNode() as HTMLElement).style.height, "800px"); 725 | }); 726 | 727 | test("should clamped by boundary element size", async (t) => { 728 | const rnd = mount( 729 |
730 |
731 | 755 |
756 |
, 757 | { attachTo: document.querySelector("div") }, 758 | ); 759 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 }); 760 | mouseMove(1200, 1200); 761 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.width, "800px"); 762 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.height, "600px"); 763 | }); 764 | 765 | test("should get rnd updated when updatePosition invoked", async (t) => { 766 | const rnd = mount(); 767 | rnd.instance().updatePosition({ x: 200, y: 300 }); 768 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(200px, 300px)"), -1); 769 | }); 770 | 771 | test("should get rnd updated when updateSize invoked", async (t) => { 772 | const rnd = mount(); 773 | rnd.instance().updateSize({ width: 200, height: 300 }); 774 | t.is((rnd.getDOMNode() as HTMLElement).style.width, "200px"); 775 | t.is((rnd.getDOMNode() as HTMLElement).style.height, "300px"); 776 | }); 777 | 778 | test("should find drag handle class when dragHandleClassName props passed", async (t) => { 779 | const onDrag = spy(); 780 | const rnd = mount( 781 | 782 |
Test
783 |
, 784 | ); 785 | rnd.find("div.handle").simulate("mousedown", { clientX: 0, clientY: 0 }); 786 | mouseMove(200, 220); 787 | t.is(onDrag.callCount, 1); 788 | }); 789 | 790 | test("should pass data- attribute", async (t) => { 791 | const rnd = mount(Test); 792 | t.is(!!rnd.find("[data-foo]"), true); 793 | }); 794 | 795 | */ 796 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Draggable, { DraggableEventHandler, DraggableProps } from "react-draggable"; 3 | import { Enable, Resizable, ResizeDirection } from "re-resizable"; 4 | import { flushSync } from "react-dom"; 5 | 6 | export type Grid = [number, number]; 7 | 8 | export type Position = { 9 | x: number; 10 | y: number; 11 | }; 12 | 13 | export type DraggableData = { 14 | node: HTMLElement; 15 | deltaX: number; 16 | deltaY: number; 17 | lastX: number; 18 | lastY: number; 19 | } & Position; 20 | 21 | export type RndDragCallback = DraggableEventHandler; 22 | 23 | export type RndDragEvent = 24 | | React.MouseEvent 25 | | React.TouchEvent 26 | | MouseEvent 27 | | TouchEvent; 28 | 29 | export type RndResizeStartCallback = ( 30 | e: React.MouseEvent | React.TouchEvent, 31 | dir: ResizeDirection, 32 | elementRef: HTMLElement, 33 | ) => void | boolean; 34 | 35 | export type ResizableDelta = { 36 | width: number; 37 | height: number; 38 | }; 39 | 40 | export type RndResizeCallback = ( 41 | e: MouseEvent | TouchEvent, 42 | dir: ResizeDirection, 43 | elementRef: HTMLElement, 44 | delta: ResizableDelta, 45 | position: Position, 46 | ) => void; 47 | 48 | type Size = { 49 | width: string | number; 50 | height: string | number; 51 | }; 52 | 53 | type State = { 54 | resizing: boolean; 55 | bounds: { 56 | top: number; 57 | right: number; 58 | bottom: number; 59 | left: number; 60 | }; 61 | maxWidth?: number | string; 62 | maxHeight?: number | string; 63 | }; 64 | 65 | type MaxSize = { 66 | maxWidth: number | string; 67 | maxHeight: number | string; 68 | }; 69 | 70 | export type ResizeEnable = 71 | | { 72 | bottom?: boolean; 73 | bottomLeft?: boolean; 74 | bottomRight?: boolean; 75 | left?: boolean; 76 | right?: boolean; 77 | top?: boolean; 78 | topLeft?: boolean; 79 | topRight?: boolean; 80 | } 81 | | boolean; 82 | 83 | export type HandleClasses = { 84 | bottom?: string; 85 | bottomLeft?: string; 86 | bottomRight?: string; 87 | left?: string; 88 | right?: string; 89 | top?: string; 90 | topLeft?: string; 91 | topRight?: string; 92 | }; 93 | 94 | export type HandleStyles = { 95 | bottom?: React.CSSProperties; 96 | bottomLeft?: React.CSSProperties; 97 | bottomRight?: React.CSSProperties; 98 | left?: React.CSSProperties; 99 | right?: React.CSSProperties; 100 | top?: React.CSSProperties; 101 | topLeft?: React.CSSProperties; 102 | topRight?: React.CSSProperties; 103 | }; 104 | 105 | export type HandleComponent = { 106 | top?: React.ReactElement; 107 | right?: React.ReactElement; 108 | bottom?: React.ReactElement; 109 | left?: React.ReactElement; 110 | topRight?: React.ReactElement; 111 | bottomRight?: React.ReactElement; 112 | bottomLeft?: React.ReactElement; 113 | topLeft?: React.ReactElement; 114 | }; 115 | 116 | export interface Props { 117 | dragGrid?: Grid; 118 | default?: { 119 | x: number; 120 | y: number; 121 | } & Size; 122 | position?: { 123 | x: number; 124 | y: number; 125 | }; 126 | size?: Size; 127 | resizeGrid?: Grid; 128 | bounds?: string | Element; 129 | onMouseDown?: (e: MouseEvent) => void; 130 | onMouseUp?: (e: MouseEvent) => void; 131 | onResizeStart?: RndResizeStartCallback; 132 | onResize?: RndResizeCallback; 133 | onResizeStop?: RndResizeCallback; 134 | onDragStart?: RndDragCallback; 135 | onDrag?: RndDragCallback; 136 | onDragStop?: RndDragCallback; 137 | className?: string; 138 | style?: React.CSSProperties; 139 | children?: React.ReactNode; 140 | enableResizing?: ResizeEnable; 141 | resizeHandleClasses?: HandleClasses; 142 | resizeHandleStyles?: HandleStyles; 143 | resizeHandleWrapperClass?: string; 144 | resizeHandleWrapperStyle?: React.CSSProperties; 145 | resizeHandleComponent?: HandleComponent; 146 | lockAspectRatio?: boolean | number; 147 | lockAspectRatioExtraWidth?: number; 148 | lockAspectRatioExtraHeight?: number; 149 | maxHeight?: number | string; 150 | maxWidth?: number | string; 151 | minHeight?: number | string; 152 | minWidth?: number | string; 153 | dragAxis?: "x" | "y" | "both" | "none"; 154 | dragHandleClassName?: string; 155 | disableDragging?: boolean; 156 | cancel?: string; 157 | enableUserSelectHack?: boolean; 158 | dragPositionOffset?: DraggableProps["positionOffset"]; 159 | allowAnyClick?: boolean; 160 | scale?: number; 161 | [key: string]: any; 162 | } 163 | 164 | const resizableStyle = { 165 | width: "auto" as "auto", 166 | height: "auto" as "auto", 167 | display: "inline-block" as "inline-block", 168 | position: "absolute" as "absolute", 169 | top: 0, 170 | left: 0, 171 | }; 172 | 173 | const getEnableResizingByFlag = (flag: boolean): Enable => ({ 174 | bottom: flag, 175 | bottomLeft: flag, 176 | bottomRight: flag, 177 | left: flag, 178 | right: flag, 179 | top: flag, 180 | topLeft: flag, 181 | topRight: flag, 182 | }); 183 | 184 | interface DefaultProps { 185 | maxWidth: number; 186 | maxHeight: number; 187 | onResizeStart: RndResizeStartCallback; 188 | onResize: RndResizeCallback; 189 | onResizeStop: RndResizeCallback; 190 | onDragStart: RndDragCallback; 191 | onDrag: RndDragCallback; 192 | onDragStop: RndDragCallback; 193 | scale: number; 194 | } 195 | 196 | export class Rnd extends React.PureComponent { 197 | public static defaultProps: DefaultProps = { 198 | maxWidth: Number.MAX_SAFE_INTEGER, 199 | maxHeight: Number.MAX_SAFE_INTEGER, 200 | scale: 1, 201 | onResizeStart: () => {}, 202 | onResize: () => {}, 203 | onResizeStop: () => {}, 204 | onDragStart: () => {}, 205 | onDrag: () => {}, 206 | onDragStop: () => {}, 207 | }; 208 | resizable!: Resizable; 209 | draggable!: Draggable; 210 | resizingPosition = { x: 0, y: 0 }; 211 | offsetFromParent = { left: 0, top: 0 }; 212 | resizableElement: { current: HTMLElement | null } = { current: null }; 213 | originalPosition = { x: 0, y: 0 }; 214 | 215 | constructor(props: Props) { 216 | super(props); 217 | this.state = { 218 | resizing: false, 219 | bounds: { 220 | top: 0, 221 | right: 0, 222 | bottom: 0, 223 | left: 0, 224 | }, 225 | maxWidth: props.maxWidth, 226 | maxHeight: props.maxHeight, 227 | }; 228 | 229 | this.onResizeStart = this.onResizeStart.bind(this); 230 | this.onResize = this.onResize.bind(this); 231 | this.onResizeStop = this.onResizeStop.bind(this); 232 | this.onDragStart = this.onDragStart.bind(this); 233 | this.onDrag = this.onDrag.bind(this); 234 | this.onDragStop = this.onDragStop.bind(this); 235 | this.getMaxSizesFromProps = this.getMaxSizesFromProps.bind(this); 236 | } 237 | 238 | componentDidMount() { 239 | this.updateOffsetFromParent(); 240 | const { left, top } = this.offsetFromParent; 241 | const { x, y } = this.getDraggablePosition(); 242 | this.draggable.setState({ 243 | x: x - left, 244 | y: y - top, 245 | }); 246 | // HACK: Apply position adjustment 247 | this.forceUpdate(); 248 | } 249 | 250 | // HACK: To get `react-draggable` state x and y. 251 | getDraggablePosition(): { x: number; y: number } { 252 | const { x, y } = (this.draggable as any).state; 253 | return { x, y }; 254 | } 255 | 256 | getParent() { 257 | return this.resizable && (this.resizable as any).parentNode; 258 | } 259 | 260 | getParentSize(): { width: number; height: number } { 261 | return (this.resizable as any).getParentSize(); 262 | } 263 | 264 | getMaxSizesFromProps(): MaxSize { 265 | const maxWidth = typeof this.props.maxWidth === "undefined" ? Number.MAX_SAFE_INTEGER : this.props.maxWidth; 266 | const maxHeight = typeof this.props.maxHeight === "undefined" ? Number.MAX_SAFE_INTEGER : this.props.maxHeight; 267 | return { maxWidth, maxHeight }; 268 | } 269 | 270 | getSelfElement(): HTMLElement | null { 271 | return this.resizable && this.resizable.resizable; 272 | } 273 | 274 | getOffsetHeight(boundary: HTMLElement) { 275 | const scale = this.props.scale as number; 276 | switch (this.props.bounds) { 277 | case "window": 278 | return window.innerHeight / scale; 279 | case "body": 280 | return document.body.offsetHeight / scale; 281 | default: 282 | return boundary.offsetHeight; 283 | } 284 | } 285 | 286 | getOffsetWidth(boundary: HTMLElement) { 287 | const scale = this.props.scale as number; 288 | switch (this.props.bounds) { 289 | case "window": 290 | return window.innerWidth / scale; 291 | case "body": 292 | return document.body.offsetWidth / scale; 293 | default: 294 | return boundary.offsetWidth; 295 | } 296 | } 297 | 298 | onDragStart(e: RndDragEvent, data: DraggableData) { 299 | if (this.props.onDragStart) { 300 | this.props.onDragStart(e, data); 301 | } 302 | const pos = this.getDraggablePosition(); 303 | this.originalPosition = pos; 304 | if (!this.props.bounds) return; 305 | const parent = this.getParent(); 306 | const scale = this.props.scale as number; 307 | let boundary; 308 | if (this.props.bounds === "parent") { 309 | boundary = parent; 310 | } else if (this.props.bounds === "body") { 311 | const parentRect = parent.getBoundingClientRect(); 312 | const parentLeft = parentRect.left; 313 | const parentTop = parentRect.top; 314 | const bodyRect = document.body.getBoundingClientRect(); 315 | const left = -(parentLeft - parent.offsetLeft * scale - bodyRect.left) / scale; 316 | const top = -(parentTop - parent.offsetTop * scale - bodyRect.top) / scale; 317 | const right = (document.body.offsetWidth - this.resizable.size.width * scale) / scale + left; 318 | const bottom = (document.body.offsetHeight - this.resizable.size.height * scale) / scale + top; 319 | return this.setState({ bounds: { top, right, bottom, left } }); 320 | } else if (this.props.bounds === "window") { 321 | if (!this.resizable) return; 322 | const parentRect = parent.getBoundingClientRect(); 323 | const parentLeft = parentRect.left; 324 | const parentTop = parentRect.top; 325 | const left = -(parentLeft - parent.offsetLeft * scale) / scale; 326 | const top = -(parentTop - parent.offsetTop * scale) / scale; 327 | const right = (window.innerWidth - this.resizable.size.width * scale) / scale + left; 328 | const bottom = (window.innerHeight - this.resizable.size.height * scale) / scale + top; 329 | return this.setState({ bounds: { top, right, bottom, left } }); 330 | } else if (typeof this.props.bounds === "string") { 331 | boundary = document.querySelector(this.props.bounds); 332 | } else if (this.props.bounds instanceof HTMLElement) { 333 | boundary = this.props.bounds; 334 | } 335 | if (!(boundary instanceof HTMLElement) || !(parent instanceof HTMLElement)) { 336 | return; 337 | } 338 | const boundaryRect = boundary.getBoundingClientRect(); 339 | const boundaryLeft = boundaryRect.left; 340 | const boundaryTop = boundaryRect.top; 341 | const parentRect = parent.getBoundingClientRect(); 342 | const parentLeft = parentRect.left; 343 | const parentTop = parentRect.top; 344 | const left = (boundaryLeft - parentLeft) / scale; 345 | const top = boundaryTop - parentTop; 346 | if (!this.resizable) return; 347 | this.updateOffsetFromParent(); 348 | const offset = this.offsetFromParent; 349 | this.setState({ 350 | bounds: { 351 | top: top - offset.top, 352 | right: left + (boundary.offsetWidth - this.resizable.size.width) - offset.left / scale, 353 | bottom: top + (boundary.offsetHeight - this.resizable.size.height) - offset.top, 354 | left: left - offset.left / scale, 355 | }, 356 | }); 357 | } 358 | 359 | onDrag(e: RndDragEvent, data: DraggableData) { 360 | if (!this.props.onDrag) return; 361 | const { left, top } = this.offsetFromParent; 362 | if (!this.props.dragAxis || this.props.dragAxis === "both") { 363 | return this.props.onDrag(e, { ...data, x: data.x + left, y: data.y + top }); 364 | } else if (this.props.dragAxis === "x") { 365 | return this.props.onDrag(e, { ...data, x: data.x + left, y: this.originalPosition.y + top, deltaY: 0 }); 366 | } else if (this.props.dragAxis === "y") { 367 | return this.props.onDrag(e, { ...data, x: this.originalPosition.x + left, y: data.y + top, deltaX: 0 }); 368 | } 369 | } 370 | 371 | onDragStop(e: RndDragEvent, data: DraggableData) { 372 | if (!this.props.onDragStop) return; 373 | const { left, top } = this.offsetFromParent; 374 | if (!this.props.dragAxis || this.props.dragAxis === "both") { 375 | return this.props.onDragStop(e, { ...data, x: data.x + left, y: data.y + top }); 376 | } else if (this.props.dragAxis === "x") { 377 | return this.props.onDragStop(e, { ...data, x: data.x + left, y: this.originalPosition.y + top, deltaY: 0 }); 378 | } else if (this.props.dragAxis === "y") { 379 | return this.props.onDragStop(e, { ...data, x: this.originalPosition.x + left, y: data.y + top, deltaX: 0 }); 380 | } 381 | } 382 | 383 | onResizeStart( 384 | e: React.MouseEvent | React.TouchEvent, 385 | dir: ResizeDirection, 386 | elementRef: HTMLElement, 387 | ) { 388 | e.stopPropagation(); 389 | this.setState({ 390 | resizing: true, 391 | }); 392 | const scale = this.props.scale as number; 393 | const offset = this.offsetFromParent; 394 | const pos = this.getDraggablePosition(); 395 | this.resizingPosition = { x: pos.x + offset.left, y: pos.y + offset.top }; 396 | this.originalPosition = pos; 397 | 398 | if (this.props.bounds) { 399 | const parent = this.getParent(); 400 | let boundary; 401 | if (this.props.bounds === "parent") { 402 | boundary = parent; 403 | } else if (this.props.bounds === "body") { 404 | boundary = document.body; 405 | } else if (this.props.bounds === "window") { 406 | boundary = window; 407 | } else if (typeof this.props.bounds === "string") { 408 | boundary = document.querySelector(this.props.bounds); 409 | } else if (this.props.bounds instanceof HTMLElement) { 410 | boundary = this.props.bounds; 411 | } 412 | 413 | const self = this.getSelfElement(); 414 | if ( 415 | self instanceof Element && 416 | (boundary instanceof HTMLElement || boundary === window) && 417 | parent instanceof HTMLElement 418 | ) { 419 | let { maxWidth, maxHeight } = this.getMaxSizesFromProps(); 420 | const parentSize = this.getParentSize(); 421 | if (maxWidth && typeof maxWidth === "string") { 422 | if (maxWidth.endsWith("%")) { 423 | const ratio = Number(maxWidth.replace("%", "")) / 100; 424 | maxWidth = parentSize.width * ratio; 425 | } else if (maxWidth.endsWith("px")) { 426 | maxWidth = Number(maxWidth.replace("px", "")); 427 | } 428 | } 429 | if (maxHeight && typeof maxHeight === "string") { 430 | if (maxHeight.endsWith("%")) { 431 | const ratio = Number(maxHeight.replace("%", "")) / 100; 432 | maxHeight = parentSize.height * ratio; 433 | } else if (maxHeight.endsWith("px")) { 434 | maxHeight = Number(maxHeight.replace("px", "")); 435 | } 436 | } 437 | const selfRect = self.getBoundingClientRect(); 438 | const selfLeft = selfRect.left; 439 | const selfTop = selfRect.top; 440 | const boundaryRect = this.props.bounds === "window" ? { left: 0, top: 0 } : boundary.getBoundingClientRect(); 441 | const boundaryLeft = boundaryRect.left; 442 | const boundaryTop = boundaryRect.top; 443 | const offsetWidth = this.getOffsetWidth(boundary); 444 | const offsetHeight = this.getOffsetHeight(boundary); 445 | const hasLeft = dir.toLowerCase().endsWith("left"); 446 | const hasRight = dir.toLowerCase().endsWith("right"); 447 | const hasTop = dir.startsWith("top"); 448 | const hasBottom = dir.startsWith("bottom"); 449 | 450 | if ((hasLeft || hasTop) && this.resizable) { 451 | const max = (selfLeft - boundaryLeft) / scale + this.resizable.size.width; 452 | this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); 453 | } 454 | // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. 455 | if (hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) { 456 | const max = offsetWidth + (boundaryLeft - selfLeft) / scale; 457 | this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); 458 | } 459 | if ((hasTop || hasLeft) && this.resizable) { 460 | const max = (selfTop - boundaryTop) / scale + this.resizable.size.height; 461 | this.setState({ 462 | maxHeight: max > Number(maxHeight) ? maxHeight : max, 463 | }); 464 | } 465 | // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. 466 | if (hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) { 467 | const max = offsetHeight + (boundaryTop - selfTop) / scale; 468 | this.setState({ 469 | maxHeight: max > Number(maxHeight) ? maxHeight : max, 470 | }); 471 | } 472 | } 473 | } else { 474 | this.setState({ 475 | maxWidth: this.props.maxWidth, 476 | maxHeight: this.props.maxHeight, 477 | }); 478 | } 479 | if (this.props.onResizeStart) { 480 | this.props.onResizeStart(e, dir, elementRef); 481 | } 482 | } 483 | 484 | onResize( 485 | e: MouseEvent | TouchEvent, 486 | direction: ResizeDirection, 487 | elementRef: HTMLElement, 488 | delta: { height: number; width: number }, 489 | ) { 490 | // INFO: Apply x and y position adjustments caused by resizing to draggable 491 | const newPos = { x: this.originalPosition.x, y: this.originalPosition.y }; 492 | const left = -delta.width; 493 | const top = -delta.height; 494 | const directions: ResizeDirection[] = ["top", "left", "topLeft", "bottomLeft", "topRight"]; 495 | 496 | if (directions.includes(direction)) { 497 | if (direction === "bottomLeft") { 498 | newPos.x += left; 499 | } else if (direction === "topRight") { 500 | newPos.y += top; 501 | } else { 502 | newPos.x += left; 503 | newPos.y += top; 504 | } 505 | } 506 | 507 | const draggableState = this.draggable.state as unknown as { x: number; y: number }; 508 | if (newPos.x !== draggableState.x || newPos.y !== draggableState.y) { 509 | flushSync(() => { 510 | this.draggable.setState(newPos); 511 | }); 512 | } 513 | 514 | this.updateOffsetFromParent(); 515 | const offset = this.offsetFromParent; 516 | const x = this.getDraggablePosition().x + offset.left; 517 | const y = this.getDraggablePosition().y + offset.top; 518 | 519 | this.resizingPosition = { x, y }; 520 | if (!this.props.onResize) return; 521 | this.props.onResize(e, direction, elementRef, delta, { 522 | x, 523 | y, 524 | }); 525 | } 526 | 527 | onResizeStop( 528 | e: MouseEvent | TouchEvent, 529 | direction: ResizeDirection, 530 | elementRef: HTMLElement, 531 | delta: { height: number; width: number }, 532 | ) { 533 | this.setState({ 534 | resizing: false, 535 | }); 536 | const { maxWidth, maxHeight } = this.getMaxSizesFromProps(); 537 | this.setState({ maxWidth, maxHeight }); 538 | if (this.props.onResizeStop) { 539 | this.props.onResizeStop(e, direction, elementRef, delta, this.resizingPosition); 540 | } 541 | } 542 | 543 | updateSize(size: { width: number | string; height: number | string }) { 544 | if (!this.resizable) return; 545 | this.resizable.updateSize({ width: size.width, height: size.height }); 546 | } 547 | 548 | updatePosition(position: Position) { 549 | this.draggable.setState(position); 550 | } 551 | 552 | updateOffsetFromParent() { 553 | const scale = this.props.scale as number; 554 | const parent = this.getParent(); 555 | const self = this.getSelfElement(); 556 | if (!parent || self === null) { 557 | return { 558 | top: 0, 559 | left: 0, 560 | }; 561 | } 562 | const parentRect = parent.getBoundingClientRect(); 563 | const parentLeft = parentRect.left; 564 | const parentTop = parentRect.top; 565 | const selfRect = self.getBoundingClientRect(); 566 | const position = this.getDraggablePosition(); 567 | const scrollLeft = parent.scrollLeft; 568 | const scrollTop = parent.scrollTop; 569 | this.offsetFromParent = { 570 | left: selfRect.left - parentLeft + scrollLeft - position.x * scale, 571 | top: selfRect.top - parentTop + scrollTop - position.y * scale, 572 | }; 573 | } 574 | 575 | render() { 576 | const { 577 | disableDragging, 578 | style, 579 | dragHandleClassName, 580 | position, 581 | onMouseDown, 582 | onMouseUp, 583 | dragAxis, 584 | dragGrid, 585 | bounds, 586 | enableUserSelectHack, 587 | cancel, 588 | children, 589 | onResizeStart, 590 | onResize, 591 | onResizeStop, 592 | onDragStart, 593 | onDrag, 594 | onDragStop, 595 | resizeHandleStyles, 596 | resizeHandleClasses, 597 | resizeHandleComponent, 598 | enableResizing, 599 | resizeGrid, 600 | resizeHandleWrapperClass, 601 | resizeHandleWrapperStyle, 602 | scale, 603 | allowAnyClick, 604 | dragPositionOffset, 605 | ...resizableProps 606 | } = this.props; 607 | const defaultValue = this.props.default ? { ...this.props.default } : undefined; 608 | // Remove unknown props, see also https://reactjs.org/warnings/unknown-prop.html 609 | delete resizableProps.default; 610 | 611 | const cursorStyle = disableDragging || dragHandleClassName ? { cursor: "auto" } : { cursor: "move" }; 612 | const innerStyle = { 613 | ...resizableStyle, 614 | ...cursorStyle, 615 | ...style, 616 | }; 617 | const { left, top } = this.offsetFromParent; 618 | let draggablePosition; 619 | if (position) { 620 | draggablePosition = { 621 | x: position.x - left, 622 | y: position.y - top, 623 | }; 624 | } 625 | // INFO: Make uncontorolled component when resizing to control position by setPostion. 626 | const pos = this.state.resizing ? undefined : draggablePosition; 627 | const dragAxisOrUndefined = this.state.resizing ? "both" : dragAxis; 628 | 629 | return ( 630 | { 632 | if (!c) return; 633 | this.draggable = c; 634 | }} 635 | handle={dragHandleClassName ? `.${dragHandleClassName}` : undefined} 636 | defaultPosition={defaultValue} 637 | onMouseDown={onMouseDown} 638 | // @ts-expect-error 639 | onMouseUp={onMouseUp} 640 | onStart={this.onDragStart} 641 | onDrag={this.onDrag} 642 | onStop={this.onDragStop} 643 | axis={dragAxisOrUndefined} 644 | disabled={disableDragging} 645 | grid={dragGrid} 646 | bounds={bounds ? this.state.bounds : undefined} 647 | position={pos} 648 | enableUserSelectHack={enableUserSelectHack} 649 | cancel={cancel} 650 | scale={scale} 651 | allowAnyClick={allowAnyClick} 652 | nodeRef={this.resizableElement} 653 | positionOffset={dragPositionOffset} 654 | > 655 | { 658 | if (!c) return; 659 | this.resizable = c; 660 | this.resizableElement.current = c.resizable; 661 | }} 662 | defaultSize={defaultValue} 663 | size={this.props.size} 664 | enable={typeof enableResizing === "boolean" ? getEnableResizingByFlag(enableResizing) : enableResizing} 665 | onResizeStart={this.onResizeStart} 666 | onResize={this.onResize} 667 | onResizeStop={this.onResizeStop} 668 | style={innerStyle} 669 | minWidth={this.props.minWidth} 670 | minHeight={this.props.minHeight} 671 | maxWidth={this.state.resizing ? this.state.maxWidth : this.props.maxWidth} 672 | maxHeight={this.state.resizing ? this.state.maxHeight : this.props.maxHeight} 673 | grid={resizeGrid} 674 | handleWrapperClass={resizeHandleWrapperClass} 675 | handleWrapperStyle={resizeHandleWrapperStyle} 676 | lockAspectRatio={this.props.lockAspectRatio} 677 | lockAspectRatioExtraWidth={this.props.lockAspectRatioExtraWidth} 678 | lockAspectRatioExtraHeight={this.props.lockAspectRatioExtraHeight} 679 | handleStyles={resizeHandleStyles} 680 | handleClasses={resizeHandleClasses} 681 | handleComponent={resizeHandleComponent} 682 | scale={this.props.scale} 683 | > 684 | {children} 685 | 686 | 687 | ); 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /stories/bare/bare.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => 001; 6 | -------------------------------------------------------------------------------- /stories/basic/controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | }; 11 | 12 | export default class Example extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 200, 17 | height: 200, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 | { 36 | this.setState({ x: d.x, y: d.y }); 37 | }} 38 | onResizeStop={(e, direction, ref, delta, position) => { 39 | this.setState({ 40 | width: ref.offsetWidth, 41 | height: ref.offsetHeight, 42 | ...position, 43 | }); 44 | }} 45 | > 46 | 001 47 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stories/basic/multi-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | type State = { 6 | rnds: { 7 | x: number; 8 | y: number; 9 | width: number; 10 | height: number; 11 | }[]; 12 | }; 13 | 14 | export default class Example extends React.Component<{}, State> { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | rnds: [0, 1, 2].map((i) => ({ 19 | width: 200, 20 | height: 200, 21 | x: i * 100, 22 | y: i * 100, 23 | })), 24 | }; 25 | } 26 | 27 | render() { 28 | return ( 29 | <> 30 | {[0, 1, 2].map((i) => ( 31 | { 43 | const rnds = [...this.state.rnds]; 44 | rnds[i] = { ...rnds[i], x: d.x, y: d.y }; 45 | this.setState({ rnds }); 46 | }} 47 | onResize={(e, direction, ref, delta, position) => { 48 | const rnds = [...this.state.rnds]; 49 | rnds[i] = { 50 | ...rnds[i], 51 | width: ref.offsetWidth, 52 | height: ref.offsetHeight, 53 | ...position, 54 | }; 55 | this.setState({ 56 | rnds, 57 | }); 58 | }} 59 | > 60 | 00{i} 61 | 62 | ))} 63 | 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /stories/basic/multi-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | <> 7 | {[0, 1, 2].map((i) => ( 8 | 21 | 00{i} 22 | 23 | ))} 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /stories/basic/uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 15 | 001 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /stories/bounds-and-offset.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import React from 'react'; 4 | import Rnd from '../src'; 5 | 6 | const style = { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | border: 'solid 1px #ddd', 11 | background: '#f0f0f0', 12 | }; 13 | 14 | const handleClick = () => { 15 | console.log('click work.') 16 | } 17 | 18 | const handleDragStart = (e, data) => { 19 | console.log(data.x, data.y) 20 | } 21 | 22 | const handleDrag = (e, data) => { 23 | console.log(data.x, data.y) 24 | } 25 | 26 | const handleDragStop = (e, data) => { 27 | console.log(data.x, data.y) 28 | } 29 | 30 | const handleResizeStart = (_, __, ele) => { 31 | console.log(ele.clientWidth, ele.clientHeight) 32 | } 33 | 34 | const handleResize = (_, __, ele, ___, pos) => { 35 | console.log(ele.clientWidth, ele.clientHeight, pos.x, pos.y) 36 | } 37 | 38 | const handleResizeStop = (_, __, ele, ___, pos) => { 39 | console.log(ele.clientWidth, ele.clientHeight, pos.x, pos.y) 40 | } 41 | 42 | export default () => ( 43 |
44 |
45 |
46 | 58 | 001 59 | 60 |
61 |
62 |
63 | ); 64 | -------------------------------------------------------------------------------- /stories/bounds/body-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | }; 11 | 12 | export default class Example extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 200, 17 | height: 200, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 | { 38 | this.setState({ x: d.x, y: d.y }); 39 | }} 40 | onResize={(e, direction, ref, delta, position) => { 41 | this.setState({ 42 | width: ref.offsetWidth, 43 | height: ref.offsetHeight, 44 | ...position, 45 | }); 46 | }} 47 | > 48 | 001 49 | 50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /stories/bounds/element-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | }; 11 | 12 | const Example: React.FC = () => { 13 | const [state, setState] = React.useState({ 14 | width: 200, 15 | height: 200, 16 | x: 0, 17 | y: 0, 18 | }); 19 | const [boundaryElm, setBoundaryElm] = React.useState(); 20 | return ( 21 |
setBoundaryElm(elm!)}> 22 | { 34 | setState({ ...state, x: d.x, y: d.y }); 35 | }} 36 | onResize={(e, direction, ref, delta, position) => { 37 | setState({ 38 | width: ref.offsetWidth, 39 | height: ref.offsetHeight, 40 | ...position, 41 | }); 42 | }} 43 | > 44 | 001 45 | 46 |
47 | ); 48 | }; 49 | 50 | export default Example; 51 | -------------------------------------------------------------------------------- /stories/bounds/element-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary } from "../styles"; 4 | 5 | export default () => { 6 | const [boundaryElm, setBoundaryElm] = React.useState(); 7 | return ( 8 |
setBoundaryElm(ref!)}> 9 | {boundaryElm && ( 10 | 20 | 001 21 | 22 | )} 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /stories/bounds/parent-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | }; 11 | 12 | export default class Example extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 200, 17 | height: 200, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 | { 38 | this.setState({ x: d.x, y: d.y }); 39 | }} 40 | onResize={(e, direction, ref, delta, position) => { 41 | this.setState({ 42 | width: ref.offsetWidth, 43 | height: ref.offsetHeight, 44 | ...position, 45 | }); 46 | }} 47 | > 48 | 001 49 | 50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /stories/bounds/parent-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary } from "../styles"; 4 | 5 | export default () => ( 6 |
7 | 17 | 001 18 | 19 |
20 | ); 21 | -------------------------------------------------------------------------------- /stories/bounds/selector-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary, selectorBoundary } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | }; 11 | 12 | export default class Example extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 200, 17 | height: 200, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |
27 | { 39 | this.setState({ x: d.x, y: d.y }); 40 | }} 41 | onResize={(e, direction, ref, delta, position) => { 42 | this.setState({ 43 | width: ref.offsetWidth, 44 | height: ref.offsetHeight, 45 | ...position, 46 | }); 47 | }} 48 | > 49 | 001 50 | 51 |
52 |
53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /stories/bounds/selector-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary, selectorBoundary } from "../styles"; 4 | 5 | export default () => ( 6 |
7 |
8 | 18 | 001 19 | 20 |
21 |
22 | ); 23 | -------------------------------------------------------------------------------- /stories/bounds/window-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | }; 11 | 12 | export default class Example extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 200, 17 | height: 200, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |
27 | { 41 | this.setState({ x: d.x, y: d.y }); 42 | }} 43 | onResize={(e, direction, ref, delta, position) => { 44 | this.setState({ 45 | width: ref.offsetWidth, 46 | height: ref.offsetHeight, 47 | ...position, 48 | }); 49 | }} 50 | > 51 | 001 52 | 53 |
54 |
55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /stories/callback/callbacks.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { action } from "@storybook/addon-actions"; 3 | import { Rnd } from "../../src"; 4 | import { style } from "../styles"; 5 | 6 | export default () => ( 7 | 25 | 001 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /stories/cancel/cancel.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 16 |
20 | 001 21 |
22 |
23 | ); 24 | -------------------------------------------------------------------------------- /stories/customization/resizeHandleComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | const SouthEastArrow = () => ( 6 | 7 | 8 | 9 | ); 10 | 11 | const CustomHandle = (props) => ( 12 |
24 | ); 25 | const BottomRightHandle = () => ( 26 | 27 | 28 | 29 | ); 30 | 31 | export default () => ( 32 | }} 41 | > 42 | 001 43 | 44 | ); 45 | -------------------------------------------------------------------------------- /stories/dragAxis/dragAxisNone.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number | string; 9 | height: number | string; 10 | }; 11 | 12 | export default class dragAxisX extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 100, 17 | height: 100, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 | { 30 | console.log("onDrag", d); 31 | }} 32 | onDragStop={(e, d) => { 33 | console.log(d); 34 | this.setState({ ...this.state, ...d }); 35 | }} 36 | onResizeStop={(e, direction, ref, delta, position) => { 37 | this.setState({ 38 | ...this.state, 39 | width: ref.style.width, 40 | height: ref.style.height, 41 | ...position, 42 | }); 43 | }} 44 | dragAxis="none" 45 | > 46 | 001 47 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stories/dragAxis/dragAxisX.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number | string; 9 | height: number | string; 10 | }; 11 | 12 | export default class dragAxisY extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 100, 17 | height: 100, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 | { 30 | console.log("onDrag", d); 31 | }} 32 | onDragStop={(e, d) => { 33 | console.log(d); 34 | this.setState({ ...this.state, ...d }); 35 | }} 36 | onResizeStop={(e, direction, ref, delta, position) => { 37 | this.setState({ 38 | ...this.state, 39 | width: ref.style.width, 40 | height: ref.style.height, 41 | ...position, 42 | }); 43 | }} 44 | dragAxis="x" 45 | > 46 | 001 47 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stories/dragAxis/dragAxisY.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number | string; 9 | height: number | string; 10 | }; 11 | 12 | export default class dragAxisX extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 100, 17 | height: 100, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 | { 30 | console.log("onDrag", d); 31 | }} 32 | onDragStop={(e, d) => { 33 | console.log(d); 34 | this.setState({ ...this.state, ...d }); 35 | }} 36 | onResizeStop={(e, direction, ref, delta, position) => { 37 | this.setState({ 38 | ...this.state, 39 | width: ref.style.width, 40 | height: ref.style.height, 41 | ...position, 42 | }); 43 | }} 44 | dragAxis="y" 45 | > 46 | 001 47 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stories/grid/both.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 17 | 001 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /stories/grid/drag.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 16 | 001 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /stories/grid/resize.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 16 | 001 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /stories/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react"; 3 | import "./styles.css"; 4 | 5 | import Bare from "./bare/bare"; 6 | 7 | import BasicUncontrolled from "./basic/uncontrolled"; 8 | import BasicControlled from "./basic/controlled"; 9 | 10 | import MinUncontrolled from "./min/uncontrolled"; 11 | 12 | import ScaleParentUnControlled from "./scale/parent-uncontrolled"; 13 | import ScaleWindowUnControlled from "./scale/window-uncontrolled"; 14 | import ScaleBodyX05UnControlled from "./scale/body-uncontrolled-x0-5"; 15 | import ScaleBodyX15UnControlled from "./scale/body-uncontrolled-x1-5"; 16 | import ScaleSelectorUnControlled from "./scale/selector-uncontrolled"; 17 | import ScaleSelectorControlled from "./scale/selector-controlled"; 18 | 19 | import BasicMultiUncontrolled from "./basic/multi-uncontrolled"; 20 | import BasicMultiControlled from "./basic/multi-controlled"; 21 | 22 | import BoundsParentUncontrolled from "./bounds/parent-uncontrolled"; 23 | import BoundsParentControlled from "./bounds/parent-controlled"; 24 | import BoundsSelectorUncontrolled from "./bounds/selector-uncontrolled"; 25 | import BoundsSelectorControlled from "./bounds/selector-controlled"; 26 | import BoundsWindowControlled from "./bounds/window-controlled"; 27 | import BoundsBodyControlled from "./bounds/body-controlled"; 28 | import BoundsElementControlled from "./bounds/element-controlled"; 29 | import BoundsElementUncontrolled from "./bounds/element-uncontrolled"; 30 | 31 | import SizePercentUncontrolled from "./size/size-percent-uncontrolled"; 32 | import SizePercentControlled from "./size/size-percent-controlled"; 33 | 34 | import Callbacks from "./callback/callbacks"; 35 | 36 | import Cancel from "./cancel/cancel"; 37 | 38 | import ResizeHandleComponent from "./customization/resizeHandleComponent"; 39 | 40 | import DragAxisX from "./dragAxis/dragAxisX"; 41 | import DragAxisY from "./dragAxis/dragAxisY"; 42 | import DragAxisNone from "./dragAxis/dragAxisNone"; 43 | 44 | import GridResize from "./grid/resize"; 45 | import GridDrag from "./grid/drag"; 46 | import GridBoth from "./grid/both"; 47 | 48 | import SandboxBodySizeToMaxWidth from "./sandbox/bodysize-to-maxwidth"; 49 | import SandboxLockAspectRatioWithBounds from "./sandbox/lock-aspect-ratio-with-bounds"; 50 | 51 | import LockAspectRatioBasic from "./lock-aspect-ratio/basic"; 52 | import Issue622 from "./sandbox/issue-#622"; 53 | 54 | storiesOf("bare", module).add("bare", () => ); 55 | 56 | storiesOf("basic", module) 57 | .add("uncontrolled", () => ) 58 | .add("controlled", () => ) 59 | .add("multi uncontrolled", () => ) 60 | .add("multi controlled", () => ); 61 | 62 | storiesOf("bounds", module) 63 | .add("parent uncontrolled", () => ) 64 | .add("parent controlled", () => ) 65 | .add("selector uncontrolled", () => ) 66 | .add("selector controlled", () => ) 67 | .add("window controlled", () => ) 68 | .add("body controlled", () => ) 69 | .add("element controlled", () => ) 70 | .add("element uncontrolled", () => ) 71 | 72 | storiesOf("scale", module) 73 | .add("with parent boundary", () => ) 74 | .add("x0.5 with body boundary", () => ) 75 | .add("x1.5 with body boundary", () => ) 76 | .add("with window boundary", () => ) 77 | .add("with selector boundary uncontrolled", () => ) 78 | .add("with selector boundary controlled", () => ) 79 | .add("with selector boundary", () => ); 80 | 81 | storiesOf("size", module) 82 | .add("percent uncontrolled", () => ) 83 | .add("percent controlled", () => ); 84 | 85 | storiesOf("callbacks", module).add("callback", () => ); 86 | 87 | storiesOf("cancel", module).add("cancel", () => ); 88 | 89 | storiesOf("customization", module).add("resizeHandleComponent", () => ); 90 | 91 | storiesOf("dragAxis", module) 92 | .add("dragAxisX", () => ) 93 | .add("dragAxisY", () => ) 94 | .add("dragAxisNone", () => ); 95 | 96 | storiesOf("grid", module) 97 | .add("resize", () => ) 98 | .add("drag", () => ) 99 | .add("both", () => ); 100 | 101 | storiesOf("sandbox", module) 102 | .add("body size apply to maxwidth", () => ) 103 | .add("lock aspect ratio with bounds", () => ) 104 | .add("issue622", () => ); 105 | 106 | storiesOf("ratio", module).add("lock aspect ratio", () => ); 107 | 108 | storiesOf("min", module).add("min uncontrolled", () => ); 109 | -------------------------------------------------------------------------------- /stories/lock-aspect-ratio/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 16 | 001 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /stories/max-size-with-percent.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import React from 'react'; 4 | import Rnd from '../src'; 5 | 6 | const style = { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | border: 'solid 1px #ddd', 11 | background: '#f0f0f0', 12 | }; 13 | 14 | export default () => ( 15 |
22 | 34 | 001 35 | 36 |
37 | ); 38 | -------------------------------------------------------------------------------- /stories/min/uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 17 | Rnd 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /stories/multiple.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import React from 'react'; 4 | import Rnd from '../src'; 5 | 6 | const style = { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | border: 'solid 1px #ddd', 11 | background: '#f0f0f0', 12 | }; 13 | 14 | export default () => ( 15 |
23 | {[...Array(3).keys()].map((_, i) => { 24 | return 34 | 00{i} 35 | 36 | })} 37 |
38 | ); 39 | -------------------------------------------------------------------------------- /stories/sandbox.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import React from 'react'; 4 | import Rnd from '../src'; 5 | 6 | const style = { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | border: 'solid 1px #ddd', 11 | background: '#f0f0f0', 12 | }; 13 | 14 | let i = 100; 15 | 16 | export default class SandBox extends React.Component { 17 | 18 | constructor(p) { 19 | super(p); 20 | this.state = { 21 | x: 100, 22 | } 23 | } 24 | 25 | componentDidMount() { 26 | setInterval(() => { 27 | i += 100; 28 | this.setState({ 29 | x: i, 30 | }) 31 | }, 1000); 32 | } 33 | 34 | render() { 35 | return 43 | 001 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /stories/sandbox/bodysize-to-maxwidth.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default class App extends React.Component { 6 | state = { 7 | maxWidth: Number.MAX_SAFE_INTEGER, 8 | }; 9 | constructor(props) { 10 | super(props); 11 | } 12 | componentDidMount() { 13 | window.addEventListener("resize", () => { 14 | this.setState({ maxWidth: document.body.clientWidth - 100 }); 15 | }); 16 | } 17 | 18 | render() { 19 | return ( 20 | 30 | Rnd 31 | 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /stories/sandbox/issue-#622.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | 4 | const style = { 5 | display: "flex", 6 | alignItems: "center", 7 | justifyContent: "center", 8 | borderLeft: "solid 5px #ddd", 9 | borderRight: "solid 5px #ddd", 10 | borderTop: "solid 1px #ddd", 11 | borderBottom: "solid 1px #ddd", 12 | background: "#f0f0f0", 13 | height: "100%", 14 | }; 15 | 16 | export default class App extends React.Component { 17 | state = { 18 | width: 200, 19 | height: 52, 20 | x: 100, 21 | y: 0, 22 | }; 23 | 24 | render() { 25 | return ( 26 |
34 | { 50 | this.setState({ x: d.x, y: d.y }); 51 | }} 52 | onResizeStop={(e, direction, ref, delta, position) => { 53 | this.setState({ 54 | width: ref.style.width, 55 | height: ref.style.height, 56 | ...position, 57 | }); 58 | }} 59 | > 60 | Rnd 61 | 62 |
63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /stories/sandbox/lock-aspect-ratio-with-bounds.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary } from "../styles"; 4 | 5 | export default () => ( 6 |
7 | 18 | 001 19 | 20 |
21 | ); 22 | -------------------------------------------------------------------------------- /stories/scale/body-uncontrolled-x0-5.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 |
7 |
8 | 19 | 001 20 | 21 |
22 |
23 | ); 24 | -------------------------------------------------------------------------------- /stories/scale/body-uncontrolled-x1-5.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 |
7 |
8 | 19 | 001 20 | 21 |
22 |
23 | ); 24 | -------------------------------------------------------------------------------- /stories/scale/parent-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary } from "../styles"; 4 | 5 | export default () => ( 6 |
7 |
8 | 19 | 001 20 | 21 |
22 |
23 | ); 24 | -------------------------------------------------------------------------------- /stories/scale/selector-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary, selectorBoundary } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | }; 11 | 12 | export default class Example extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: 200, 17 | height: 200, 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |
27 | { 40 | this.setState({ x: d.x, y: d.y }); 41 | }} 42 | onResize={(e, direction, ref, delta, position) => { 43 | this.setState({ 44 | width: ref.offsetWidth, 45 | height: ref.offsetHeight, 46 | ...position, 47 | }); 48 | }} 49 | > 50 | 001 51 | 52 |
53 |
54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /stories/scale/selector-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style, parentBoundary, selectorBoundary } from "../styles"; 4 | 5 | export default () => ( 6 |
7 |
8 | 19 | 001 20 | 21 |
22 |
23 | ); 24 | -------------------------------------------------------------------------------- /stories/scale/window-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 |
7 |
8 | 19 | 001 20 | 21 |
22 |
23 | ); 24 | -------------------------------------------------------------------------------- /stories/size-and-position.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import React from 'react'; 4 | import Rnd from '../src'; 5 | 6 | const style = { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | border: 'solid 1px #ddd', 11 | background: '#f0f0f0', 12 | }; 13 | 14 | export default class Example extends React.Component { 15 | 16 | constructor() { 17 | super(); 18 | this.state = { 19 | width: 100, 20 | height: 120, 21 | x: 0, 22 | y: 0, 23 | }; 24 | } 25 | 26 | render() { 27 | return ( 28 | { this.setState({ x: d.x, y: d.y }) }} 40 | onResize={(e, direction, ref, delta, position) => { 41 | this.setState({ 42 | width: ref.offsetWidth, 43 | height: ref.offsetHeight, 44 | ...position, 45 | }); 46 | }} 47 | > 48 | 001 49 | 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /stories/size-percentage.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import React from 'react'; 4 | import Rnd from '../src'; 5 | 6 | const style = { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | border: 'solid 1px #ddd', 11 | background: '#f0f0f0', 12 | }; 13 | 14 | export default () => ( 15 |
23 | 33 | 001 34 | 35 |
36 | ); 37 | -------------------------------------------------------------------------------- /stories/size/size-percent-controlled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | type State = { 6 | x: number; 7 | y: number; 8 | width: number | string; 9 | height: number | string; 10 | }; 11 | 12 | export default class Example extends React.Component<{}, State> { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | width: "30%", 17 | height: "30%", 18 | x: 0, 19 | y: 0, 20 | }; 21 | } 22 | 23 | render() { 24 | return ( 25 | { 36 | this.setState({ x: d.x, y: d.y }); 37 | }} 38 | onResize={(e, direction, ref, delta, position) => { 39 | this.setState({ 40 | width: ref.style.width, 41 | height: ref.style.height, 42 | ...position, 43 | }); 44 | }} 45 | > 46 | 001 47 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stories/size/size-percent-uncontrolled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Rnd } from "../../src"; 3 | import { style } from "../styles"; 4 | 5 | export default () => ( 6 | 15 | 001 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /stories/styles.css: -------------------------------------------------------------------------------- 1 | html, body, #root, #root > div { 2 | height: 100%; 3 | margin: 0; 4 | padding: 10px; 5 | box-sizing: border-box; 6 | } 7 | 8 | body { 9 | background: #fffefe; 10 | border: solid 1px #f0f0f0; 11 | } 12 | 13 | html { 14 | overflow: hidden; 15 | } 16 | 17 | #root > div { 18 | padding: 0px; 19 | } -------------------------------------------------------------------------------- /stories/styles.ts: -------------------------------------------------------------------------------- 1 | export const style = { 2 | display: "flex", 3 | alignItems: "center", 4 | justifyContent: "center", 5 | border: "solid 1px #ddd", 6 | background: "#f0f0f0", 7 | }; 8 | 9 | export const parentBoundary = { 10 | background: "#eee", 11 | width: "100%", 12 | height: "100%", 13 | }; 14 | 15 | export const selectorBoundary = { 16 | background: "#d1d8ff", 17 | padding: "20px", 18 | width: "100%", 19 | height: "100%", 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "ES5", 5 | // "module": "ES2015", 6 | "lib": ["es5", "es2015", "dom"], 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 10 | "declaration": true /* Generates corresponding '.d.ts' file. */, 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./lib", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./src/components", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true /* Enable all strict type-checking options. */, 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | }, 59 | "exclude": ["stories", "lib", "node_modules"], 60 | "include": [ 61 | "src" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": ["tslint-eslint-rules"], 4 | "linterOptions": { "exclude": ["src/index.test.tsx"] }, 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [true, "check-space"], 8 | "indent": [true, "spaces"], 9 | "import-blacklist": [true, "lodash", "date-fns", "d3"], 10 | "import-spacing": true, 11 | "object-curly-spacing": [true, "always"], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-debugger": true, 15 | "no-console": [true, "log"], 16 | "no-internal-module": true, 17 | "no-trailing-whitespace": [true, "ignore-comments"], 18 | "no-unsafe-finally": true, 19 | "no-var-keyword": true, 20 | "no-unused-variable": [true], 21 | "no-reference": true, 22 | "one-line": [true, "check-whitespace"], 23 | "quotemark": [true, "double"], 24 | "semicolon": [false, "always"], 25 | "triple-equals": [true, "allow-null-check"], 26 | "typedef-whitespace": [ 27 | true, 28 | { 29 | "call-signature": "nospace", 30 | "index-signature": "nospace", 31 | "parameter": "nospace", 32 | "property-declaration": "nospace", 33 | "variable-declaration": "nospace" 34 | } 35 | ], 36 | "variable-name": [true, "ban-keywords"], 37 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], 38 | "prefer-const": true 39 | } 40 | } 41 | --------------------------------------------------------------------------------