From 0c7746dc726c77408a093189eb2c20a2cc8c1efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Kaln=C3=BD?= Date: Mon, 5 Jul 2021 12:00:12 +0200 Subject: [PATCH] added preventDefault prop --- CHANGELOG.md | 18 +++--- README.md | 10 +++- example/example.js | 5 ++ lib/DraggableCore.js | 7 ++- package.json | 2 +- specs/draggable.spec.jsx | 122 ++++++++++++++++++++++++++++++++------- 6 files changed, 131 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd8eb515..3d37bf02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 4.4.4 (July 4, 2021) + +- Add `preventDefault` prop to allow touch scroll + ### 4.4.3 (June 8, 2020) - Add `nodeRef` to TypeScript definitions @@ -41,16 +45,16 @@ `nodeRef` is also available on ``. - Remove "browser" field in "package.json": - There is nothing special in the browser build that is actually practical - for modern use. The "browser" field, as defined in + for modern use. The "browser" field, as defined in https://github.com/defunctzombie/package-browser-field-spec#overview, indicates that you should use it if you are directly accessing globals, using browser-specific features, dom manipulation, etc. - + React components like react-draggable are built to do minimal raw DOM manipulation, and to always gate this behind conditionals to ensure that server-side rendering still works. We don't make any changes to any of that for the "browser" build, so it's entirely redundant. - + This should also fix the "Super expression must either be null or a function" error (#472) that some users have experienced with particular bundler configurations. @@ -61,7 +65,7 @@ - The browser build will likely be removed entirely in 5.0. - Fix: Make `bounds` optional in TypeScript [#473](https://github.com/strml/react-draggable/pull/473) -### 4.3.1 (Apr 11, 2020) +### 4.3.1 (Apr 11, 2020) > This is a bugfix release. @@ -72,7 +76,7 @@ return React.cloneElement(this.props.children, {style: this.props.children.props.style}); ``` , `style` ends up undefined. -- Fixed a bug that caused debug output to show up in the build. +- Fixed a bug that caused debug output to show up in the build. - `babel-loader` cache does not invalidate when it should. I had modified webpack.config.js in the last version but it reused stale cache. ### 4.3.0 (Apr 10, 2020) @@ -82,7 +86,7 @@ - Thanks @schnerd, [#450](https://github.com/mzabriskie/react-draggable/pull/450) - Fix an issue where the insides of a `` were not scrollable on touch devices due to the outer container having `touch-action: none`. - This was a long-standing hack for mobile devices. Without it, the page will scroll while you drag the element. - - The new solution will simply cancel the touch event `e.preventDefault()`. However, due to changes in Chrome >= 56, this is only possible on + - The new solution will simply cancel the touch event `e.preventDefault()`. However, due to changes in Chrome >= 56, this is only possible on non-passive event handlers. To fix this, we now add/remove the touchEvent on lifecycle events rather than using React's event system. - [#465](https://github.com/mzabriskie/react-draggable/pull/465) - Upgrade devDeps and fix security warnings. None of them actually applied to this module. @@ -106,7 +110,7 @@ * **`"module"`**: ES6-compatible build using import/export. This should fix issues like https://github.com/STRML/react-resizable/issues/113 while allowing modern bundlers to consume esm modules in the future. - + No compatibility changes are expected. ### 4.0.3 (Sep 10, 2019) diff --git a/README.md b/README.md index 772ff368..aed795be 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,12 @@ positionOffset: {x: number | string, y: number | string}, // Specifies the scale of the canvas your are dragging this element on. This allows // you to, for example, get the correct drag deltas while you are zoomed in or out via // a transform or matrix in the parent of this element. -scale: number +scale: number, + +// If set to false, the input event will not be default-prevented. +// You should call `.preventDefault() `within the `onStart`, `onDrag`, and `onEnd` event handlers. +// This allows for touch scrolling to work when the event originates on a draggable element. +preventDefault: boolean } ``` @@ -321,7 +326,8 @@ on itself and thus must have callbacks attached to be useful. onDrag: DraggableEventHandler, onStop: DraggableEventHandler, onMouseDown: (e: MouseEvent) => void, - scale: number + scale: number, + preventDefault: boolean } ``` diff --git a/example/example.js b/example/example.js index e5effe93..09783ffe 100644 --- a/example/example.js +++ b/example/example.js @@ -150,6 +150,11 @@ class App extends React.Component { Both parent padding and child margin work properly. + +
+ I don't prevent touches from scrolling the container. +
+
diff --git a/lib/DraggableCore.js b/lib/DraggableCore.js index c5a3da8f..4961a2cf 100644 --- a/lib/DraggableCore.js +++ b/lib/DraggableCore.js @@ -55,6 +55,7 @@ export type DraggableCoreDefaultProps = { onDrag: DraggableEventHandler, onStop: DraggableEventHandler, onMouseDown: (e: MouseEvent) => void, + preventDefault: true, scale: number, }; @@ -65,6 +66,7 @@ export type DraggableCoreProps = { offsetParent: HTMLElement, grid: [number, number], handle: string, + preventDefault: boolean, nodeRef?: ?React.ElementRef, }; @@ -208,6 +210,8 @@ export default class DraggableCore extends React.Component ); - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); it('should throw when setting className', function () { @@ -249,7 +249,7 @@ describe('react-draggable', function () { ); const node = ReactDOM.findDOMNode(drag); - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); const style = node.getAttribute('style'); assert(dragged === true); @@ -265,7 +265,7 @@ describe('react-draggable', function () { ); const node = ReactDOM.findDOMNode(drag); - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); const style = node.getAttribute('style'); assert(dragged === true); @@ -281,7 +281,7 @@ describe('react-draggable', function () { ); const node = ReactDOM.findDOMNode(drag); - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); const style = node.getAttribute('style'); assert(dragged === true); @@ -297,7 +297,7 @@ describe('react-draggable', function () { ); const node = ReactDOM.findDOMNode(drag); - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); const style = node.getAttribute('style'); assert(dragged === true); @@ -313,7 +313,7 @@ describe('react-draggable', function () { ); const node = ReactDOM.findDOMNode(drag); - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); const style = node.getAttribute('style'); assert(dragged === true); @@ -348,7 +348,7 @@ describe('react-draggable', function () { ); const node = ReactDOM.findDOMNode(drag); - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); const transform = node.getAttribute('transform'); assert(transform.indexOf('translate(100,100)') >= 0); @@ -461,7 +461,7 @@ describe('react-draggable', function () { const body = iframeDoc.body; const node = body.querySelector('.react-draggable'); if (!node) return setTimeout(checkIframe, 50); - simulateMovementFromTo(node, 0, 0, 100, 100); + simulateMouseFromTo(node, 0, 0, 100, 100); const style = node.getAttribute('style'); assert(dragged === true); @@ -711,6 +711,48 @@ describe('react-draggable', function () { done(); }, 50); }); + + // it('should allow touch scrolling parent', function (done) { + // let dragCalled = false; + // function onDrag(event, data) { + // dragCalled = true; + // // assert(data.x === 100); + // // assert(data.y === 100); + // // assert(data.deltaX === 100); + // // assert(data.deltaY === 100); + // } + + // const scrollParent = fragmentFromString(` + //
+ //
+ //
+ //
+ // `); + + // drag = TestUtils.renderIntoDocument( + // + //
+ // + // ); + // const node = ReactDOM.findDOMNode(drag); + + // transplantNodeInto(node, scrollParent, (f) => f.children[0]); + + // const scrollParentNode = ReactDOM.findDOMNode(scrollParent); + + // simulateTouchFromTo(node, 200, 200, 100, 100); + + // setTimeout(() => { + // console.log(node); + // assert(dragCalled, 'onDrag was not called'); + // assert(scrollParentNode.scrollTop !== 0 && scrollParentNode.scrollLeft !== 0, 'parent didn\'t scroll on touch'); + // assert(scrollParentNode.scrollTop === 100, 'parent vertical scroll is off'); + // assert(scrollParentNode.scrollLeft === 100, 'parent horizontal scroll is off'); + // // cleanup + // document.body.removeChild(scrollParent); + // done(); + // }, 50); + // }); }); describe('draggable callbacks', function () { @@ -729,7 +771,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); it('should call back with correct dom node with nodeRef', function () { @@ -748,7 +790,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); it('should call back on drag, with values within the defined bounds', function(){ @@ -765,7 +807,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); @@ -782,7 +824,7 @@ describe('react-draggable', function () { ); - simulateMovementFromTo(drag, 200, 200, 300, 300); + simulateMouseFromTo(drag, 200, 200, 300, 300); }); it('should call back with correct position when parent element is 2x scaled', function() { @@ -801,7 +843,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); it('should call back with correct position when parent element is 0.5x scaled', function() { @@ -820,7 +862,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); it('should not throw an error if unmounted during a callback', function () { @@ -847,7 +889,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(dragRef.current, 0, 0, 100, 100); + simulateMouseFromTo(dragRef.current, 0, 0, 100, 100); // ok, was a setstate warning thrown? // Assert unmounted @@ -872,7 +914,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); it('should call back with correct position when parent element is 2x scaled', function() { @@ -891,7 +933,7 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); it('should call back with correct position when parent element is 0.5x scaled', function() { @@ -910,11 +952,10 @@ describe('react-draggable', function () { ); // (element, fromX, fromY, toX, toY) - simulateMovementFromTo(drag, 0, 0, 100, 100); + simulateMouseFromTo(drag, 0, 0, 100, 100); }); }); - describe('validation', function () { it('should result with invariant when there isn\'t a child', function () { const renderer = new ShallowRenderer(); @@ -951,15 +992,52 @@ function mouseMove(x, y, node) { return evt; } +function createClientXY(x, y) { + return { clientX: x, clientY: y }; +} + +function touchMove(x, y, node) { + const touchObj = new Touch({ + identifier: Date.now(), + target: node, + clientX: x, + clientY: y, + radiusX: 2.5, + radiusY: 2.5, + rotationAngle: 10, + force: 0.5, + }); + + const touchEvent = new TouchEvent('touchmove', { + cancelable: true, + bubbles: true, + touches: [touchObj], + targetTouches: [], + changedTouches: [touchObj], + shiftKey: true, + }); + + node.dispatchEvent(touchEvent); +} + -function simulateMovementFromTo(drag, fromX, fromY, toX, toY) { +function simulateMouseFromTo(drag, fromX, fromY, toX, toY) { const node = ReactDOM.findDOMNode(drag); - TestUtils.Simulate.mouseDown(node, {clientX: fromX, clientY: fromY}); + TestUtils.Simulate.mouseDown(node, createClientXY(fromX, fromY)); mouseMove(toX, toY, node); TestUtils.Simulate.mouseUp(node); } +// // Does not work, cannot figure out how to correctly simulate touches +// function simulateTouchFromTo(drag, fromX, fromY, toX, toY) { +// const node = ReactDOM.findDOMNode(drag); + +// TestUtils.Simulate.touchStart(node, { touches: [createClientXY(fromX, fromY)] }); +// touchMove(toX, toY, node); +// TestUtils.Simulate.touchEnd(node, { touches: [createClientXY(toX, toY)], changedTouches: [createClientXY(toX, toY)]}); +// } + function fragmentFromString(strHTML) { var temp = document.createElement('div'); temp.innerHTML = strHTML;