From bf5df6413276beb8f250c22307464d320ee08903 Mon Sep 17 00:00:00 2001 From: boev Date: Mon, 11 Nov 2024 16:59:35 +0300 Subject: [PATCH 1/4] Added resize symmetry feature --- README.md | 5 ++ src/index.tsx | 74 +++++++++++++++++++++++---- stories/index.tsx | 9 ++++ stories/resizeSymmetry/central.tsx | 36 +++++++++++++ stories/resizeSymmetry/horizontal.tsx | 25 +++++++++ stories/resizeSymmetry/vertical.tsx | 28 ++++++++++ 6 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 stories/resizeSymmetry/central.tsx create mode 100644 stories/resizeSymmetry/horizontal.tsx create mode 100644 stories/resizeSymmetry/vertical.tsx diff --git a/README.md b/README.md index 742028be..43a8bc73 100755 --- a/README.md +++ b/README.md @@ -331,6 +331,11 @@ you to, for example, get the correct resize and drag deltas while you are zoomed a transform or matrix in the parent of this element. If omitted, set `1`. +#### `resizeSymmetry?: 'none' | 'vertical' | 'horizontal' | 'central'` + +Allows resize so what `vertical` or `horizontal` axes or `central` point stands still while resizing. +Also useful with `lockAspectRatio` option. + ## Callback #### `onResizeStart?: RndResizeStartCallback;` diff --git a/src/index.tsx b/src/index.tsx index e0d95477..fa0e5444 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -157,6 +157,7 @@ export interface Props { enableUserSelectHack?: boolean; allowAnyClick?: boolean; scale?: number; + resizeSymmetry?: "none" | "horizontal" | "vertical" | "central" [key: string]: any; } @@ -488,21 +489,64 @@ export class Rnd extends React.PureComponent { ) { // INFO: Apply x and y position adjustments caused by resizing to draggable const newPos = { x: this.originalPosition.x, y: this.originalPosition.y }; - const left = -delta.width; - const top = -delta.height; - const directions: ResizeDirection[] = ["top", "left", "topLeft", "bottomLeft", "topRight"]; - - if (directions.includes(direction)) { - if (direction === "bottomLeft") { - newPos.x += left; - } else if (direction === "topRight") { - newPos.y += top; - } else { + if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "none") + { + const left = -delta.width; + const top = -delta.height; + const directions: ResizeDirection[] = ["top", "left", "topLeft", "bottomLeft", "topRight"]; + if (directions.includes(direction)) { + if (direction === "bottomLeft") { + newPos.x += left; + } else if (direction === "topRight") { + newPos.y += top; + } else { + newPos.x += left; + newPos.y += top; + } + } + } + else if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "vertical") + { + const left = -delta.width / 2; + const top = -delta.height; + const directions: ResizeDirection[] = ["top", "left", "right", "topLeft", "bottomLeft", "topRight", "bottomRight"]; + if (directions.includes(direction)) { + if (direction === "bottomLeft" || direction === "bottomRight") { + newPos.x += left; + } else if (direction === "right") { + newPos.x -= -left; + newPos.y += top; + } else { + newPos.x += left; + newPos.y += top; + } + } + } + else if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "horizontal") + { + const left = -delta.width; + const top = -delta.height / 2; + const directions: ResizeDirection[] = ["left", "bottom", "top", "bottomLeft", "bottomRight", "topLeft", "topRight", "bottomLeft"]; + if (directions.includes(direction)) { + if (direction === "bottomRight" || direction === "topRight") { + newPos.y += top; + } else { + newPos.x += left; + newPos.y += top; + } + } + } + else if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "central") + { + const left = -delta.width / 2; + const top = -delta.height / 2; + const directions: ResizeDirection[] = ["top", "left", "right", "bottom", "topLeft", "bottomLeft", "topRight", "bottomRight"]; + if (directions.includes(direction)) { newPos.x += left; newPos.y += top; } } - + const draggableState = this.draggable.state as unknown as { x: number; y: number }; if (newPos.x !== draggableState.x || newPos.y !== draggableState.y) { flushSync(() => { @@ -623,6 +667,13 @@ export class Rnd extends React.PureComponent { // INFO: Make uncontorolled component when resizing to control position by setPostion. const pos = this.state.resizing ? undefined : draggablePosition; const dragAxisOrUndefined = this.state.resizing ? "both" : dragAxis; + let resizeRatio:number | [number, number] | undefined = [1, 1]; + if (this.props.resizeSymmetry == "vertical") + resizeRatio = [2, 1]; + else if (this.props.resizeSymmetry == "horizontal") + resizeRatio = [1, 2]; + else if (this.props.resizeSymmetry == "central") + resizeRatio = [2, 2]; return ( { handleClasses={resizeHandleClasses} handleComponent={resizeHandleComponent} scale={this.props.scale} + resizeRatio={resizeRatio} > {children} diff --git a/stories/index.tsx b/stories/index.tsx index e54836ce..0c6165c0 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -51,6 +51,10 @@ import SandboxLockAspectRatioWithBounds from "./sandbox/lock-aspect-ratio-with-b import LockAspectRatioBasic from "./lock-aspect-ratio/basic"; import Issue622 from "./sandbox/issue-#622"; +import ResizeSymmetryVertical from "./resizeSymmetry/vertical" +import ResizeSymmetryHorizontal from "./resizeSymmetry/horizontal" +import ResizeSymmetryCentral from "./resizeSymmetry/central" + storiesOf("bare", module).add("bare", () => ); storiesOf("basic", module) @@ -106,3 +110,8 @@ storiesOf("sandbox", module) storiesOf("ratio", module).add("lock aspect ratio", () => ); storiesOf("min", module).add("min uncontrolled", () => ); + +storiesOf("resize symmetry", module).add("vertical", () => ); +storiesOf("resize symmetry", module).add("horizontal", () => ); +storiesOf("resize symmetry", module).add("central", () => ); + diff --git a/stories/resizeSymmetry/central.tsx b/stories/resizeSymmetry/central.tsx new file mode 100644 index 00000000..11953bff --- /dev/null +++ b/stories/resizeSymmetry/central.tsx @@ -0,0 +1,36 @@ +import React, {CSSProperties} from "react"; +import { Rnd } from "../../src"; +import { style } from "../styles"; + +const innerDiv: CSSProperties = { + display: "flex", + alignItems: "center", + justifyContent: "center", + border: "dashed 1px #9C27B0", + height: "120%", +}; +const lineStyle: CSSProperties = { + width: "120%", + top: "50%", + border: "dashed 1px #9C27B0", + position: 'absolute' +} + + +export default () => ( + +
+
+ +
+ +); diff --git a/stories/resizeSymmetry/horizontal.tsx b/stories/resizeSymmetry/horizontal.tsx new file mode 100644 index 00000000..856dacbe --- /dev/null +++ b/stories/resizeSymmetry/horizontal.tsx @@ -0,0 +1,25 @@ +import React, {CSSProperties} from "react"; +import { Rnd } from "../../src"; +import { style } from "../styles"; + +const lineStyle: CSSProperties = { + width: "120%", + top: "50%", + border: "dashed 1px #9C27B0", + position: 'absolute' +} + +export default () => ( + +
+ +); diff --git a/stories/resizeSymmetry/vertical.tsx b/stories/resizeSymmetry/vertical.tsx new file mode 100644 index 00000000..f744d840 --- /dev/null +++ b/stories/resizeSymmetry/vertical.tsx @@ -0,0 +1,28 @@ +import React, {CSSProperties} from "react"; +import { Rnd } from "../../src"; +import { style } from "../styles"; + +const innerDiv: CSSProperties = { + display: "flex", + alignItems: "center", + justifyContent: "center", + border: "dashed 1px #9C27B0", + height: "120%", +}; + +export default () => ( + +
+ +
+
+); From 246b06de058bf40c780e7a9f6d3d637237d0e006 Mon Sep 17 00:00:00 2001 From: boev Date: Mon, 2 Dec 2024 20:15:02 +0300 Subject: [PATCH 2/4] fixed bounded case for resize symmetry --- src/index.tsx | 76 +++++++++++++++++++++---------- stories/index.tsx | 2 + stories/resizeSymmetry/bounds.tsx | 39 ++++++++++++++++ 3 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 stories/resizeSymmetry/bounds.tsx diff --git a/src/index.tsx b/src/index.tsx index fa0e5444..c4c1d34d 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -447,28 +447,56 @@ export class Rnd extends React.PureComponent { const hasTop = dir.startsWith("top"); const hasBottom = dir.startsWith("bottom"); - if ((hasLeft || hasTop) && this.resizable) { - const max = (selfLeft - boundaryLeft) / scale + this.resizable.size.width; - this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); - } - // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. - if (hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) { - const max = offsetWidth + (boundaryLeft - selfLeft) / scale; - this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); - } - if ((hasTop || hasLeft) && this.resizable) { - const max = (selfTop - boundaryTop) / scale + this.resizable.size.height; - this.setState({ - maxHeight: max > Number(maxHeight) ? maxHeight : max, - }); - } - // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. - if (hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) { - const max = offsetHeight + (boundaryTop - selfTop) / scale; - this.setState({ - maxHeight: max > Number(maxHeight) ? maxHeight : max, - }); + if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "none") + { + if ((hasLeft || hasTop) && this.resizable) { + const max = (selfLeft - boundaryLeft) / scale + this.resizable.size.width; + this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); + } + // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. + if (hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) { + const max = offsetWidth + (boundaryLeft - selfLeft) / scale; + this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); + } + if ((hasTop || hasLeft) && this.resizable) { + const max = (selfTop - boundaryTop) / scale + this.resizable.size.height; + this.setState({ + maxHeight: max > Number(maxHeight) ? maxHeight : max, + }); + } + // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. + if (hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) { + const max = offsetHeight + (boundaryTop - selfTop) / scale; + this.setState({ + maxHeight: max > Number(maxHeight) ? maxHeight : max, + }); + } } + else + { + if ((hasLeft || hasTop || hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) && this.resizable) { + if (this.props.resizeSymmetry == "vertical" || this.props.resizeSymmetry == "central") + { + const spaceLeft = (selfLeft - boundaryLeft) / scale; + const spaceRight = offsetWidth - spaceLeft - this.resizable.size.width; + const max = spaceRight > spaceLeft ? (this.resizable.size.width + 2 * spaceLeft) : (this.resizable.size.width + 2 * spaceRight); + this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); + } + } + + if ((hasTop || hasLeft || hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) && this.resizable) { + if (this.props.resizeSymmetry == "horizontal" || this.props.resizeSymmetry == "central") + { + const spaceTop = (selfTop - boundaryTop) / scale; + const spaceBottom = offsetHeight - spaceTop - this.resizable.size.height; + const max = spaceBottom > spaceTop ? (this.resizable.size.height + 2 * spaceTop) : (this.resizable.size.height + 2 * spaceBottom); + + this.setState({ + maxHeight: max > Number(maxHeight) ? maxHeight : max, + }); + } + } + } } } else { this.setState({ @@ -505,7 +533,7 @@ export class Rnd extends React.PureComponent { } } } - else if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "vertical") + else if (this.props.resizeSymmetry == "vertical") { const left = -delta.width / 2; const top = -delta.height; @@ -522,7 +550,7 @@ export class Rnd extends React.PureComponent { } } } - else if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "horizontal") + else if (this.props.resizeSymmetry == "horizontal") { const left = -delta.width; const top = -delta.height / 2; @@ -536,7 +564,7 @@ export class Rnd extends React.PureComponent { } } } - else if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "central") + else if (this.props.resizeSymmetry == "central") { const left = -delta.width / 2; const top = -delta.height / 2; diff --git a/stories/index.tsx b/stories/index.tsx index 0c6165c0..6fec6ebd 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -54,6 +54,7 @@ import Issue622 from "./sandbox/issue-#622"; import ResizeSymmetryVertical from "./resizeSymmetry/vertical" import ResizeSymmetryHorizontal from "./resizeSymmetry/horizontal" import ResizeSymmetryCentral from "./resizeSymmetry/central" +import ResizeSymmetryBounds from "./resizeSymmetry/bounds" storiesOf("bare", module).add("bare", () => ); @@ -114,4 +115,5 @@ storiesOf("min", module).add("min uncontrolled", () => ); storiesOf("resize symmetry", module).add("vertical", () => ); storiesOf("resize symmetry", module).add("horizontal", () => ); storiesOf("resize symmetry", module).add("central", () => ); +storiesOf("resize symmetry", module).add("bounds", () => ); diff --git a/stories/resizeSymmetry/bounds.tsx b/stories/resizeSymmetry/bounds.tsx new file mode 100644 index 00000000..3ef72b83 --- /dev/null +++ b/stories/resizeSymmetry/bounds.tsx @@ -0,0 +1,39 @@ +import React, {CSSProperties} from "react"; +import { Rnd } from "../../src"; +import { style, parentBoundary } from "../styles"; + +const innerDiv: CSSProperties = { + display: "flex", + alignItems: "center", + justifyContent: "center", + border: "dashed 1px #9C27B0", + height: "120%", +}; +const lineStyle: CSSProperties = { + width: "120%", + top: "50%", + border: "dashed 1px #9C27B0", + position: 'absolute' +} + + +export default () => ( +
+ +
+
+ +
+ +
+); \ No newline at end of file From 6b9cc69cc8e52e9b2836bd01d2cb359e550232f2 Mon Sep 17 00:00:00 2001 From: boev Date: Tue, 3 Dec 2024 07:20:19 +0300 Subject: [PATCH 3/4] fixed bounds case with symmetry resize --- src/index.tsx | 65 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index c4c1d34d..48b0697b 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -157,7 +157,7 @@ export interface Props { enableUserSelectHack?: boolean; allowAnyClick?: boolean; scale?: number; - resizeSymmetry?: "none" | "horizontal" | "vertical" | "central" + resizeSymmetry?: "none" | "horizontal" | "vertical" | "central"; [key: string]: any; } @@ -447,40 +447,78 @@ export class Rnd extends React.PureComponent { const hasTop = dir.startsWith("top"); const hasBottom = dir.startsWith("bottom"); - if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "none") + const setSymmetricMaxWidth = () => + { + const spaceLeft = (selfLeft - boundaryLeft) / scale; + const spaceRight = offsetWidth - spaceLeft - this.resizable.size.width; + const max = spaceRight > spaceLeft ? (this.resizable.size.width + 2 * spaceLeft) : (this.resizable.size.width + 2 * spaceRight); + this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); + } + + const setSymmetricMaxHeight = () => { - if ((hasLeft || hasTop) && this.resizable) { + const spaceTop = (selfTop - boundaryTop) / scale; + const spaceBottom = offsetHeight - spaceTop - this.resizable.size.height; + const max = spaceBottom > spaceTop ? (this.resizable.size.height + 2 * spaceTop) : (this.resizable.size.height + 2 * spaceBottom); + + this.setState({ + maxHeight: max > Number(maxHeight) ? maxHeight : max, + }); + } + + if ((hasLeft || hasTop) && this.resizable) { + if (this.props.resizeSymmetry == "vertical" || this.props.resizeSymmetry == "central") + setSymmetricMaxWidth(); + else + { const max = (selfLeft - boundaryLeft) / scale + this.resizable.size.width; this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); } - // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. - if (hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) { + } + // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. + if (hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) { + if (this.props.resizeSymmetry == "vertical" || this.props.resizeSymmetry == "central") + setSymmetricMaxWidth(); + else + { const max = offsetWidth + (boundaryLeft - selfLeft) / scale; this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); } - if ((hasTop || hasLeft) && this.resizable) { + } + if ((hasTop || hasLeft) && this.resizable) { + if (this.props.resizeSymmetry == "horizontal" || this.props.resizeSymmetry == "central") + setSymmetricMaxHeight(); + else + { const max = (selfTop - boundaryTop) / scale + this.resizable.size.height; this.setState({ maxHeight: max > Number(maxHeight) ? maxHeight : max, }); } - // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. - if (hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) { + } + // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story. + if (hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) { + if (this.props.resizeSymmetry == "horizontal" || this.props.resizeSymmetry == "central") + setSymmetricMaxHeight(); + else + { const max = offsetHeight + (boundaryTop - selfTop) / scale; this.setState({ maxHeight: max > Number(maxHeight) ? maxHeight : max, }); } } + + if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "none") + { + + } else { if ((hasLeft || hasTop || hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) && this.resizable) { - if (this.props.resizeSymmetry == "vertical" || this.props.resizeSymmetry == "central") + if (this.props.resizeSymmetry == "vertical" || this.props.resizeSymmetry == "central") { - const spaceLeft = (selfLeft - boundaryLeft) / scale; - const spaceRight = offsetWidth - spaceLeft - this.resizable.size.width; - const max = spaceRight > spaceLeft ? (this.resizable.size.width + 2 * spaceLeft) : (this.resizable.size.width + 2 * spaceRight); - this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); + } } @@ -672,6 +710,7 @@ export class Rnd extends React.PureComponent { resizeHandleWrapperStyle, scale, allowAnyClick, + resizeSymmetry, ...resizableProps } = this.props; const defaultValue = this.props.default ? { ...this.props.default } : undefined; From 4024555dbdf9cd9c0d819cf1a2b6b4716a41eb75 Mon Sep 17 00:00:00 2001 From: boev Date: Thu, 5 Dec 2024 16:39:25 +0300 Subject: [PATCH 4/4] fixed bounds case with symmetry resize --- src/index.tsx | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 48b0697b..a8976b32 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -507,33 +507,6 @@ export class Rnd extends React.PureComponent { maxHeight: max > Number(maxHeight) ? maxHeight : max, }); } - } - - if (!this.props.resizeSymmetry || this.props.resizeSymmetry == "none") - { - - } - else - { - if ((hasLeft || hasTop || hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) && this.resizable) { - if (this.props.resizeSymmetry == "vertical" || this.props.resizeSymmetry == "central") - { - - } - } - - if ((hasTop || hasLeft || hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) && this.resizable) { - if (this.props.resizeSymmetry == "horizontal" || this.props.resizeSymmetry == "central") - { - const spaceTop = (selfTop - boundaryTop) / scale; - const spaceBottom = offsetHeight - spaceTop - this.resizable.size.height; - const max = spaceBottom > spaceTop ? (this.resizable.size.height + 2 * spaceTop) : (this.resizable.size.height + 2 * spaceBottom); - - this.setState({ - maxHeight: max > Number(maxHeight) ? maxHeight : max, - }); - } - } } } } else {