diff --git a/__tests__/Resizable.test.js b/__tests__/Resizable.test.js
index 7b12e5ca..1dd52528 100644
--- a/__tests__/Resizable.test.js
+++ b/__tests__/Resizable.test.js
@@ -63,4 +63,111 @@ describe('render Resizable', () => {
expect(element.find('.custom-component-se')).toHaveLength(1);
});
});
+
+ describe('onResize callback with modified position', () => {
+ const customProps = {
+ ...props,
+ resizeHandles: ['nw', 'sw' ,'ne', 'se', 'n', 's', 'w', 'e'],
+ };
+ const mockClientRect = {
+ left: 0,
+ top: 0,
+ };
+ const eventTarget = document.createElement('div');
+ // $FlowIgnore need to override to have control over dummy dom element
+ eventTarget.getBoundingClientRect = () => ({ ...mockClientRect });
+ const mockEvent = { target: eventTarget };
+ const element = shallow({resizableBoxChildren});
+ const nwHandle = element.find('DraggableCore').first();
+
+ test('Gradual resizing without movement between does not modify callback', () => {
+ expect(props.onResize).not.toHaveBeenCalled();
+ nwHandle.prop('onDrag')(mockEvent, { node: null, deltaX: 5, deltaY: 10 });
+ expect(props.onResize).lastCalledWith(
+ mockEvent,
+ expect.objectContaining({
+ size: {
+ height: 40,
+ width: 45,
+ },
+ })
+ );
+ });
+
+ test('Movement between callbacks modifies response values', () => {
+ expect(props.onResize).not.toHaveBeenCalled();
+
+ mockClientRect.top = -10; // Object moves between callbacks
+ nwHandle.prop('onDrag')(mockEvent, { node: null, deltaX: 5, deltaY: 10 });
+ expect(props.onResize).lastCalledWith(
+ mockEvent,
+ expect.objectContaining({
+ size: {
+ height: 50, // No height change since deltaY is caused by clientRect moving vertically
+ width: 45,
+ },
+ })
+ );
+
+ mockClientRect.left = 20; // Object moves between callbacks
+ nwHandle.prop('onDrag')(mockEvent, { node: null, deltaX: 5, deltaY: 10 });
+ expect(props.onResize).lastCalledWith(
+ mockEvent,
+ expect.objectContaining({
+ size: {
+ height: 40, // Height decreased as deltaY increases - no further top position change since last
+ width: 25, // Width decreased 25 - 5 from deltaX and 20 from changing position
+ },
+ })
+ );
+
+ props.onResize.mockClear();
+ mockClientRect.left -= 10; // Object moves between callbacks
+ mockClientRect.top -= 10; // Object moves between callbacks
+ nwHandle.prop('onDrag')(mockEvent, { node: null, deltaX: 10, deltaY: 10 });
+ expect(props.onResize).not.toHaveBeenCalled();
+
+ mockClientRect.left -= 10; // Object moves between callbacks
+ mockClientRect.top -= 10; // Object moves between callbacks
+ const swHandle = element.find('DraggableCore').at(1);
+ swHandle.prop('onDrag')(mockEvent, { node: null, deltaX: 10, deltaY: 10 });
+ expect(props.onResize).lastCalledWith(
+ mockEvent,
+ expect.objectContaining({
+ size: {
+ height: 60, // Changed since resizing from bottom doesn't cause position change
+ width: 50, // No change - movement has caused entire delta
+ },
+ })
+ );
+
+ mockClientRect.left -= 10; // Object moves between callbacks
+ mockClientRect.top -= 10; // Object moves between callbacks
+ const neHandle = element.find('DraggableCore').at(2);
+ neHandle.prop('onDrag')(mockEvent, { node: null, deltaX: 10, deltaY: 10 });
+ expect(props.onResize).lastCalledWith(
+ mockEvent,
+ expect.objectContaining({
+ size: {
+ height: 50, // No change - movement has caused entire delta
+ width: 60, // Changed since resizing from right doesn't cause position change
+ },
+ })
+ );
+
+ mockClientRect.left -= 10; // Object moves between callbacks
+ mockClientRect.top -= 10; // Object moves between callbacks
+ const seHandle = element.find('DraggableCore').at(3);
+ seHandle.prop('onDrag')(mockEvent, { node: null, deltaX: 10, deltaY: 10 });
+ expect(props.onResize).lastCalledWith(
+ mockEvent,
+ expect.objectContaining({
+ size: {
+ height: 60, // Changed since resizing from right doesn't cause position change
+ width: 60, // Changed since resizing from right doesn't cause position change
+ },
+ })
+ );
+ });
+ });
});
diff --git a/examples/1.html b/examples/1.html
index c63def0b..70e3a600 100644
--- a/examples/1.html
+++ b/examples/1.html
@@ -7,13 +7,20 @@
}
#content {
width: 100%;
- background: #eee;
- padding-bottom: 200px;
+ padding-bottom: 250px;
}
.layoutRoot {
display: flex;
+ background: #eee;
+ margin-bottom: 20px;
flex-wrap: wrap;
}
+ .absoluteLayout {
+ height: 600px;
+ position: relative;
+ justify-content: center;
+ align-items: center
+ }
.box {
display: inline-block;
background: #ccc;
@@ -42,6 +49,24 @@
.box3:hover .react-resizable-handle {
display: block;
}
+ .absolutely-positioned {
+ position: absolute !important;
+ }
+ .center-aligned {
+ margin: auto;
+ }
+ .left-aligned {
+ left: 0;
+ }
+ .right-aligned {
+ right: 0;
+ }
+ .top-aligned {
+ top: 0;
+ }
+ .bottom-aligned {
+ bottom: 0;
+ }
diff --git a/examples/ExampleLayout.js b/examples/ExampleLayout.js
index 22666fcd..1e3ee089 100644
--- a/examples/ExampleLayout.js
+++ b/examples/ExampleLayout.js
@@ -2,18 +2,50 @@ import React from 'react';
import Resizable from '../lib/Resizable';
import ResizableBox from '../lib/ResizableBox';
import 'style-loader!css-loader!../css/styles.css';
-import 'style-loader!css-loader!./test.css';
+import 'style-loader!css-loader!./example.css';
export default class ExampleLayout extends React.Component<{}, {width: number, height: number}> {
- state = {width: 200, height: 200};
+ state = {
+ width: 200,
+ height: 200,
+ absoluteWidth: 200,
+ absoluteHeight: 200,
+ absoluteLeft: 0,
+ absoluteTop: 0,
+ };
onClick = () => {
- this.setState({width: 200, height: 200});
+ this.setState({ width: 200, height: 200, absoluteWidth: 200, absoluteHeight: 200 });
};
onResize = (event, {element, size, handle}) => {
this.setState({width: size.width, height: size.height});
};
+ onResizeAbsolute = (event, {element, size, handle}) => {
+ this.setState((state) => {
+ let newLeft = state.absoluteLeft;
+ let newTop = state.absoluteTop;
+ const deltaHeight = size.height - state.absoluteHeight;
+ const deltaWidth = size.width - state.absoluteWidth;
+ if (handle[0] === 'n') {
+ newTop -= deltaHeight / 2;
+ } else if (handle[0] === 's') {
+ newTop += deltaHeight / 2;
+ }
+ if (handle[handle.length - 1] === 'w') {
+ newLeft -= deltaWidth / 2;
+ } else if (handle[handle.length - 1] === 'e') {
+ newLeft += deltaWidth / 2;
+ }
+
+ return {
+ absoluteWidth: size.width,
+ absoluteHeight: size.height,
+ absoluteLeft: newLeft,
+ absoluteTop: newTop,
+ };
+ });
+ };
render() {
return (
@@ -73,6 +105,38 @@ export default class ExampleLayout extends React.Component<{}, {width: number, h
Not resizable ("none" axis).
+
+
+ {"Top-left Aligned"}
+
+
+ {"Bottom-left Aligned"}
+
+
+
+ {"Raw use of element with controlled position. Resize and reposition in all directions"}
+
+
+
+ {"Top-right Aligned"}
+
+
+ {"Bottom-right Aligned"}
+
+
);
}
diff --git a/lib/Resizable.js b/lib/Resizable.js
index 83353d25..d5561881 100644
--- a/lib/Resizable.js
+++ b/lib/Resizable.js
@@ -2,10 +2,9 @@
import React from 'react';
import type {Node as ReactNode} from 'react';
import {DraggableCore} from 'react-draggable';
-
import {cloneElement} from './utils';
import {resizableProps} from "./propTypes";
-import type {ResizeHandleAxis, Props, ResizableState, DragCallbackData} from './propTypes';
+import type {ResizeHandleAxis, Props, ResizableState, DragCallbackData, ClientRect} from './propTypes';
export default class Resizable extends React.Component {
static propTypes = resizableProps;
@@ -24,6 +23,9 @@ export default class Resizable extends React.Component {
slackW: 0, slackH: 0,
};
+ lastHandleRect: ?ClientRect = null;
+ draggingNode: ?HTMLElement = null;
+
lockAspectRatio(width: number, height: number, aspectRatio: number): [number, number] {
height = width / aspectRatio;
width = height * aspectRatio;
@@ -93,6 +95,36 @@ export default class Resizable extends React.Component {
const canDragX = (this.props.axis === 'both' || this.props.axis === 'x') && ['n', 's'].indexOf(axis) === -1;
const canDragY = (this.props.axis === 'both' || this.props.axis === 'y') && ['e', 'w'].indexOf(axis) === -1;
+ /*
+ Track the element being dragged to account for changes in position.
+ If a handle's position is changed between callbacks, we need to factor this in to the next callback
+ */
+ if (this.draggingNode == null && e.target instanceof HTMLElement) {
+ this.draggingNode = e.target;
+ }
+ if (this.draggingNode instanceof HTMLElement) {
+ const handleRect = this.draggingNode.getBoundingClientRect();
+ if (this.lastHandleRect != null) {
+ // Find how much the handle has moved since the last callback
+ const deltaLeftSinceLast = handleRect.left - this.lastHandleRect.left;
+ const deltaTopSinceLast = handleRect.top - this.lastHandleRect.top;
+
+ // If the handle has repositioned on either axis since last render,
+ // we need to increase our callback values by this much.
+ // Only checking 'n', 'w' since resizing by 's', 'w' won't affect the overall position on page
+ if (canDragX && axis[axis.length - 1] === 'w') {
+ deltaX += deltaLeftSinceLast / this.props.transformScale;
+ }
+ if(canDragY && axis[0] === 'n') {
+ deltaY += deltaTopSinceLast / this.props.transformScale;
+ }
+ }
+ this.lastHandleRect = {
+ top: handleRect.top,
+ left: handleRect.left,
+ };
+ }
+
// reverse delta if using top or left drag handles
if (canDragX && axis[axis.length - 1] === 'w') {
deltaX = -deltaX;
@@ -117,6 +149,7 @@ export default class Resizable extends React.Component {
// nothing
} else if (handlerName === 'onResizeStop') {
newState.slackW = newState.slackH = 0;
+ this.lastHandleRect = this.draggingNode = null;
} else {
// Early return if no change after constraints
if (width === this.props.width && height === this.props.height) return;
diff --git a/lib/propTypes.js b/lib/propTypes.js
index 6aef0c1a..6ec135ff 100644
--- a/lib/propTypes.js
+++ b/lib/propTypes.js
@@ -23,6 +23,10 @@ export type ResizeCallbackData = {|
size: {|width: number, height: number|},
handle: ResizeHandleAxis
|};
+export type ClientRect = {|
+ left: number;
+ top: number;
+|};
//
export type Props = {|
diff --git a/webpack.config.js b/webpack.config.js
index e60e4cae..9954e050 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -4,7 +4,7 @@ const path = require('path');
module.exports = {
context: __dirname,
entry: {
- test: "./test/test.js",
+ test: "./examples/example.js",
},
output: {
path: path.join(__dirname, "dist"),