Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Symmetrical resize feature (Fixes: #740) #955

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;`
Expand Down
140 changes: 116 additions & 24 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export interface Props {
enableUserSelectHack?: boolean;
allowAnyClick?: boolean;
scale?: number;
resizeSymmetry?: "none" | "horizontal" | "vertical" | "central";
[key: string]: any;
}

Expand Down Expand Up @@ -446,28 +447,67 @@ export class Rnd extends React.PureComponent<Props, State> {
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({
Expand All @@ -488,21 +528,64 @@ export class Rnd extends React.PureComponent<Props, State> {
) {
// 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(() => {
Expand Down Expand Up @@ -600,6 +683,7 @@ export class Rnd extends React.PureComponent<Props, State> {
resizeHandleWrapperStyle,
scale,
allowAnyClick,
resizeSymmetry,
...resizableProps
} = this.props;
const defaultValue = this.props.default ? { ...this.props.default } : undefined;
Expand All @@ -623,6 +707,13 @@ export class Rnd extends React.PureComponent<Props, State> {
// 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 (
<Draggable
Expand Down Expand Up @@ -677,6 +768,7 @@ export class Rnd extends React.PureComponent<Props, State> {
handleClasses={resizeHandleClasses}
handleComponent={resizeHandleComponent}
scale={this.props.scale}
resizeRatio={resizeRatio}
>
{children}
</Resizable>
Expand Down
11 changes: 11 additions & 0 deletions stories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => <Bare />);

storiesOf("basic", module)
Expand Down Expand Up @@ -106,3 +111,9 @@ storiesOf("sandbox", module)
storiesOf("ratio", module).add("lock aspect ratio", () => <LockAspectRatioBasic />);

storiesOf("min", module).add("min uncontrolled", () => <MinUncontrolled />);

storiesOf("resize symmetry", module).add("vertical", () => <ResizeSymmetryVertical />);
storiesOf("resize symmetry", module).add("horizontal", () => <ResizeSymmetryHorizontal />);
storiesOf("resize symmetry", module).add("central", () => <ResizeSymmetryCentral />);
storiesOf("resize symmetry", module).add("bounds", () => <ResizeSymmetryBounds />);

39 changes: 39 additions & 0 deletions stories/resizeSymmetry/bounds.tsx
Original file line number Diff line number Diff line change
@@ -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 () => (
<div style={parentBoundary}>
<Rnd
style={style}
bounds="parent"
default={{
width: 200,
height: 200,
x: 0,
y: 0,
}}
resizeSymmetry="central"
>
<div style={lineStyle} />
<div style={innerDiv}>

</div>
</Rnd>
</div>
);
36 changes: 36 additions & 0 deletions stories/resizeSymmetry/central.tsx
Original file line number Diff line number Diff line change
@@ -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 () => (
<Rnd
style={style}
default={{
width: 200,
height: 200,
x: 0,
y: 0,
}}
resizeSymmetry="central"
>
<div style={lineStyle} />
<div style={innerDiv}>

</div>
</Rnd>
);
25 changes: 25 additions & 0 deletions stories/resizeSymmetry/horizontal.tsx
Original file line number Diff line number Diff line change
@@ -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 () => (
<Rnd
style={style}
default={{
width: 200,
height: 200,
x: 0,
y: 0,
}}
resizeSymmetry="horizontal"
>
<div style={lineStyle} />
</Rnd>
);
28 changes: 28 additions & 0 deletions stories/resizeSymmetry/vertical.tsx
Original file line number Diff line number Diff line change
@@ -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 () => (
<Rnd
style={style}
default={{
width: 200,
height: 200,
x: 0,
y: 0,
}}
resizeSymmetry="vertical"
>
<div style={innerDiv}>

</div>
</Rnd>
);
Loading