react-resizable 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,11 +11,12 @@ A simple widget that can be resized via one or more handles.
11
11
  You can either use the `<Resizable>` element directly, or use the much simpler `<ResizableBox>` element.
12
12
 
13
13
  See the example and associated code in [ExampleLayout](https://github.com/react-grid-layout/react-resizable/blob/master/examples/ExampleLayout.js) and
14
- [ResizableBox](https://github.com/react-grid-layout/react-resizable/blob/master/lib/ResizableBox.js) for more details.
14
+ [ResizableBox](https://github.com/react-grid-layout/react-resizable/blob/master/lib/ResizableBox.tsx) for more details.
15
15
 
16
16
  ## Table of Contents
17
17
 
18
18
  - [Installation](#installation)
19
+ - [TypeScript](#typescript)
19
20
  - [Compatibility](#compatibility)
20
21
  - [Usage](#usage)
21
22
  - [Resizable](#resizable)
@@ -47,20 +48,59 @@ Or import it in your CSS:
47
48
 
48
49
  If you're using a bundler that doesn't support CSS imports, you can find the styles at `node_modules/react-resizable/css/styles.css` and include them manually.
49
50
 
51
+ ## TypeScript
52
+
53
+ As of `4.0.0`, the library is authored in TypeScript and ships bundled type
54
+ declarations in `build/*.d.ts`. You **do not** need to install
55
+ `@types/react-resizable`; if you previously installed it, remove it so the
56
+ bundled types take precedence:
57
+
58
+ ```bash
59
+ npm uninstall @types/react-resizable
60
+ # or
61
+ yarn remove @types/react-resizable
62
+ ```
63
+
64
+ Public types are re-exported from the package root:
65
+
66
+ ```ts
67
+ import {
68
+ Resizable,
69
+ ResizableBox,
70
+ // types
71
+ type ResizeCallbackData,
72
+ type ResizeHandleAxis,
73
+ type Axis,
74
+ type Props as ResizableProps,
75
+ } from 'react-resizable';
76
+ ```
77
+
78
+ ### Flow
79
+
80
+ Flow is no longer supported as of `4.0.0`. Earlier versions shipped
81
+ `*.js.flow` sidecar files generated from the Flow-annotated source; those
82
+ have been removed.
83
+
84
+ If you still need Flow types, you can vendor the last Flow-annotated source
85
+ locally from the [`3.2.0` tag](https://github.com/react-grid-layout/react-resizable/tree/db2e37eda85fb21b1864e36b01c4922452f28418/lib).
86
+ They will not be updated to reflect changes landing after `4.0.0`. The
87
+ official recommendation is to migrate to TypeScript.
88
+
50
89
  ## Compatibility
51
90
 
52
- | Version | React Version |
53
- |---------|---------------|
54
- | [3.x](https://github.com/react-grid-layout/react-resizable/blob/master/CHANGELOG.md#300-may-10-2021) | `>= 16.3` |
55
- | 2.x | Skipped |
56
- | [1.x](https://github.com/react-grid-layout/react-resizable/blob/master/CHANGELOG.md#1111-mar-5-2021) | `14 - 17` |
91
+ | Version | React Version | Types |
92
+ |---------|---------------|----------------|
93
+ | [4.x](https://github.com/react-grid-layout/react-resizable/blob/master/CHANGELOG.md#400-may-12-2026) | `>= 16.3` | TypeScript (bundled) |
94
+ | [3.x](https://github.com/react-grid-layout/react-resizable/blob/master/CHANGELOG.md#300-may-10-2021) | `>= 16.3` | Flow (`*.js.flow`) |
95
+ | 2.x | Skipped | |
96
+ | [1.x](https://github.com/react-grid-layout/react-resizable/blob/master/CHANGELOG.md#1111-mar-5-2021) | `14 - 17` | Flow |
57
97
 
58
98
  ## Usage
59
99
 
60
100
  This package has two major exports:
61
101
 
62
- * [`<Resizable>`](https://github.com/react-grid-layout/react-resizable/blob/master/lib/Resizable.js): A raw component that does not have state. Use as a building block for larger components, by listening to its callbacks and setting its props.
63
- * [`<ResizableBox>`](https://github.com/react-grid-layout/react-resizable/blob/master/lib/ResizableBox.js): A simple `<div {...props} />` element that manages basic state. Convenient for simple use-cases.
102
+ * [`<Resizable>`](https://github.com/react-grid-layout/react-resizable/blob/master/lib/Resizable.tsx): A raw component that does not have state. Use as a building block for larger components, by listening to its callbacks and setting its props.
103
+ * [`<ResizableBox>`](https://github.com/react-grid-layout/react-resizable/blob/master/lib/ResizableBox.tsx): A simple `<div {...props} />` element that manages basic state. Convenient for simple use-cases.
64
104
 
65
105
  ### `<Resizable>`
66
106
 
@@ -124,43 +164,46 @@ class Example extends React.Component {
124
164
 
125
165
  These props apply to both `<Resizable>` and `<ResizableBox>`. Unknown props that are not in the list below will be passed to the child component.
126
166
 
127
- ```js
167
+ ```ts
128
168
  type ResizeCallbackData = {
129
- node: HTMLElement,
130
- size: {width: number, height: number},
131
- handle: ResizeHandleAxis
169
+ node: HTMLElement;
170
+ size: {width: number; height: number};
171
+ handle: ResizeHandleAxis;
132
172
  };
133
173
 
134
174
  type ResizeHandleAxis = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne';
135
175
 
136
176
  type ResizableProps = {
137
- children: React.Element<any>,
138
- width: number,
139
- height: number,
177
+ children: React.ReactElement<any>;
178
+ width: number;
179
+ height: number;
140
180
  // Either a ReactElement to be used as handle, or a function
141
181
  // returning an element that is fed the handle's location as its first argument.
142
- handle: ReactElement<any> | (resizeHandle: ResizeHandleAxis, ref: ReactRef<HTMLElement>) => ReactElement<any>,
143
- // If you change this, be sure to update your css
144
- handleSize: [number, number] = [10, 10],
145
- lockAspectRatio: boolean = false,
146
- axis: 'both' | 'x' | 'y' | 'none' = 'both',
147
- minConstraints: [number, number] = [10, 10],
148
- maxConstraints: [number, number] = [Infinity, Infinity],
149
- onResizeStop?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any,
150
- onResizeStart?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any,
151
- onResize?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any,
152
- draggableOpts?: ?Object,
153
- resizeHandles?: ?Array<ResizeHandleAxis> = ['se'],
182
+ handle?:
183
+ | React.ReactElement<any>
184
+ | ((resizeHandle: ResizeHandleAxis, ref: React.RefObject<HTMLElement>) => React.ReactElement<any>);
185
+ // If you change this, be sure to update your css. Default: [20, 20].
186
+ handleSize?: [number, number];
187
+ lockAspectRatio?: boolean; // default: false
188
+ axis?: 'both' | 'x' | 'y' | 'none'; // default: 'both'
189
+ minConstraints?: [number, number]; // default: [20, 20]
190
+ maxConstraints?: [number, number]; // default: [Infinity, Infinity]
191
+ onResizeStop?: (e: React.SyntheticEvent, data: ResizeCallbackData) => any;
192
+ onResizeStart?: (e: React.SyntheticEvent, data: ResizeCallbackData) => any;
193
+ onResize?: (e: React.SyntheticEvent, data: ResizeCallbackData) => any;
194
+ // Forwarded to react-draggable's <DraggableCore>.
195
+ draggableOpts?: Partial<React.ComponentProps<typeof import('react-draggable').DraggableCore>>;
196
+ resizeHandles?: ResizeHandleAxis[]; // default: ['se']
154
197
  // If `transform: scale(n)` is set on the parent, this should be set to `n`.
155
- transformScale?: number = 1
198
+ transformScale?: number; // default: 1
156
199
  };
157
200
  ```
158
201
 
159
202
  The following props can also be used on `<ResizableBox>`:
160
203
 
161
- ```js
204
+ ```ts
162
205
  {
163
- style?: Object // styles the returned <div />
206
+ style?: React.CSSProperties; // styles the returned <div />
164
207
  }
165
208
  ```
166
209
 
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import type { ResizeHandleAxis, DefaultProps, Props, DragCallbackData } from './propTypes';
3
+ export default class Resizable extends React.Component<Props, {}> {
4
+ static propTypes: {
5
+ [key: string]: any;
6
+ };
7
+ static defaultProps: DefaultProps;
8
+ handleRefs: {
9
+ [key in ResizeHandleAxis]?: React.RefObject<HTMLElement>;
10
+ };
11
+ lastHandleRect: DOMRect | null;
12
+ slack: [number, number] | null;
13
+ lastSize: {
14
+ width: number;
15
+ height: number;
16
+ } | null;
17
+ componentWillUnmount(): void;
18
+ resetData(): void;
19
+ runConstraints(width: number, height: number): [number, number];
20
+ /**
21
+ * Wrapper around drag events to provide more useful data.
22
+ *
23
+ * @param {String} handlerName Handler name to wrap.
24
+ * @return {Function} Handler function.
25
+ */
26
+ resizeHandler(handlerName: 'onResize' | 'onResizeStart' | 'onResizeStop', axis: ResizeHandleAxis): (e: React.SyntheticEvent, data: DragCallbackData) => void;
27
+ renderResizeHandle(handleAxis: ResizeHandleAxis, ref: React.RefObject<HTMLElement>): React.ReactNode;
28
+ render(): React.ReactNode;
29
+ }
@@ -63,7 +63,7 @@ class Resizable extends React.Component {
63
63
  // Add slack to the values used to calculate bound position. This will ensure that if
64
64
  // we start removing slack, the element won't react to it right away until it's been
65
65
  // completely removed.
66
- let _ref = this.slack || [0, 0],
66
+ const _ref = this.slack || [0, 0],
67
67
  slackW = _ref[0],
68
68
  slackH = _ref[1];
69
69
  width += slackW;
@@ -213,7 +213,6 @@ class Resizable extends React.Component {
213
213
  }
214
214
  render() {
215
215
  // Pass along only props not meant for the `<Resizable>`.`
216
- // eslint-disable-next-line no-unused-vars
217
216
  const _this$props2 = this.props,
218
217
  children = _this$props2.children,
219
218
  className = _this$props2.className,
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import type { Props as ResizableProps, ResizeCallbackData, ResizableBoxState } from './propTypes';
4
+ type ResizableBoxProps = Omit<ResizableProps, 'children'> & {
5
+ style?: React.CSSProperties;
6
+ children?: React.ReactElement<any>;
7
+ className?: string | null;
8
+ };
9
+ export default class ResizableBox extends React.Component<ResizableBoxProps, ResizableBoxState> {
10
+ static propTypes: {
11
+ children: PropTypes.Requireable<PropTypes.ReactElementLike>;
12
+ };
13
+ state: ResizableBoxState;
14
+ static getDerivedStateFromProps(props: ResizableBoxProps, state: ResizableBoxState): ResizableBoxState | null;
15
+ onResize: (e: React.SyntheticEvent, data: ResizeCallbackData) => void;
16
+ render(): React.ReactNode;
17
+ }
18
+ export {};
@@ -16,10 +16,8 @@ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object
16
16
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
17
17
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
18
18
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
19
- // ElementConfig gives us an object type where all items present in `defaultProps` are made optional.
20
- // <ResizableBox> does not have defaultProps, so we can use this type to tell Flow that we don't
21
- // care about that and will handle it in <Resizable> instead.
22
- // A <ResizableBox> can also have a `style` property.
19
+ // <ResizableBox> does not have defaultProps, so we make all of <Resizable>'s defaults optional here
20
+ // and add an optional `style` property.
23
21
 
24
22
  class ResizableBox extends React.Component {
25
23
  constructor() {
@@ -0,0 +1,3 @@
1
+ export { default as Resizable } from './Resizable';
2
+ export { default as ResizableBox } from './ResizableBox';
3
+ export type { Axis, DefaultProps, DragCallbackData, Props, ReactRef, ResizableBoxState, ResizableState, ResizeCallbackData, ResizeHandleAxis, } from './propTypes';
package/build/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.ResizableBox = exports.Resizable = void 0;
5
+ var _Resizable = _interopRequireDefault(require("./Resizable"));
6
+ exports.Resizable = _Resizable.default;
7
+ var _ResizableBox = _interopRequireDefault(require("./ResizableBox"));
8
+ exports.ResizableBox = _ResizableBox.default;
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -0,0 +1,55 @@
1
+ import type * as React from 'react';
2
+ import type { DraggableCore } from 'react-draggable';
3
+ export type ReactRef<T extends HTMLElement> = {
4
+ current: T | null;
5
+ };
6
+ export type Axis = 'both' | 'x' | 'y' | 'none';
7
+ export type ResizeHandleAxis = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne';
8
+ export type ResizableState = {};
9
+ export type ResizableBoxState = {
10
+ width: number;
11
+ height: number;
12
+ propsWidth: number;
13
+ propsHeight: number;
14
+ };
15
+ export type DragCallbackData = {
16
+ node: HTMLElement;
17
+ x: number;
18
+ y: number;
19
+ deltaX: number;
20
+ deltaY: number;
21
+ lastX: number;
22
+ lastY: number;
23
+ };
24
+ export type ResizeCallbackData = {
25
+ node: HTMLElement;
26
+ size: {
27
+ width: number;
28
+ height: number;
29
+ };
30
+ handle: ResizeHandleAxis;
31
+ };
32
+ export type DefaultProps = {
33
+ axis: Axis;
34
+ handleSize: [number, number];
35
+ lockAspectRatio: boolean;
36
+ minConstraints: [number, number];
37
+ maxConstraints: [number, number];
38
+ resizeHandles: ResizeHandleAxis[];
39
+ transformScale: number;
40
+ };
41
+ export type ResizeHandleFn = (resizeHandleAxis: ResizeHandleAxis, ref: React.RefObject<HTMLElement>) => React.ReactElement<any>;
42
+ export type Props = DefaultProps & {
43
+ children: React.ReactElement<any>;
44
+ className?: string | null;
45
+ draggableOpts?: Partial<React.ComponentProps<typeof DraggableCore>> | null;
46
+ height: number;
47
+ handle?: React.ReactElement<any> | ResizeHandleFn;
48
+ onResizeStop?: ((e: React.SyntheticEvent, data: ResizeCallbackData) => any) | null;
49
+ onResizeStart?: ((e: React.SyntheticEvent, data: ResizeCallbackData) => any) | null;
50
+ onResize?: ((e: React.SyntheticEvent, data: ResizeCallbackData) => any) | null;
51
+ width: number;
52
+ };
53
+ export declare const resizableProps: {
54
+ [key: string]: any;
55
+ };
@@ -3,10 +3,9 @@
3
3
  exports.__esModule = true;
4
4
  exports.resizableProps = void 0;
5
5
  var _propTypes = _interopRequireDefault(require("prop-types"));
6
- var _reactDraggable = require("react-draggable");
7
6
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
- /* global Element */
9
7
  // <Resizable>
8
+
10
9
  const resizableProps = exports.resizableProps = {
11
10
  /*
12
11
  * Restricts resizing to a particular axis (default: 'both')
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare function cloneElement(element: React.ReactElement<any>, props: Record<string, any>): React.ReactElement<any>;
package/package.json CHANGED
@@ -1,25 +1,26 @@
1
1
  {
2
2
  "name": "react-resizable",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "A component that is resizable with handles.",
5
5
  "main": "index.js",
6
+ "types": "./build/index.d.ts",
6
7
  "files": [
7
8
  "build/",
8
9
  "css/",
9
10
  "index.js"
10
11
  ],
11
12
  "scripts": {
12
- "lint": "eslint lib/ __tests__/ setupTests/; flow",
13
+ "lint": "eslint lib/ __tests__/ setupTests/",
14
+ "typecheck": "tsc --noEmit",
13
15
  "test": "jest --coverage",
14
16
  "unit": "jest --watch --verbose",
15
17
  "build": "bash build.sh",
16
18
  "build-example": "webpack",
17
19
  "dev": "webpack serve --open",
18
20
  "prepublishOnly": "npm run build",
19
- "preversion": "npm run lint",
21
+ "preversion": "npm run lint && npm run typecheck",
20
22
  "version": "git add CHANGELOG.md",
21
- "postversion": "git push && git push --tags",
22
- "flow": "flow"
23
+ "postversion": "git push && git push --tags"
23
24
  },
24
25
  "repository": {
25
26
  "type": "git",
@@ -39,24 +40,28 @@
39
40
  "devDependencies": {
40
41
  "@babel/cli": "^7.28.6",
41
42
  "@babel/core": "^7.29.0",
42
- "@babel/eslint-parser": "^7.28.6",
43
43
  "@babel/plugin-proposal-class-properties": "^7.18.6",
44
44
  "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
45
45
  "@babel/preset-env": "^7.29.5",
46
- "@babel/preset-flow": "^7.27.1",
47
46
  "@babel/preset-react": "^7.28.5",
47
+ "@babel/preset-typescript": "^7.28.5",
48
48
  "@eslint/js": "^9.39.4",
49
49
  "@testing-library/dom": "^10.4.1",
50
50
  "@testing-library/jest-dom": "^6.1.0",
51
51
  "@testing-library/react": "^16.3.2",
52
52
  "@testing-library/user-event": "^14.5.0",
53
+ "@types/jest": "^30.0.0",
54
+ "@types/prop-types": "^15.7.0",
55
+ "@types/react": "^19.2.0",
56
+ "@types/react-dom": "^19.2.0",
57
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
58
+ "@typescript-eslint/parser": "^8.0.0",
53
59
  "babel-loader": "^10.1.1",
54
60
  "cross-env": "^10.1.0",
55
61
  "css-loader": "^7.1.4",
56
62
  "eslint": "^9.39.4",
57
63
  "eslint-plugin-jest": "^29.15.2",
58
64
  "eslint-plugin-react": "^7.37.5",
59
- "flow-bin": "^0.153.0",
60
65
  "jest": "^30.4.2",
61
66
  "jest-environment-jsdom": "^30.4.1",
62
67
  "lodash": "^4.18.1",
@@ -64,6 +69,7 @@
64
69
  "react": "^19.2.6",
65
70
  "react-dom": "^19.2.6",
66
71
  "style-loader": "^4.0.0",
72
+ "typescript": "^5.6.0",
67
73
  "webpack": "^5.106.2",
68
74
  "webpack-cli": "^7.0.2",
69
75
  "webpack-dev-server": "^5.2.4"
@@ -1,231 +0,0 @@
1
- // @flow
2
- import * as React from 'react';
3
- import type {Node as ReactNode} from 'react';
4
- import {DraggableCore} from 'react-draggable';
5
- import {cloneElement} from './utils';
6
- import {resizableProps} from "./propTypes";
7
- import type {ResizeHandleAxis, DefaultProps, Props, ReactRef, DragCallbackData} from './propTypes';
8
-
9
- // The base <Resizable> component.
10
- // This component does not have state and relies on the parent to set its props based on callback data.
11
- export default class Resizable extends React.Component<Props, void> {
12
- static propTypes = resizableProps;
13
-
14
- static defaultProps: DefaultProps = {
15
- axis: 'both',
16
- handleSize: [20, 20],
17
- lockAspectRatio: false,
18
- minConstraints: [20, 20],
19
- maxConstraints: [Infinity, Infinity],
20
- resizeHandles: ['se'],
21
- transformScale: 1
22
- };
23
-
24
- handleRefs: {[key: ResizeHandleAxis]: ReactRef<HTMLElement>} = {};
25
- lastHandleRect: ?ClientRect = null;
26
- slack: ?[number, number] = null;
27
- lastSize: ?{width: number, height: number} = null;
28
-
29
- componentWillUnmount() {
30
- this.resetData();
31
- }
32
-
33
- resetData() {
34
- this.lastHandleRect = this.slack = this.lastSize = null;
35
- }
36
-
37
- // Clamp width and height within provided constraints
38
- runConstraints(width: number, height: number): [number, number] {
39
- const {minConstraints, maxConstraints, lockAspectRatio} = this.props;
40
- // short circuit
41
- if (!minConstraints && !maxConstraints && !lockAspectRatio) return [width, height];
42
-
43
- // If constraining to min and max, we need to also fit width and height to aspect ratio.
44
- if (lockAspectRatio) {
45
- const ratio = this.props.width / this.props.height;
46
- const deltaW = width - this.props.width;
47
- const deltaH = height - this.props.height;
48
-
49
- // Find which coordinate was greater and should push the other toward it.
50
- // E.g.:
51
- // ratio = 1, deltaW = 10, deltaH = 5, deltaH should become 10.
52
- // ratio = 2, deltaW = 10, deltaH = 6, deltaW should become 12.
53
- if (Math.abs(deltaW) > Math.abs(deltaH * ratio)) {
54
- height = width / ratio;
55
- } else {
56
- width = height * ratio;
57
- }
58
- }
59
-
60
- const [oldW, oldH] = [width, height];
61
-
62
- // Add slack to the values used to calculate bound position. This will ensure that if
63
- // we start removing slack, the element won't react to it right away until it's been
64
- // completely removed.
65
- let [slackW, slackH] = this.slack || [0, 0];
66
- width += slackW;
67
- height += slackH;
68
-
69
- if (minConstraints) {
70
- width = Math.max(minConstraints[0], width);
71
- height = Math.max(minConstraints[1], height);
72
- }
73
- if (maxConstraints) {
74
- width = Math.min(maxConstraints[0], width);
75
- height = Math.min(maxConstraints[1], height);
76
- }
77
-
78
- // If the width or height changed, we must have introduced some slack. Record it for the next iteration.
79
- this.slack = [slackW + (oldW - width), slackH + (oldH - height)];
80
-
81
- return [width, height];
82
- }
83
-
84
- /**
85
- * Wrapper around drag events to provide more useful data.
86
- *
87
- * @param {String} handlerName Handler name to wrap.
88
- * @return {Function} Handler function.
89
- */
90
- resizeHandler(handlerName: 'onResize' | 'onResizeStart' | 'onResizeStop', axis: ResizeHandleAxis): Function {
91
- return (e: SyntheticEvent<>, {node, deltaX, deltaY}: DragCallbackData) => {
92
- // Reset data in case it was left over somehow (should not be possible)
93
- if (handlerName === 'onResizeStart') this.resetData();
94
-
95
- // Axis restrictions
96
- const canDragX = (this.props.axis === 'both' || this.props.axis === 'x') && axis !== 'n' && axis !== 's';
97
- const canDragY = (this.props.axis === 'both' || this.props.axis === 'y') && axis !== 'e' && axis !== 'w';
98
- // No dragging possible.
99
- if (!canDragX && !canDragY) return;
100
-
101
- // Decompose axis for later use
102
- const axisV = axis[0];
103
- const axisH = axis[axis.length - 1]; // intentionally not axis[1], so that this catches axis === 'w' for example
104
-
105
- // Track the element being dragged to account for changes in position.
106
- // If a handle's position is changed between callbacks, we need to factor this in to the next callback.
107
- // Failure to do so will cause the element to "skip" when resized upwards or leftwards.
108
- const handleRect = node.getBoundingClientRect();
109
- if (this.lastHandleRect != null) {
110
- // If the handle has repositioned on either axis since last render,
111
- // we need to increase our callback values by this much.
112
- // Only checking 'n', 'w' since resizing by 's', 'w' won't affect the overall position on page,
113
- if (axisH === 'w') {
114
- const deltaLeftSinceLast = handleRect.left - this.lastHandleRect.left;
115
- deltaX += deltaLeftSinceLast;
116
- }
117
- if (axisV === 'n') {
118
- const deltaTopSinceLast = handleRect.top - this.lastHandleRect.top;
119
- deltaY += deltaTopSinceLast;
120
- }
121
- }
122
- // Storage of last rect so we know how much it has really moved.
123
- this.lastHandleRect = handleRect;
124
-
125
- // Reverse delta if using top or left drag handles.
126
- if (axisH === 'w') deltaX = -deltaX;
127
- if (axisV === 'n') deltaY = -deltaY;
128
-
129
- // Update w/h by the deltas. Also factor in transformScale.
130
- // Use lastSize (if available) instead of props to avoid losing deltas
131
- // when React can't re-render between consecutive mouse events.
132
- const baseWidth = this.lastSize?.width ?? this.props.width;
133
- const baseHeight = this.lastSize?.height ?? this.props.height;
134
- let width = baseWidth + (canDragX ? deltaX / this.props.transformScale : 0);
135
- let height = baseHeight + (canDragY ? deltaY / this.props.transformScale : 0);
136
-
137
- // Run user-provided constraints.
138
- [width, height] = this.runConstraints(width, height);
139
-
140
- // For onResizeStop, use the last size from onResize rather than recalculating.
141
- // This avoids issues where props.width/height are stale due to React's batched updates.
142
- if (handlerName === 'onResizeStop' && this.lastSize) {
143
- ({width, height} = this.lastSize);
144
- }
145
-
146
- // Compare against the base (lastSize-or-props) so that callbacks correctly
147
- // suppress when the net delta is zero, even if props are stale relative to
148
- // the accumulated lastSize.
149
- const dimensionsChanged = width !== baseWidth || height !== baseHeight;
150
-
151
- // Store the size for use in onResizeStop. We do this after the onResizeStop check
152
- // above so we don't overwrite the stored value with a potentially stale calculation.
153
- if (handlerName !== 'onResizeStop') {
154
- this.lastSize = {width, height};
155
- }
156
-
157
- // Call user-supplied callback if present.
158
- const cb = typeof this.props[handlerName] === 'function' ? this.props[handlerName] : null;
159
- // Don't call 'onResize' if dimensions haven't changed.
160
- const shouldSkipCb = handlerName === 'onResize' && !dimensionsChanged;
161
- if (cb && !shouldSkipCb) {
162
- e.persist?.();
163
- cb(e, {node, size: {width, height}, handle: axis});
164
- }
165
-
166
- // Reset internal data
167
- if (handlerName === 'onResizeStop') this.resetData();
168
- };
169
- }
170
-
171
- // Render a resize handle given an axis & DOM ref. Ref *must* be attached for
172
- // the underlying draggable library to work properly.
173
- renderResizeHandle(handleAxis: ResizeHandleAxis, ref: ReactRef<HTMLElement>): ReactNode {
174
- const {handle} = this.props;
175
- // No handle provided, make the default
176
- if (!handle) {
177
- return <span className={`react-resizable-handle react-resizable-handle-${handleAxis}`} ref={ref} />;
178
- }
179
- // Handle is a function, such as:
180
- // `handle={(handleAxis) => <span className={...} />}`
181
- if (typeof handle === 'function') {
182
- return handle(handleAxis, ref);
183
- }
184
- // Handle is a React component (composite or DOM).
185
- const isDOMElement = typeof handle.type === 'string';
186
- const props = {
187
- ref,
188
- // Add `handleAxis` prop iff this is not a DOM element,
189
- // otherwise we'll get an unknown property warning
190
- ...(isDOMElement ? {} : {handleAxis})
191
- };
192
- return React.cloneElement(handle, props);
193
-
194
- }
195
-
196
- render(): ReactNode {
197
- // Pass along only props not meant for the `<Resizable>`.`
198
- // eslint-disable-next-line no-unused-vars
199
- const {children, className, draggableOpts, width, height, handle, handleSize,
200
- lockAspectRatio, axis, minConstraints, maxConstraints, onResize,
201
- onResizeStop, onResizeStart, resizeHandles, transformScale, ...p} = this.props;
202
-
203
- // What we're doing here is getting the child of this element, and cloning it with this element's props.
204
- // We are then defining its children as:
205
- // 1. Its original children (resizable's child's children), and
206
- // 2. One or more draggable handles.
207
- return cloneElement(children, {
208
- ...p,
209
- className: `${className ? `${className} ` : ''}react-resizable`,
210
- children: [
211
- ...React.Children.toArray(children.props.children),
212
- ...resizeHandles.map((handleAxis) => {
213
- // Create a ref to the handle so that `<DraggableCore>` doesn't have to use ReactDOM.findDOMNode().
214
- const ref = (this.handleRefs[handleAxis]) ?? (this.handleRefs[handleAxis] = React.createRef());
215
- return (
216
- <DraggableCore
217
- {...draggableOpts}
218
- nodeRef={ref}
219
- key={`resizableHandle-${handleAxis}`}
220
- onStop={this.resizeHandler('onResizeStop', handleAxis)}
221
- onStart={this.resizeHandler('onResizeStart', handleAxis)}
222
- onDrag={this.resizeHandler('onResize', handleAxis)}
223
- >
224
- {this.renderResizeHandle(handleAxis, ref)}
225
- </DraggableCore>
226
- );
227
- })
228
- ]
229
- });
230
- }
231
- }
@@ -1,98 +0,0 @@
1
- // @flow
2
- import * as React from 'react';
3
- import type {Node as ReactNode, Element as ReactElement} from 'react';
4
- import PropTypes from 'prop-types';
5
-
6
- import Resizable from './Resizable';
7
- import {resizableProps} from "./propTypes";
8
- import type {ResizeCallbackData, ResizableBoxState} from './propTypes';
9
-
10
- // ElementConfig gives us an object type where all items present in `defaultProps` are made optional.
11
- // <ResizableBox> does not have defaultProps, so we can use this type to tell Flow that we don't
12
- // care about that and will handle it in <Resizable> instead.
13
- // A <ResizableBox> can also have a `style` property.
14
- type ResizableBoxProps = {|...React.ElementConfig<typeof Resizable>, style?: Object, children?: ReactElement<any>|};
15
-
16
- export default class ResizableBox extends React.Component<ResizableBoxProps, ResizableBoxState> {
17
-
18
- // PropTypes are identical to <Resizable>, except that children are not strictly required to be present.
19
- static propTypes = {
20
- ...resizableProps,
21
- children: PropTypes.element,
22
- };
23
-
24
- state: ResizableBoxState = {
25
- width: this.props.width,
26
- height: this.props.height,
27
- propsWidth: this.props.width,
28
- propsHeight: this.props.height,
29
- };
30
-
31
- static getDerivedStateFromProps(props: ResizableBoxProps, state: ResizableBoxState): ?ResizableBoxState {
32
- // If parent changes height/width, set that in our state.
33
- if (state.propsWidth !== props.width || state.propsHeight !== props.height) {
34
- return {
35
- width: props.width,
36
- height: props.height,
37
- propsWidth: props.width,
38
- propsHeight: props.height,
39
- };
40
- }
41
- return null;
42
- }
43
-
44
- onResize: (e: SyntheticEvent<>, data: ResizeCallbackData) => void = (e, data) => {
45
- const {size} = data;
46
- if (this.props.onResize) {
47
- e.persist?.();
48
- this.setState(size, () => this.props.onResize && this.props.onResize(e, data));
49
- } else {
50
- this.setState(size);
51
- }
52
- };
53
-
54
- render(): ReactNode {
55
- // Basic wrapper around a Resizable instance.
56
- // If you use Resizable directly, you are responsible for updating the child component
57
- // with a new width and height.
58
- const {
59
- handle,
60
- handleSize,
61
- onResize,
62
- onResizeStart,
63
- onResizeStop,
64
- draggableOpts,
65
- minConstraints,
66
- maxConstraints,
67
- lockAspectRatio,
68
- axis,
69
- width,
70
- height,
71
- resizeHandles,
72
- style,
73
- transformScale,
74
- ...props
75
- } = this.props;
76
-
77
- return (
78
- <Resizable
79
- axis={axis}
80
- draggableOpts={draggableOpts}
81
- handle={handle}
82
- handleSize={handleSize}
83
- height={this.state.height}
84
- lockAspectRatio={lockAspectRatio}
85
- maxConstraints={maxConstraints}
86
- minConstraints={minConstraints}
87
- onResizeStart={onResizeStart}
88
- onResize={this.onResize}
89
- onResizeStop={onResizeStop}
90
- resizeHandles={resizeHandles}
91
- transformScale={transformScale}
92
- width={this.state.width}
93
- >
94
- <div {...props} style={{...style, width: this.state.width + 'px', height: this.state.height + 'px'}} />
95
- </Resizable>
96
- );
97
- }
98
- }
@@ -1,161 +0,0 @@
1
- // @flow
2
- /* global Element */
3
- import PropTypes from 'prop-types';
4
- import {DraggableCore} from "react-draggable";
5
- import type {Element as ReactElement, ElementConfig} from 'react';
6
-
7
- export type ReactRef<T: HTMLElement> = {
8
- current: T | null
9
- };
10
-
11
- export type Axis = 'both' | 'x' | 'y' | 'none';
12
- export type ResizeHandleAxis = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne';
13
- export type ResizableState = void;
14
- export type ResizableBoxState = {
15
- width: number, height: number,
16
- propsWidth: number, propsHeight: number
17
- };
18
- export type DragCallbackData = {
19
- node: HTMLElement,
20
- x: number, y: number,
21
- deltaX: number, deltaY: number,
22
- lastX: number, lastY: number
23
- };
24
- export type ResizeCallbackData = {
25
- node: HTMLElement,
26
- size: {width: number, height: number},
27
- handle: ResizeHandleAxis
28
- };
29
-
30
- // <Resizable>
31
- export type DefaultProps = {
32
- axis: Axis,
33
- handleSize: [number, number],
34
- lockAspectRatio: boolean,
35
- minConstraints: [number, number],
36
- maxConstraints: [number, number],
37
- resizeHandles: ResizeHandleAxis[],
38
- transformScale: number,
39
- };
40
-
41
- export type Props = {
42
- ...DefaultProps,
43
- children: ReactElement<any>,
44
- className?: ?string,
45
- draggableOpts?: ?ElementConfig<typeof DraggableCore>,
46
- height: number,
47
- handle?: ReactElement<any> | (resizeHandleAxis: ResizeHandleAxis, ref: ReactRef<HTMLElement>) => ReactElement<any>,
48
- onResizeStop?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any,
49
- onResizeStart?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any,
50
- onResize?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any,
51
- width: number,
52
- };
53
-
54
-
55
-
56
- export const resizableProps: Object = {
57
- /*
58
- * Restricts resizing to a particular axis (default: 'both')
59
- * 'both' - allows resizing by width or height
60
- * 'x' - only allows the width to be changed
61
- * 'y' - only allows the height to be changed
62
- * 'none' - disables resizing altogether
63
- * */
64
- axis: PropTypes.oneOf(['both', 'x', 'y', 'none']),
65
- className: PropTypes.string,
66
- /*
67
- * Require that one and only one child be present.
68
- * */
69
- children: PropTypes.element.isRequired,
70
- /*
71
- * These will be passed wholesale to react-draggable's DraggableCore
72
- * */
73
- draggableOpts: PropTypes.shape({
74
- allowAnyClick: PropTypes.bool,
75
- cancel: PropTypes.string,
76
- children: PropTypes.node,
77
- disabled: PropTypes.bool,
78
- enableUserSelectHack: PropTypes.bool,
79
- // #251: Check for Element to support SSR environments where DOM globals don't exist
80
- offsetParent: typeof Element !== 'undefined' ? PropTypes.instanceOf(Element) : PropTypes.any,
81
- grid: PropTypes.arrayOf(PropTypes.number),
82
- handle: PropTypes.string,
83
- nodeRef: PropTypes.object,
84
- onStart: PropTypes.func,
85
- onDrag: PropTypes.func,
86
- onStop: PropTypes.func,
87
- onMouseDown: PropTypes.func,
88
- scale: PropTypes.number,
89
- }),
90
- /*
91
- * Initial height
92
- * */
93
- height: (...args) => {
94
- const [props] = args;
95
- // Required if resizing height or both
96
- if (props.axis === 'both' || props.axis === 'y') {
97
- return PropTypes.number.isRequired(...args);
98
- }
99
- return PropTypes.number(...args);
100
- },
101
- /*
102
- * Customize cursor resize handle
103
- * */
104
- handle: PropTypes.oneOfType([
105
- PropTypes.node,
106
- PropTypes.func
107
- ]),
108
- /*
109
- * If you change this, be sure to update your css
110
- * */
111
- handleSize: PropTypes.arrayOf(PropTypes.number),
112
- lockAspectRatio: PropTypes.bool,
113
- /*
114
- * Max X & Y measure
115
- * */
116
- maxConstraints: PropTypes.arrayOf(PropTypes.number),
117
- /*
118
- * Min X & Y measure
119
- * */
120
- minConstraints: PropTypes.arrayOf(PropTypes.number),
121
- /*
122
- * Called on stop resize event
123
- * */
124
- onResizeStop: PropTypes.func,
125
- /*
126
- * Called on start resize event
127
- * */
128
- onResizeStart: PropTypes.func,
129
- /*
130
- * Called on resize event
131
- * */
132
- onResize: PropTypes.func,
133
- /*
134
- * Defines which resize handles should be rendered (default: 'se')
135
- * 's' - South handle (bottom-center)
136
- * 'w' - West handle (left-center)
137
- * 'e' - East handle (right-center)
138
- * 'n' - North handle (top-center)
139
- * 'sw' - Southwest handle (bottom-left)
140
- * 'nw' - Northwest handle (top-left)
141
- * 'se' - Southeast handle (bottom-right)
142
- * 'ne' - Northeast handle (top-center)
143
- * */
144
- resizeHandles: PropTypes.arrayOf(PropTypes.oneOf(['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne'])),
145
-
146
- /*
147
- * If `transform: scale(n)` is set on the parent, this should be set to `n`.
148
- * */
149
- transformScale: PropTypes.number,
150
- /*
151
- * Initial width
152
- */
153
- width: (...args) => {
154
- const [props] = args;
155
- // Required if resizing width or both
156
- if (props.axis === 'both' || props.axis === 'x') {
157
- return PropTypes.number.isRequired(...args);
158
- }
159
- return PropTypes.number(...args);
160
- },
161
- };
@@ -1,14 +0,0 @@
1
- // @flow
2
- import React from 'react';
3
- import type {Element as ReactElement} from 'react';
4
-
5
- // React.addons.cloneWithProps look-alike that merges style & className.
6
- export function cloneElement(element: ReactElement<any>, props: Object): ReactElement<any> {
7
- if (props.style && element.props.style) {
8
- props.style = {...element.props.style, ...props.style};
9
- }
10
- if (props.className && element.props.className) {
11
- props.className = `${element.props.className} ${props.className}`;
12
- }
13
- return React.cloneElement(element, props);
14
- }