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..a8976b32 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; } @@ -446,28 +447,67 @@ 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; + 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 = () => + { + 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)) { - const max = offsetWidth + (boundaryLeft - selfLeft) / scale; - this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max }); + 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) { - const max = (selfTop - boundaryTop) / scale + this.resizable.size.height; - this.setState({ - maxHeight: max > Number(maxHeight) ? maxHeight : max, - }); + 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)) { - const max = offsetHeight + (boundaryTop - selfTop) / scale; - this.setState({ - maxHeight: max > Number(maxHeight) ? maxHeight : max, - }); - } + 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, + }); + } + } } } else { this.setState({ @@ -488,21 +528,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 == "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 == "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 == "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(() => { @@ -600,6 +683,7 @@ export class Rnd extends React.PureComponent { resizeHandleWrapperStyle, scale, allowAnyClick, + resizeSymmetry, ...resizableProps } = this.props; const defaultValue = this.props.default ? { ...this.props.default } : undefined; @@ -623,6 +707,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..6fec6ebd 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -51,6 +51,11 @@ 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" +import ResizeSymmetryBounds from "./resizeSymmetry/bounds" + storiesOf("bare", module).add("bare", () => ); storiesOf("basic", module) @@ -106,3 +111,9 @@ 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", () => ); +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 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 () => ( + +
+ +
+
+);