+
+ );
+ }
}
-ReactDOM.render(, document.body);
+ReactDOM.render(, document.body);
```
### Draggable API
@@ -134,9 +131,10 @@ The `` component transparently adds draggability to its children.
For the `` component to correctly attach itself to its child, the child element must provide support
for the following props:
-- `style` is used to give the transform css to the child.
-- `className` is used to apply the proper classes to the object being dragged.
-- `onMouseDown`, `onMouseUp`, `onTouchStart`, and `onTouchEnd` are used to keep track of dragging state.
+
+- `style` is used to give the transform css to the child.
+- `className` is used to apply the proper classes to the object being dragged.
+- `onMouseDown`, `onMouseUp`, `onTouchStart`, and `onTouchEnd` are used to keep track of dragging state.
React.DOM elements support the above properties by default, so you may use those elements as children without
any changes. If you wish to use a React component you created, you'll need to be sure to
@@ -168,7 +166,7 @@ allowAnyClick: boolean,
// If set to `true`, the 'touchstart' event will not be prevented,
// which will allow scrolling inside containers. We recommend
// using the 'handle' / 'cancel' props when possible instead of enabling this.
-//
+//
// See https://github.com/react-grid-layout/react-draggable/issues/728
allowMobileScroll: boolean,
@@ -257,7 +255,7 @@ onStop: DraggableEventHandler,
// pointing to the actual child DOM node and not a custom component.
//
// For rich components, you need to both forward the ref *and props* to the underlying DOM
-// element. Props must be forwarded so that DOM event handlers can be attached.
+// element. Props must be forwarded so that DOM event handlers can be attached.
// For example:
//
// const Component1 = React.forwardRef(function (props, ref) {
@@ -288,15 +286,17 @@ 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,
+
+// The delay is in milliseconds and defaults to 0. If you set this to a value greater than 0,
+// the drag will not start until the specified delay has passed after the initial touchstart event.
+mobileDragDelay: number | undefined
}
```
-
Note that sending `className`, `style`, or `transform` as properties will error - set them on the child element
directly.
-
## Controlled vs. Uncontrolled
`` is a 'batteries-included' component that manages its own state. If you want to completely
@@ -346,7 +346,8 @@ on itself and thus must have callbacks attached to be useful.
onDrag: DraggableEventHandler,
onStop: DraggableEventHandler,
onMouseDown: (e: MouseEvent) => void,
- scale: number
+ scale: number,
+ mobileDragDelay: number,
}
```
@@ -356,24 +357,24 @@ to set actual positions on ``.
Drag callbacks (`onStart`, `onDrag`, `onStop`) are called with the [same arguments as ``](#draggable-api).
-----
+---
### Contributing
-- Fork the project
-- Run the project in development mode: `$ npm run dev`
-- Make changes.
-- Add appropriate tests
-- `$ npm test`
-- If tests don't pass, make them pass.
-- Update README with appropriate docs.
-- Commit and PR
+- Fork the project
+- Run the project in development mode: `$ npm run dev`
+- Make changes.
+- Add appropriate tests
+- `$ npm test`
+- If tests don't pass, make them pass.
+- Update README with appropriate docs.
+- Commit and PR
### Release checklist
-- Update CHANGELOG
-- `make release-patch`, `make release-minor`, or `make-release-major`
-- `make publish`
+- Update CHANGELOG
+- `make release-patch`, `make release-minor`, or `make-release-major`
+- `make publish`
### License
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 11cf50d9..584c54c7 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,11 +1,11 @@
-import { defineConfig, globalIgnores } from "eslint/config";
-import react from "eslint-plugin-react";
-import globals from "globals";
-import babelParser from "@babel/eslint-parser";
-import path from "node:path";
-import { fileURLToPath } from "node:url";
-import js from "@eslint/js";
-import { FlatCompat } from "@eslint/eslintrc";
+import { defineConfig, globalIgnores } from 'eslint/config';
+import react from 'eslint-plugin-react';
+import globals from 'globals';
+import babelParser from '@babel/eslint-parser';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import js from '@eslint/js';
+import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -15,8 +15,8 @@ const compat = new FlatCompat({
allConfig: js.configs.all
});
-export default defineConfig([globalIgnores(["build/**/*.js"]), {
- extends: compat.extends("eslint:recommended"),
+export default defineConfig([globalIgnores(['build/**/*.js']), {
+ extends: compat.extends('eslint:recommended'),
plugins: {
react,
@@ -39,20 +39,20 @@ export default defineConfig([globalIgnores(["build/**/*.js"]), {
rules: {
strict: 0,
- quotes: [1, "single"],
- curly: [1, "multi-line"],
+ quotes: [1, 'single'],
+ curly: [1, 'multi-line'],
camelcase: 0,
- "comma-dangle": 0,
- "no-console": 2,
- "no-use-before-define": [1, "nofunc"],
- "no-underscore-dangle": 0,
+ 'comma-dangle': 0,
+ 'no-console': 2,
+ 'no-use-before-define': [1, 'nofunc'],
+ 'no-underscore-dangle': 0,
- "no-unused-vars": [1, {
+ 'no-unused-vars': [1, {
ignoreRestSiblings: true,
}],
- "new-cap": 0,
- "prefer-const": 1,
+ 'new-cap': 0,
+ 'prefer-const': 1,
semi: 1,
},
}]);
\ No newline at end of file
diff --git a/lib/Draggable.js b/lib/Draggable.js
index 1ce95ee1..094cce8c 100644
--- a/lib/Draggable.js
+++ b/lib/Draggable.js
@@ -3,40 +3,52 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { clsx } from 'clsx';
-import {createCSSTransform, createSVGTransform} from './utils/domFns';
-import {canDragX, canDragY, createDraggableData, getBoundPosition} from './utils/positionFns';
-import {dontSetMe} from './utils/shims';
+import { createCSSTransform, createSVGTransform } from './utils/domFns';
+import {
+ canDragX,
+ canDragY,
+ createDraggableData,
+ getBoundPosition,
+} from './utils/positionFns';
+import { dontSetMe } from './utils/shims';
import DraggableCore from './DraggableCore';
-import type {ControlPosition, PositionOffsetControlPosition, DraggableCoreProps, DraggableCoreDefaultProps} from './DraggableCore';
+import type {
+ ControlPosition,
+ PositionOffsetControlPosition,
+ DraggableCoreProps,
+ DraggableCoreDefaultProps,
+} from './DraggableCore';
import log from './utils/log';
-import type {Bounds, DraggableEventHandler} from './utils/types';
-import type {Element as ReactElement} from 'react';
+import type { Bounds, DraggableEventHandler } from './utils/types';
+import type { Element as ReactElement } from 'react';
type DraggableState = {
- dragging: boolean,
- dragged: boolean,
- x: number, y: number,
- slackX: number, slackY: number,
- isElementSVG: boolean,
- prevPropsPosition: ?ControlPosition,
+ dragging: boolean,
+ dragged: boolean,
+ x: number,
+ y: number,
+ slackX: number,
+ slackY: number,
+ isElementSVG: boolean,
+ prevPropsPosition: ?ControlPosition,
};
export type DraggableDefaultProps = {
- ...DraggableCoreDefaultProps,
- axis: 'both' | 'x' | 'y' | 'none',
- bounds: Bounds | string | false,
- defaultClassName: string,
- defaultClassNameDragging: string,
- defaultClassNameDragged: string,
- defaultPosition: ControlPosition,
- scale: number,
+ ...DraggableCoreDefaultProps,
+ axis: 'both' | 'x' | 'y' | 'none',
+ bounds: Bounds | string | false,
+ defaultClassName: string,
+ defaultClassNameDragging: string,
+ defaultClassNameDragged: string,
+ defaultPosition: ControlPosition,
+ scale: number,
};
export type DraggableProps = {
- ...DraggableCoreProps,
- ...DraggableDefaultProps,
- positionOffset: PositionOffsetControlPosition,
- position: ControlPosition,
+ ...DraggableCoreProps,
+ ...DraggableDefaultProps,
+ positionOffset: PositionOffsetControlPosition,
+ position: ControlPosition,
};
//
@@ -44,358 +56,384 @@ export type DraggableProps = {
//
class Draggable extends React.Component {
+ static displayName: ?string = 'Draggable';
+
+ static propTypes: DraggableProps = {
+ // Accepts all props accepts.
+ ...DraggableCore.propTypes,
+
+ /**
+ * `axis` determines which axis the draggable can move.
+ *
+ * Note that all callbacks will still return data as normal. This only
+ * controls flushing to the DOM.
+ *
+ * 'both' allows movement horizontally and vertically.
+ * 'x' limits movement to horizontal axis.
+ * 'y' limits movement to vertical axis.
+ * 'none' limits all movement.
+ *
+ * Defaults to 'both'.
+ */
+ axis: PropTypes.oneOf(['both', 'x', 'y', 'none']),
+
+ /**
+ * `bounds` determines the range of movement available to the element.
+ * Available values are:
+ *
+ * 'parent' restricts movement within the Draggable's parent node.
+ *
+ * Alternatively, pass an object with the following properties, all of which are optional:
+ *
+ * {left: LEFT_BOUND, right: RIGHT_BOUND, bottom: BOTTOM_BOUND, top: TOP_BOUND}
+ *
+ * All values are in px.
+ *
+ * Example:
+ *
+ * ```jsx
+ * let App = React.createClass({
+ * render: function () {
+ * return (
+ *
+ *
Content
+ *
+ * );
+ * }
+ * });
+ * ```
+ */
+ bounds: PropTypes.oneOfType([
+ PropTypes.shape({
+ left: PropTypes.number,
+ right: PropTypes.number,
+ top: PropTypes.number,
+ bottom: PropTypes.number,
+ }),
+ PropTypes.string,
+ PropTypes.oneOf([false]),
+ ]),
+
+ defaultClassName: PropTypes.string,
+ defaultClassNameDragging: PropTypes.string,
+ defaultClassNameDragged: PropTypes.string,
+
+ /**
+ * `defaultPosition` specifies the x and y that the dragged item should start at
+ *
+ * Example:
+ *
+ * ```jsx
+ * let App = React.createClass({
+ * render: function () {
+ * return (
+ *
+ *
I start with transformX: 25px and transformY: 25px;
+ *
+ * );
+ * }
+ * });
+ * ```
+ */
+ defaultPosition: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number,
+ }),
+ positionOffset: PropTypes.shape({
+ x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ }),
+
+ /**
+ * `position`, if present, defines the current position of the element.
+ *
+ * This is similar to how form elements in React work - if no `position` is supplied, the component
+ * is uncontrolled.
+ *
+ * Example:
+ *
+ * ```jsx
+ * let App = React.createClass({
+ * render: function () {
+ * return (
+ *
+ *
I start with transformX: 25px and transformY: 25px;
+ *
+ * );
+ * }
+ * });
+ * ```
+ */
+ position: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number,
+ }),
+
+ /**
+ * These properties should be defined on the child, not here.
+ */
+ className: dontSetMe,
+ style: dontSetMe,
+ transform: dontSetMe,
+ };
+
+ static defaultProps: DraggableDefaultProps = {
+ ...DraggableCore.defaultProps,
+ axis: 'both',
+ bounds: false,
+ defaultClassName: 'react-draggable',
+ defaultClassNameDragging: 'react-draggable-dragging',
+ defaultClassNameDragged: 'react-draggable-dragged',
+ defaultPosition: { x: 0, y: 0 },
+ scale: 1,
+ };
- static displayName: ?string = 'Draggable';
-
- static propTypes: DraggableProps = {
- // Accepts all props accepts.
- ...DraggableCore.propTypes,
-
- /**
- * `axis` determines which axis the draggable can move.
- *
- * Note that all callbacks will still return data as normal. This only
- * controls flushing to the DOM.
- *
- * 'both' allows movement horizontally and vertically.
- * 'x' limits movement to horizontal axis.
- * 'y' limits movement to vertical axis.
- * 'none' limits all movement.
- *
- * Defaults to 'both'.
- */
- axis: PropTypes.oneOf(['both', 'x', 'y', 'none']),
-
- /**
- * `bounds` determines the range of movement available to the element.
- * Available values are:
- *
- * 'parent' restricts movement within the Draggable's parent node.
- *
- * Alternatively, pass an object with the following properties, all of which are optional:
- *
- * {left: LEFT_BOUND, right: RIGHT_BOUND, bottom: BOTTOM_BOUND, top: TOP_BOUND}
- *
- * All values are in px.
- *
- * Example:
- *
- * ```jsx
- * let App = React.createClass({
- * render: function () {
- * return (
- *
- *
Content
- *
- * );
- * }
- * });
- * ```
- */
- bounds: PropTypes.oneOfType([
- PropTypes.shape({
- left: PropTypes.number,
- right: PropTypes.number,
- top: PropTypes.number,
- bottom: PropTypes.number
- }),
- PropTypes.string,
- PropTypes.oneOf([false])
- ]),
-
- defaultClassName: PropTypes.string,
- defaultClassNameDragging: PropTypes.string,
- defaultClassNameDragged: PropTypes.string,
-
- /**
- * `defaultPosition` specifies the x and y that the dragged item should start at
- *
- * Example:
- *
- * ```jsx
- * let App = React.createClass({
- * render: function () {
- * return (
- *
- *
I start with transformX: 25px and transformY: 25px;
- *
- * );
- * }
- * });
- * ```
- */
- defaultPosition: PropTypes.shape({
- x: PropTypes.number,
- y: PropTypes.number
- }),
- positionOffset: PropTypes.shape({
- x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- y: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
- }),
-
- /**
- * `position`, if present, defines the current position of the element.
- *
- * This is similar to how form elements in React work - if no `position` is supplied, the component
- * is uncontrolled.
- *
- * Example:
- *
- * ```jsx
- * let App = React.createClass({
- * render: function () {
- * return (
- *
- *
I start with transformX: 25px and transformY: 25px;
- *
- * );
- * }
- * });
- * ```
- */
- position: PropTypes.shape({
- x: PropTypes.number,
- y: PropTypes.number
- }),
-
- /**
- * These properties should be defined on the child, not here.
- */
- className: dontSetMe,
- style: dontSetMe,
- transform: dontSetMe
- };
-
- static defaultProps: DraggableDefaultProps = {
- ...DraggableCore.defaultProps,
- axis: 'both',
- bounds: false,
- defaultClassName: 'react-draggable',
- defaultClassNameDragging: 'react-draggable-dragging',
- defaultClassNameDragged: 'react-draggable-dragged',
- defaultPosition: {x: 0, y: 0},
- scale: 1
- };
-
- // React 16.3+
- // Arity (props, state)
- static getDerivedStateFromProps({position}: DraggableProps, {prevPropsPosition}: DraggableState): ?Partial {
- // Set x/y if a new position is provided in props that is different than the previous.
- if (
- position &&
- (!prevPropsPosition ||
- position.x !== prevPropsPosition.x || position.y !== prevPropsPosition.y
- )
- ) {
- log('Draggable: getDerivedStateFromProps %j', {position, prevPropsPosition});
- return {
- x: position.x,
- y: position.y,
- prevPropsPosition: {...position}
- };
+ // React 16.3+
+ // Arity (props, state)
+ static getDerivedStateFromProps(
+ { position }: DraggableProps,
+ { prevPropsPosition }: DraggableState
+ ): ?Partial {
+ // Set x/y if a new position is provided in props that is different than the previous.
+ if (
+ position &&
+ (!prevPropsPosition ||
+ position.x !== prevPropsPosition.x ||
+ position.y !== prevPropsPosition.y)
+ ) {
+ log('Draggable: getDerivedStateFromProps %j', {
+ position,
+ prevPropsPosition,
+ });
+ return {
+ x: position.x,
+ y: position.y,
+ prevPropsPosition: { ...position },
+ };
+ }
+ return null;
}
- return null;
- }
- constructor(props: DraggableProps) {
- super(props);
+ constructor(props: DraggableProps) {
+ super(props);
- this.state = {
- // Whether or not we are currently dragging.
- dragging: false,
+ this.state = {
+ // Whether or not we are currently dragging.
+ dragging: false,
- // Whether or not we have been dragged before.
- dragged: false,
+ // Whether or not we have been dragged before.
+ dragged: false,
- // Current transform x and y.
- x: props.position ? props.position.x : props.defaultPosition.x,
- y: props.position ? props.position.y : props.defaultPosition.y,
+ // Current transform x and y.
+ x: props.position ? props.position.x : props.defaultPosition.x,
+ y: props.position ? props.position.y : props.defaultPosition.y,
- prevPropsPosition: {...props.position},
+ prevPropsPosition: { ...props.position },
- // Used for compensating for out-of-bounds drags
- slackX: 0, slackY: 0,
+ // Used for compensating for out-of-bounds drags
+ slackX: 0,
+ slackY: 0,
- // Can only determine if SVG after mounting
- isElementSVG: false
- };
+ // Can only determine if SVG after mounting
+ isElementSVG: false,
+ };
- if (props.position && !(props.onDrag || props.onStop)) {
- // eslint-disable-next-line no-console
- console.warn('A `position` was applied to this , without drag handlers. This will make this ' +
- 'component effectively undraggable. Please attach `onDrag` or `onStop` handlers so you can adjust the ' +
- '`position` of this element.');
+ if (props.position && !(props.onDrag || props.onStop)) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'A `position` was applied to this , without drag handlers. This will make this ' +
+ 'component effectively undraggable. Please attach `onDrag` or `onStop` handlers so you can adjust the ' +
+ '`position` of this element.'
+ );
+ }
}
- }
- componentDidMount() {
- // Check to see if the element passed is an instanceof SVGElement
- if(typeof window.SVGElement !== 'undefined' && this.findDOMNode() instanceof window.SVGElement) {
- this.setState({isElementSVG: true});
+ componentDidMount() {
+ // Check to see if the element passed is an instanceof SVGElement
+ if (
+ typeof window.SVGElement !== 'undefined' &&
+ this.findDOMNode() instanceof window.SVGElement
+ ) {
+ this.setState({ isElementSVG: true });
+ }
}
- }
- componentWillUnmount() {
- if (this.state.dragging) {
- this.setState({dragging: false}); // prevents invariant if unmounted while dragging
+ componentWillUnmount() {
+ if (this.state.dragging) {
+ this.setState({ dragging: false }); // prevents invariant if unmounted while dragging
+ }
}
- }
-
- // React Strict Mode compatibility: if `nodeRef` is passed, we will use it instead of trying to find
- // the underlying DOM node ourselves. See the README for more information.
- findDOMNode(): ?HTMLElement {
- return this.props?.nodeRef?.current ?? ReactDOM.findDOMNode(this);
- }
-
- onDragStart: DraggableEventHandler = (e, coreData) => {
- log('Draggable: onDragStart: %j', coreData);
-
- // Short-circuit if user's callback killed it.
- const shouldStart = this.props.onStart(e, createDraggableData(this, coreData));
- // Kills start event on core as well, so move handlers are never bound.
- if (shouldStart === false) return false;
-
- this.setState({dragging: true, dragged: true});
- };
-
- onDrag: DraggableEventHandler = (e, coreData) => {
- if (!this.state.dragging) return false;
- log('Draggable: onDrag: %j', coreData);
- const uiData = createDraggableData(this, coreData);
-
- const newState = {
- x: uiData.x,
- y: uiData.y,
- slackX: 0,
- slackY: 0,
- };
-
- // Keep within bounds.
- if (this.props.bounds) {
- // Save original x and y.
- const {x, y} = newState;
-
- // Add slack to the values used to calculate bound position. This will ensure that if
- // we start removing slack, the element won't react to it right away until it's been
- // completely removed.
- newState.x += this.state.slackX;
- newState.y += this.state.slackY;
-
- // Get bound position. This will ceil/floor the x and y within the boundaries.
- const [newStateX, newStateY] = getBoundPosition(this, newState.x, newState.y);
- newState.x = newStateX;
- newState.y = newStateY;
-
- // Recalculate slack by noting how much was shaved by the boundPosition handler.
- newState.slackX = this.state.slackX + (x - newState.x);
- newState.slackY = this.state.slackY + (y - newState.y);
-
- // Update the event we fire to reflect what really happened after bounds took effect.
- uiData.x = newState.x;
- uiData.y = newState.y;
- uiData.deltaX = newState.x - this.state.x;
- uiData.deltaY = newState.y - this.state.y;
+ // React Strict Mode compatibility: if `nodeRef` is passed, we will use it instead of trying to find
+ // the underlying DOM node ourselves. See the README for more information.
+ findDOMNode(): ?HTMLElement {
+ return this.props?.nodeRef?.current ?? ReactDOM.findDOMNode(this);
}
- // Short-circuit if user's callback killed it.
- const shouldUpdate = this.props.onDrag(e, uiData);
- if (shouldUpdate === false) return false;
-
- this.setState(newState);
- };
-
- onDragStop: DraggableEventHandler = (e, coreData) => {
- if (!this.state.dragging) return false;
+ onDragStart: DraggableEventHandler = (e, coreData) => {
+ log('Draggable: onDragStart: %j', coreData);
- // Short-circuit if user's callback killed it.
- const shouldContinue = this.props.onStop(e, createDraggableData(this, coreData));
- if (shouldContinue === false) return false;
+ // Short-circuit if user's callback killed it.
+ const shouldStart = this.props.onStart(
+ e,
+ createDraggableData(this, coreData)
+ );
+ // Kills start event on core as well, so move handlers are never bound.
+ if (shouldStart === false) return false;
- log('Draggable: onDragStop: %j', coreData);
-
- const newState: Partial = {
- dragging: false,
- slackX: 0,
- slackY: 0
+ this.setState({ dragging: true, dragged: true });
};
- // If this is a controlled component, the result of this operation will be to
- // revert back to the old position. We expect a handler on `onDragStop`, at the least.
- const controlled = Boolean(this.props.position);
- if (controlled) {
- const {x, y} = this.props.position;
- newState.x = x;
- newState.y = y;
- }
+ onDrag: DraggableEventHandler = (e, coreData) => {
+ if (!this.state.dragging) return false;
+ log('Draggable: onDrag: %j', coreData);
+
+ const uiData = createDraggableData(this, coreData);
+
+ const newState = {
+ x: uiData.x,
+ y: uiData.y,
+ slackX: 0,
+ slackY: 0,
+ };
+
+ // Keep within bounds.
+ if (this.props.bounds) {
+ // Save original x and y.
+ const { x, y } = newState;
+
+ // Add slack to the values used to calculate bound position. This will ensure that if
+ // we start removing slack, the element won't react to it right away until it's been
+ // completely removed.
+ newState.x += this.state.slackX;
+ newState.y += this.state.slackY;
+
+ // Get bound position. This will ceil/floor the x and y within the boundaries.
+ const [newStateX, newStateY] = getBoundPosition(
+ this,
+ newState.x,
+ newState.y
+ );
+ newState.x = newStateX;
+ newState.y = newStateY;
+
+ // Recalculate slack by noting how much was shaved by the boundPosition handler.
+ newState.slackX = this.state.slackX + (x - newState.x);
+ newState.slackY = this.state.slackY + (y - newState.y);
+
+ // Update the event we fire to reflect what really happened after bounds took effect.
+ uiData.x = newState.x;
+ uiData.y = newState.y;
+ uiData.deltaX = newState.x - this.state.x;
+ uiData.deltaY = newState.y - this.state.y;
+ }
+
+ // Short-circuit if user's callback killed it.
+ const shouldUpdate = this.props.onDrag(e, uiData);
+ if (shouldUpdate === false) return false;
+
+ this.setState(newState);
+ };
- this.setState(newState);
- };
-
- render(): ReactElement {
- const {
- axis,
- bounds,
- children,
- defaultPosition,
- defaultClassName,
- defaultClassNameDragging,
- defaultClassNameDragged,
- position,
- positionOffset,
- scale,
- ...draggableCoreProps
- } = this.props;
-
- let style = {};
- let svgTransform = null;
-
- // If this is controlled, we don't want to move it - unless it's dragging.
- const controlled = Boolean(position);
- const draggable = !controlled || this.state.dragging;
-
- const validPosition = position || defaultPosition;
- const transformOpts = {
- // Set left if horizontal drag is enabled
- x: canDragX(this) && draggable ?
- this.state.x :
- validPosition.x,
-
- // Set top if vertical drag is enabled
- y: canDragY(this) && draggable ?
- this.state.y :
- validPosition.y
+ onDragStop: DraggableEventHandler = (e, coreData) => {
+ if (!this.state.dragging) return false;
+
+ // Short-circuit if user's callback killed it.
+ const shouldContinue = this.props.onStop(
+ e,
+ createDraggableData(this, coreData)
+ );
+ if (shouldContinue === false) return false;
+
+ log('Draggable: onDragStop: %j', coreData);
+
+ const newState: Partial = {
+ dragging: false,
+ slackX: 0,
+ slackY: 0,
+ };
+
+ // If this is a controlled component, the result of this operation will be to
+ // revert back to the old position. We expect a handler on `onDragStop`, at the least.
+ const controlled = Boolean(this.props.position);
+ if (controlled) {
+ const { x, y } = this.props.position;
+ newState.x = x;
+ newState.y = y;
+ }
+
+ this.setState(newState);
};
- // If this element was SVG, we use the `transform` attribute.
- if (this.state.isElementSVG) {
- svgTransform = createSVGTransform(transformOpts, positionOffset);
- } else {
- // Add a CSS transform to move the element around. This allows us to move the element around
- // without worrying about whether or not it is relatively or absolutely positioned.
- // If the item you are dragging already has a transform set, wrap it in a so
- // has a clean slate.
- style = createCSSTransform(transformOpts, positionOffset);
+ render(): ReactElement {
+ const {
+ axis,
+ bounds,
+ children,
+ defaultPosition,
+ defaultClassName,
+ defaultClassNameDragging,
+ defaultClassNameDragged,
+ position,
+ positionOffset,
+ scale,
+ ...draggableCoreProps
+ } = this.props;
+
+ let style = {};
+ let svgTransform = null;
+
+ // If this is controlled, we don't want to move it - unless it's dragging.
+ const controlled = Boolean(position);
+ const draggable = !controlled || this.state.dragging;
+
+ const validPosition = position || defaultPosition;
+ const transformOpts = {
+ // Set left if horizontal drag is enabled
+ x: canDragX(this) && draggable ? this.state.x : validPosition.x,
+
+ // Set top if vertical drag is enabled
+ y: canDragY(this) && draggable ? this.state.y : validPosition.y,
+ };
+
+ // If this element was SVG, we use the `transform` attribute.
+ if (this.state.isElementSVG) {
+ svgTransform = createSVGTransform(transformOpts, positionOffset);
+ } else {
+ // Add a CSS transform to move the element around. This allows us to move the element around
+ // without worrying about whether or not it is relatively or absolutely positioned.
+ // If the item you are dragging already has a transform set, wrap it in a so
+ // has a clean slate.
+ style = createCSSTransform(transformOpts, positionOffset);
+ }
+
+ // Mark with class while dragging
+ const className = clsx(
+ children.props.className || '',
+ defaultClassName,
+ {
+ [defaultClassNameDragging]: this.state.dragging,
+ [defaultClassNameDragged]: this.state.dragged,
+ }
+ );
+
+ // Reuse the child provided
+ // This makes it flexible to use whatever element is wanted (div, ul, etc)
+ return (
+
+ {React.cloneElement(React.Children.only(children), {
+ className: className,
+ style: { ...children.props.style, ...style },
+ transform: svgTransform,
+ })}
+
+ );
}
-
- // Mark with class while dragging
- const className = clsx((children.props.className || ''), defaultClassName, {
- [defaultClassNameDragging]: this.state.dragging,
- [defaultClassNameDragged]: this.state.dragged
- });
-
- // Reuse the child provided
- // This makes it flexible to use whatever element is wanted (div, ul, etc)
- return (
-
- {React.cloneElement(React.Children.only(children), {
- className: className,
- style: {...children.props.style, ...style},
- transform: svgTransform
- })}
-
- );
- }
}
-export {Draggable as default, DraggableCore};
+export { Draggable as default, DraggableCore };
diff --git a/lib/DraggableCore.js b/lib/DraggableCore.js
index e673e024..a0ec4cbc 100644
--- a/lib/DraggableCore.js
+++ b/lib/DraggableCore.js
@@ -2,64 +2,85 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
-import {matchesSelectorAndParentsTo, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier,
- scheduleRemoveUserSelectStyles} from './utils/domFns';
-import {createCoreData, getControlPosition, snapToGrid} from './utils/positionFns';
-import {dontSetMe} from './utils/shims';
+import {
+ matchesSelectorAndParentsTo,
+ addEvent,
+ removeEvent,
+ addUserSelectStyles,
+ getTouchIdentifier,
+ scheduleRemoveUserSelectStyles,
+} from './utils/domFns';
+import {
+ createCoreData,
+ getControlPosition,
+ snapToGrid,
+} from './utils/positionFns';
+import { dontSetMe } from './utils/shims';
import log from './utils/log';
-import type {EventHandler, MouseTouchEvent} from './utils/types';
-import type {Element as ReactElement} from 'react';
+import type { EventHandler, MouseTouchEvent } from './utils/types';
+import type { Element as ReactElement } from 'react';
// Simple abstraction for dragging events names.
const eventsFor = {
- touch: {
- start: 'touchstart',
- move: 'touchmove',
- stop: 'touchend'
- },
- mouse: {
- start: 'mousedown',
- move: 'mousemove',
- stop: 'mouseup'
- }
+ touch: {
+ start: 'touchstart',
+ move: 'touchmove',
+ stop: 'touchend',
+ },
+ mouse: {
+ start: 'mousedown',
+ move: 'mousemove',
+ stop: 'mouseup',
+ },
};
// Default to mouse events.
let dragEventFor = eventsFor.mouse;
export type DraggableData = {
- node: HTMLElement,
- x: number, y: number,
- deltaX: number, deltaY: number,
- lastX: number, lastY: number,
+ node: HTMLElement,
+ x: number,
+ y: number,
+ deltaX: number,
+ deltaY: number,
+ lastX: number,
+ lastY: number,
};
-export type DraggableEventHandler = (e: MouseEvent, data: DraggableData) => void | false;
+export type DraggableEventHandler = (
+ e: MouseEvent,
+ data: DraggableData
+) => void | false;
-export type ControlPosition = {x: number, y: number};
-export type PositionOffsetControlPosition = {x: number|string, y: number|string};
+export type ControlPosition = { x: number, y: number };
+export type PositionOffsetControlPosition = {
+ x: number | string,
+ y: number | string,
+};
export type DraggableCoreDefaultProps = {
- allowAnyClick: boolean,
- allowMobileScroll: boolean,
- disabled: boolean,
- enableUserSelectHack: boolean,
- onStart: DraggableEventHandler,
- onDrag: DraggableEventHandler,
- onStop: DraggableEventHandler,
- onMouseDown: (e: MouseEvent) => void,
- scale: number,
+ allowAnyClick: boolean,
+ allowMobileScroll: boolean,
+ disabled: boolean,
+ enableUserSelectHack: boolean,
+ onStart: DraggableEventHandler,
+ onDrag: DraggableEventHandler,
+ onStop: DraggableEventHandler,
+ onMouseDown: (e: MouseEvent) => void,
+ scale: number,
+ mobileDragDelay: number,
};
export type DraggableCoreProps = {
- ...DraggableCoreDefaultProps,
- cancel: string,
- children: ReactElement,
- offsetParent: HTMLElement,
- grid: [number, number],
- handle: string,
- nodeRef?: ?React.ElementRef,
+ ...DraggableCoreDefaultProps,
+ cancel: string,
+ children: ReactElement,
+ offsetParent: HTMLElement,
+ grid: [number, number],
+ handle: string,
+ nodeRef?: ?React.ElementRef,
+ mobileDragDelay?: number,
};
//
@@ -70,397 +91,572 @@ export type DraggableCoreProps = {
//
export default class DraggableCore extends React.Component {
-
- static displayName: ?string = 'DraggableCore';
-
- static propTypes: Object = {
- /**
- * `allowAnyClick` allows dragging using any mouse button.
- * By default, we only accept the left button.
- *
- * Defaults to `false`.
- */
- allowAnyClick: PropTypes.bool,
-
- /**
- * `allowMobileScroll` turns off cancellation of the 'touchstart' event
- * on mobile devices. Only enable this if you are having trouble with click
- * events. Prefer using 'handle' / 'cancel' instead.
- *
- * Defaults to `false`.
- */
- allowMobileScroll: PropTypes.bool,
-
- children: PropTypes.node.isRequired,
-
- /**
- * `disabled`, if true, stops the from dragging. All handlers,
- * with the exception of `onMouseDown`, will not fire.
- */
- disabled: PropTypes.bool,
-
- /**
- * By default, we add 'user-select:none' attributes to the document body
- * to prevent ugly text selection during drag. If this is causing problems
- * for your app, set this to `false`.
- */
- enableUserSelectHack: PropTypes.bool,
-
- /**
- * `offsetParent`, if set, uses the passed DOM node to compute drag offsets
- * instead of using the parent node.
- */
- offsetParent: function(props: DraggableCoreProps, propName: $Keys) {
- if (props[propName] && props[propName].nodeType !== 1) {
- throw new Error('Draggable\'s offsetParent must be a DOM Node.');
- }
- },
-
- /**
- * `grid` specifies the x and y that dragging should snap to.
- */
- grid: PropTypes.arrayOf(PropTypes.number),
-
- /**
- * `handle` specifies a selector to be used as the handle that initiates drag.
- *
- * Example:
- *
- * ```jsx
- * let App = React.createClass({
- * render: function () {
- * return (
- *
- *
- *
Click me to drag
- *
This is some other content
- *
- *
- * );
- * }
- * });
- * ```
- */
- handle: PropTypes.string,
-
- /**
- * `cancel` specifies a selector to be used to prevent drag initialization.
- *
- * Example:
- *
- * ```jsx
- * let App = React.createClass({
- * render: function () {
- * return(
- *
- *
- *
You can't drag from here
- *
Dragging here works fine
- *
- *
- * );
- * }
- * });
- * ```
- */
- cancel: PropTypes.string,
-
- /* If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.
- * Unfortunately, in order for to work properly, we need raw access
- * to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`
- * as in this example:
- *
- * function MyComponent() {
- * const nodeRef = React.useRef(null);
- * return (
- *
- *
Example Target
- *
- * );
- * }
- *
- * This can be used for arbitrarily nested components, so long as the ref ends up
- * pointing to the actual child DOM node and not a custom component.
- */
- nodeRef: PropTypes.object,
-
- /**
- * Called when dragging starts.
- * If this function returns the boolean false, dragging will be canceled.
- */
- onStart: PropTypes.func,
-
- /**
- * Called while dragging.
- * If this function returns the boolean false, dragging will be canceled.
- */
- onDrag: PropTypes.func,
-
- /**
- * Called when dragging stops.
- * If this function returns the boolean false, the drag will remain active.
- */
- onStop: PropTypes.func,
-
- /**
- * A workaround option which can be passed if onMouseDown needs to be accessed,
- * since it'll always be blocked (as there is internal use of onMouseDown)
- */
- onMouseDown: PropTypes.func,
-
- /**
- * `scale`, if set, applies scaling while dragging an element
- */
- scale: PropTypes.number,
-
- /**
- * These properties should be defined on the child, not here.
- */
- className: dontSetMe,
- style: dontSetMe,
- transform: dontSetMe
- };
-
- static defaultProps: DraggableCoreDefaultProps = {
- allowAnyClick: false, // by default only accept left click
- allowMobileScroll: false,
- disabled: false,
- enableUserSelectHack: true,
- onStart: function(){},
- onDrag: function(){},
- onStop: function(){},
- onMouseDown: function(){},
- scale: 1,
- };
-
- dragging: boolean = false;
-
- // Used while dragging to determine deltas.
- lastX: number = NaN;
- lastY: number = NaN;
-
- touchIdentifier: ?number = null;
-
- mounted: boolean = false;
-
- componentDidMount() {
- this.mounted = true;
- // Touch handlers must be added with {passive: false} to be cancelable.
- // https://developers.google.com/web/updates/2017/01/scrolling-intervention
- const thisNode = this.findDOMNode();
- if (thisNode) {
- addEvent(thisNode, eventsFor.touch.start, this.onTouchStart, {passive: false});
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- // Remove any leftover event handlers. Remove both touch and mouse handlers in case
- // some browser quirk caused a touch event to fire during a mouse move, or vice versa.
- const thisNode = this.findDOMNode();
- if (thisNode) {
- const {ownerDocument} = thisNode;
- removeEvent(ownerDocument, eventsFor.mouse.move, this.handleDrag);
- removeEvent(ownerDocument, eventsFor.touch.move, this.handleDrag);
- removeEvent(ownerDocument, eventsFor.mouse.stop, this.handleDragStop);
- removeEvent(ownerDocument, eventsFor.touch.stop, this.handleDragStop);
- removeEvent(thisNode, eventsFor.touch.start, this.onTouchStart, {passive: false});
- if (this.props.enableUserSelectHack) scheduleRemoveUserSelectStyles(ownerDocument);
- }
- }
-
- // React Strict Mode compatibility: if `nodeRef` is passed, we will use it instead of trying to find
- // the underlying DOM node ourselves. See the README for more information.
- findDOMNode(): ?HTMLElement {
- return this.props?.nodeRef ? this.props?.nodeRef?.current : ReactDOM.findDOMNode(this);
- }
-
- handleDragStart: EventHandler = (e) => {
- // Make it possible to attach event handlers on top of this one.
- this.props.onMouseDown(e);
-
- // Only accept left-clicks.
- if (!this.props.allowAnyClick && typeof e.button === 'number' && e.button !== 0) return false;
-
- // Get nodes. Be sure to grab relative document (could be iframed)
- const thisNode = this.findDOMNode();
- if (!thisNode || !thisNode.ownerDocument || !thisNode.ownerDocument.body) {
- throw new Error(' not mounted on DragStart!');
+ static displayName: ?string = 'DraggableCore';
+
+ static propTypes: Object = {
+ /**
+ * `allowAnyClick` allows dragging using any mouse button.
+ * By default, we only accept the left button.
+ *
+ * Defaults to `false`.
+ */
+ allowAnyClick: PropTypes.bool,
+
+ /**
+ * `allowMobileScroll` turns off cancellation of the 'touchstart' event
+ * on mobile devices. Only enable this if you are having trouble with click
+ * events. Prefer using 'handle' / 'cancel' instead.
+ *
+ * Defaults to `false`.
+ */
+ allowMobileScroll: PropTypes.bool,
+
+ children: PropTypes.node.isRequired,
+
+ /**
+ * `disabled`, if true, stops the from dragging. All handlers,
+ * with the exception of `onMouseDown`, will not fire.
+ */
+ disabled: PropTypes.bool,
+
+ /**
+ * By default, we add 'user-select:none' attributes to the document body
+ * to prevent ugly text selection during drag. If this is causing problems
+ * for your app, set this to `false`.
+ */
+ enableUserSelectHack: PropTypes.bool,
+
+ /**
+ * `offsetParent`, if set, uses the passed DOM node to compute drag offsets
+ * instead of using the parent node.
+ */
+ offsetParent: function (
+ props: DraggableCoreProps,
+ propName: $Keys
+ ) {
+ if (props[propName] && props[propName].nodeType !== 1) {
+ throw new Error("Draggable's offsetParent must be a DOM Node.");
+ }
+ },
+
+ /**
+ * `grid` specifies the x and y that dragging should snap to.
+ */
+ grid: PropTypes.arrayOf(PropTypes.number),
+
+ /**
+ * `handle` specifies a selector to be used as the handle that initiates drag.
+ *
+ * Example:
+ *
+ * ```jsx
+ * let App = React.createClass({
+ * render: function () {
+ * return (
+ *
+ *
+ *
Click me to drag
+ *
This is some other content
+ *
+ *
+ * );
+ * }
+ * });
+ * ```
+ */
+ handle: PropTypes.string,
+
+ /**
+ * `cancel` specifies a selector to be used to prevent drag initialization.
+ *
+ * Example:
+ *
+ * ```jsx
+ * let App = React.createClass({
+ * render: function () {
+ * return(
+ *
+ *
+ *
You can't drag from here
+ *
Dragging here works fine
+ *
+ *
+ * );
+ * }
+ * });
+ * ```
+ */
+ cancel: PropTypes.string,
+
+ /* If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.
+ * Unfortunately, in order for to work properly, we need raw access
+ * to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`
+ * as in this example:
+ *
+ * function MyComponent() {
+ * const nodeRef = React.useRef(null);
+ * return (
+ *
+ *
Example Target
+ *
+ * );
+ * }
+ *
+ * This can be used for arbitrarily nested components, so long as the ref ends up
+ * pointing to the actual child DOM node and not a custom component.
+ */
+ nodeRef: PropTypes.object,
+
+ /**
+ * Called when dragging starts.
+ * If this function returns the boolean false, dragging will be canceled.
+ */
+ onStart: PropTypes.func,
+
+ /**
+ * Called while dragging.
+ * If this function returns the boolean false, dragging will be canceled.
+ */
+ onDrag: PropTypes.func,
+
+ /**
+ * Called when dragging stops.
+ * If this function returns the boolean false, the drag will remain active.
+ */
+ onStop: PropTypes.func,
+
+ /**
+ * A workaround option which can be passed if onMouseDown needs to be accessed,
+ * since it'll always be blocked (as there is internal use of onMouseDown)
+ */
+ onMouseDown: PropTypes.func,
+
+ /**
+ * `scale`, if set, applies scaling while dragging an element
+ */
+ scale: PropTypes.number,
+
+ /**
+ * Delay on mobile devices before the drag starts.
+ * This is useful to prevent accidental drags when scrolling on touch devices.
+ */
+ mobileDragDelay: PropTypes.number,
+
+ /**
+ * These properties should be defined on the child, not here.
+ */
+ className: dontSetMe,
+ style: dontSetMe,
+ transform: dontSetMe,
+ };
+
+ static defaultProps: DraggableCoreDefaultProps = {
+ allowAnyClick: false, // by default only accept left click
+ allowMobileScroll: false,
+ disabled: false,
+ enableUserSelectHack: true,
+ onStart: function () {},
+ onDrag: function () {},
+ onStop: function () {},
+ onMouseDown: function () {},
+ scale: 1,
+ mobileDragDelay: 250, // 250ms delay on mobile devices
+ };
+
+ dragging: boolean = false;
+
+ dragTimeout: TimeoutID | null = null;
+
+ // Used while dragging to determine deltas.
+ lastX: number = NaN;
+ lastY: number = NaN;
+
+ touchIdentifier: ?number = null;
+
+ mounted: boolean = false;
+
+ componentDidMount() {
+ this.mounted = true;
+ // Touch handlers must be added with {passive: false} to be cancelable.
+ // https://developers.google.com/web/updates/2017/01/scrolling-intervention
+ const thisNode = this.findDOMNode();
+ if (thisNode) {
+ addEvent(thisNode, eventsFor.touch.start, this.onTouchStart, {
+ passive: false,
+ });
+ // Disable context menu for touch elements
+ addEvent(thisNode, 'contextmenu', this.onContextMenu);
+ }
}
- const {ownerDocument} = thisNode;
-
- // Short circuit if handle or cancel prop was provided and selector doesn't match.
- if (this.props.disabled ||
- (!(e.target instanceof ownerDocument.defaultView.Node)) ||
- (this.props.handle && !matchesSelectorAndParentsTo(e.target, this.props.handle, thisNode)) ||
- (this.props.cancel && matchesSelectorAndParentsTo(e.target, this.props.cancel, thisNode))) {
- return;
- }
-
- // Prevent scrolling on mobile devices, like ipad/iphone.
- // Important that this is after handle/cancel.
- if (e.type === 'touchstart' && !this.props.allowMobileScroll) e.preventDefault();
-
- // Set touch identifier in component state if this is a touch event. This allows us to
- // distinguish between individual touches on multitouch screens by identifying which
- // touchpoint was set to this element.
- const touchIdentifier = getTouchIdentifier(e);
- this.touchIdentifier = touchIdentifier;
-
- // Get the current drag point from the event. This is used as the offset.
- const position = getControlPosition(e, touchIdentifier, this);
- if (position == null) return; // not possible but satisfies flow
- const {x, y} = position;
-
- // Create an event object with all the data parents need to make a decision here.
- const coreEvent = createCoreData(this, x, y);
-
- log('DraggableCore: handleDragStart: %j', coreEvent);
-
- // Call event handler. If it returns explicit false, cancel.
- log('calling', this.props.onStart);
- const shouldUpdate = this.props.onStart(e, coreEvent);
- if (shouldUpdate === false || this.mounted === false) return;
-
- // Add a style to the body to disable user-select. This prevents text from
- // being selected all over the page.
- if (this.props.enableUserSelectHack) addUserSelectStyles(ownerDocument);
-
- // Initiate dragging. Set the current x and y as offsets
- // so we know how much we've moved during the drag. This allows us
- // to drag elements around even if they have been moved, without issue.
- this.dragging = true;
- this.lastX = x;
- this.lastY = y;
-
- // Add events to the document directly so we catch when the user's mouse/touch moves outside of
- // this element. We use different events depending on whether or not we have detected that this
- // is a touch-capable device.
- addEvent(ownerDocument, dragEventFor.move, this.handleDrag);
- addEvent(ownerDocument, dragEventFor.stop, this.handleDragStop);
- };
-
- handleDrag: EventHandler = (e) => {
-
- // Get the current drag point from the event. This is used as the offset.
- const position = getControlPosition(e, this.touchIdentifier, this);
- if (position == null) return;
- let {x, y} = position;
-
- // Snap to grid if prop has been provided
- if (Array.isArray(this.props.grid)) {
- let deltaX = x - this.lastX, deltaY = y - this.lastY;
- [deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);
- if (!deltaX && !deltaY) return; // skip useless drag
- x = this.lastX + deltaX, y = this.lastY + deltaY;
- }
-
- const coreEvent = createCoreData(this, x, y);
-
- log('DraggableCore: handleDrag: %j', coreEvent);
-
- // Call event handler. If it returns explicit false, trigger end.
- const shouldUpdate = this.props.onDrag(e, coreEvent);
- if (shouldUpdate === false || this.mounted === false) {
- try {
- // $FlowIgnore
- this.handleDragStop(new MouseEvent('mouseup'));
- } catch (err) {
- // Old browsers
- const event = ((document.createEvent('MouseEvents'): any): MouseTouchEvent);
- // I see why this insanity was deprecated
- // $FlowIgnore
- event.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- this.handleDragStop(event);
- }
- return;
- }
-
- this.lastX = x;
- this.lastY = y;
- };
- handleDragStop: EventHandler = (e) => {
- if (!this.dragging) return;
-
- const position = getControlPosition(e, this.touchIdentifier, this);
- if (position == null) return;
- let {x, y} = position;
-
- // Snap to grid if prop has been provided
- if (Array.isArray(this.props.grid)) {
- let deltaX = x - this.lastX || 0;
- let deltaY = y - this.lastY || 0;
- [deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);
- x = this.lastX + deltaX, y = this.lastY + deltaY;
+ componentWillUnmount() {
+ this.mounted = false;
+ // Remove any leftover event handlers. Remove both touch and mouse handlers in case
+ // some browser quirk caused a touch event to fire during a mouse move, or vice versa.
+ const thisNode = this.findDOMNode();
+ if (thisNode) {
+ const { ownerDocument } = thisNode;
+ removeEvent(ownerDocument, eventsFor.mouse.move, this.handleDrag);
+ removeEvent(ownerDocument, eventsFor.touch.move, this.handleDrag);
+ removeEvent(
+ ownerDocument,
+ eventsFor.mouse.stop,
+ this.handleDragStop
+ );
+ removeEvent(
+ ownerDocument,
+ eventsFor.touch.stop,
+ this.handleDragStop
+ );
+ removeEvent(thisNode, eventsFor.touch.start, this.onTouchStart, {
+ passive: false,
+ });
+ // Remove context menu event listener
+ removeEvent(thisNode, 'contextmenu', this.onContextMenu);
+ if (this.props.enableUserSelectHack) {
+ scheduleRemoveUserSelectStyles(ownerDocument);
+ }
+ }
}
- const coreEvent = createCoreData(this, x, y);
-
- // Call event handler
- const shouldContinue = this.props.onStop(e, coreEvent);
- if (shouldContinue === false || this.mounted === false) return false;
-
- const thisNode = this.findDOMNode();
- if (thisNode) {
- // Remove user-select hack
- if (this.props.enableUserSelectHack) scheduleRemoveUserSelectStyles(thisNode.ownerDocument);
+ // React Strict Mode compatibility: if `nodeRef` is passed, we will use it instead of trying to find
+ // the underlying DOM node ourselves. See the README for more information.
+ findDOMNode(): ?HTMLElement {
+ return this.props?.nodeRef
+ ? this.props?.nodeRef?.current
+ : ReactDOM.findDOMNode(this);
}
- log('DraggableCore: handleDragStop: %j', coreEvent);
-
- // Reset the el.
- this.dragging = false;
- this.lastX = NaN;
- this.lastY = NaN;
-
- if (thisNode) {
- // Remove event handlers
- log('DraggableCore: Removing handlers');
- removeEvent(thisNode.ownerDocument, dragEventFor.move, this.handleDrag);
- removeEvent(thisNode.ownerDocument, dragEventFor.stop, this.handleDragStop);
+ handleDragStart: EventHandler = (e) => {
+ // Make it possible to attach event handlers on top of this one.
+ this.props.onMouseDown(e);
+
+ // Only accept left-clicks.
+ if (
+ !this.props.allowAnyClick &&
+ typeof e.button === 'number' &&
+ e.button !== 0
+ ) {
+ return false;
+ }
+
+ // Get nodes. Be sure to grab relative document (could be iframed)
+ const thisNode = this.findDOMNode();
+ if (
+ !thisNode ||
+ !thisNode.ownerDocument ||
+ !thisNode.ownerDocument.body
+ ) {
+ throw new Error(' not mounted on DragStart!');
+ }
+ const { ownerDocument } = thisNode;
+
+ // Short circuit if handle or cancel prop was provided and selector doesn't match.
+ if (
+ this.props.disabled ||
+ !(e.target instanceof ownerDocument.defaultView.Node) ||
+ (this.props.handle &&
+ !matchesSelectorAndParentsTo(
+ e.target,
+ this.props.handle,
+ thisNode
+ )) ||
+ (this.props.cancel &&
+ matchesSelectorAndParentsTo(
+ e.target,
+ this.props.cancel,
+ thisNode
+ ))
+ ) {
+ return;
+ }
+
+ // Prevent scrolling on mobile devices, like iPad/iPhone
+ if (e.type === 'touchstart' && !this.props.allowMobileScroll) {
+ e.preventDefault();
+ }
+
+ // Set touch identifier
+ const touchIdentifier = getTouchIdentifier(e);
+ this.touchIdentifier = touchIdentifier;
+
+ // Get the current drag point
+ const position = getControlPosition(e, touchIdentifier, this);
+ if (position == null) return;
+ const { x: initialX, y: initialY } = position;
+
+ const coreEvent = createCoreData(this, initialX, initialY);
+ log('DraggableCore: handleDragStart: %j', coreEvent);
+
+ const startDragging = () => {
+ log('calling', this.props.onStart);
+ const shouldUpdate = this.props.onStart(e, coreEvent);
+ if (shouldUpdate === false || this.mounted === false) return;
+
+ // Prevent text selection while dragging
+ if (this.props.enableUserSelectHack) {
+ addUserSelectStyles(ownerDocument);
+ }
+
+ // Prevent scrolling during drag
+ addEvent(ownerDocument, 'touchmove', this.preventScroll, {
+ passive: false,
+ });
+
+ this.dragging = true;
+ this.lastX = initialX; // Update lastX and lastY only after drag starts
+ this.lastY = initialY;
+
+ // Add global move/stop listeners
+ addEvent(ownerDocument, dragEventFor.move, this.handleDrag);
+ addEvent(ownerDocument, dragEventFor.stop, this.handleDragStop);
+ };
+
+ // If it's a touch — delay start
+ if (
+ this.props.allowMobileScroll &&
+ this.props.mobileDragDelay &&
+ this.props.mobileDragDelay > 0 &&
+ e.type === 'touchstart'
+ ) {
+ clearTimeout(this.dragTimeout);
+ if (typeof e.persist === 'function') e.persist();
+
+ addEvent(ownerDocument, 'contextmenu', this.onContextMenu);
+
+ const onTouchEnd = () => {
+ log('DraggableCore: touchend detected, canceling drag timeout');
+ clearTimeout(this.dragTimeout);
+ removeEvent(ownerDocument, eventsFor.touch.stop, onTouchEnd);
+ removeEvent(ownerDocument, 'contextmenu', this.onContextMenu);
+ };
+
+ const onTouchMove = (moveEvent: MouseTouchEvent) => {
+ const currentPosition = getControlPosition(
+ moveEvent,
+ touchIdentifier,
+ this
+ );
+ if (currentPosition) {
+ const { x: currentX, y: currentY } = currentPosition;
+ const deltaX = Math.abs(currentX - initialX);
+ const deltaY = Math.abs(currentY - initialY);
+ const delta = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+
+ const movementThreshold = 10;
+ if (delta > movementThreshold) {
+ log(
+ 'DraggableCore: Drag canceled due to movement during delay'
+ );
+ clearTimeout(this.dragTimeout);
+ removeEvent(
+ ownerDocument,
+ eventsFor.touch.stop,
+ onTouchEnd
+ );
+ removeEvent(
+ ownerDocument,
+ eventsFor.touch.move,
+ onTouchMove
+ );
+ removeEvent(
+ ownerDocument,
+ 'contextmenu',
+ this.onContextMenu
+ );
+ }
+ }
+ };
+
+ addEvent(ownerDocument, eventsFor.touch.stop, onTouchEnd);
+ addEvent(ownerDocument, eventsFor.touch.move, onTouchMove);
+
+ this.dragTimeout = setTimeout(() => {
+ log('DraggableCore: Timeout end, starting drag');
+ removeEvent(ownerDocument, eventsFor.touch.stop, onTouchEnd);
+ removeEvent(ownerDocument, eventsFor.touch.move, onTouchMove);
+ removeEvent(ownerDocument, 'contextmenu', this.onContextMenu);
+ startDragging();
+ }, this.props.mobileDragDelay);
+ } else {
+ // Mouse or no delay — start immediately
+ startDragging();
+ }
+ };
+
+ handleDrag: EventHandler = (e) => {
+ // Ensure drag has started
+ if (!this.dragging) return;
+
+ // Get the current drag point from the event. This is used as the offset.
+ const position = getControlPosition(e, this.touchIdentifier, this);
+ if (position == null) return;
+ let { x, y } = position;
+
+ // Snap to grid if prop has been provided
+ if (Array.isArray(this.props.grid)) {
+ let deltaX = x - this.lastX,
+ deltaY = y - this.lastY;
+ [deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);
+ if (!deltaX && !deltaY) return; // skip useless drag
+ (x = this.lastX + deltaX), (y = this.lastY + deltaY);
+ }
+
+ const coreEvent = createCoreData(this, x, y);
+
+ log('DraggableCore: handleDrag: %j', coreEvent);
+
+ // Call event handler. If it returns explicit false, trigger end.
+ const shouldUpdate = this.props.onDrag(e, coreEvent);
+ if (shouldUpdate === false || this.mounted === false) {
+ try {
+ // $FlowIgnore
+ this.handleDragStop(new MouseEvent('mouseup'));
+ } catch (err) {
+ // Old browsers
+ const event = ((document.createEvent(
+ 'MouseEvents'
+ ): any): MouseTouchEvent);
+ // I see why this insanity was deprecated
+ // $FlowIgnore
+ event.initMouseEvent(
+ 'mouseup',
+ true,
+ true,
+ window,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null
+ );
+ this.handleDragStop(event);
+ }
+ return;
+ }
+
+ this.lastX = x;
+ this.lastY = y;
+ };
+
+ handleDragStop: EventHandler = (e) => {
+ // Ensure drag has started
+ if (!this.dragging) return;
+
+ if (this.dragTimeout) {
+ clearTimeout(this.dragTimeout);
+ this.dragTimeout = null;
+ }
+ const position = getControlPosition(e, this.touchIdentifier, this);
+ if (position == null) return;
+ let { x, y } = position;
+
+ // Snap to grid if prop has been provided
+ if (Array.isArray(this.props.grid)) {
+ let deltaX = x - this.lastX || 0;
+ let deltaY = y - this.lastY || 0;
+ [deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);
+ (x = this.lastX + deltaX), (y = this.lastY + deltaY);
+ }
+ const coreEvent = createCoreData(this, x, y);
+
+ // Call event handler
+ const shouldContinue = this.props.onStop(e, coreEvent);
+ if (shouldContinue === false || this.mounted === false) return false;
+ const thisNode = this.findDOMNode();
+ if (thisNode) {
+ // Remove user-select hack
+ if (this.props.enableUserSelectHack) {
+ scheduleRemoveUserSelectStyles(thisNode.ownerDocument);
+ }
+ }
+ log('DraggableCore: handleDragStop: %j', coreEvent);
+
+ // Reset the el.
+ this.dragging = false;
+ this.lastX = NaN;
+ this.lastY = NaN;
+ if (thisNode) {
+ // Remove event handlers
+ log('DraggableCore: Removing handlers');
+ removeEvent(
+ thisNode.ownerDocument,
+ dragEventFor.move,
+ this.handleDrag
+ );
+ removeEvent(
+ thisNode.ownerDocument,
+ dragEventFor.stop,
+ this.handleDragStop
+ );
+ // Remove scroll prevention
+ removeEvent(
+ thisNode.ownerDocument,
+ 'touchmove',
+ this.preventScroll,
+ { passive: false }
+ );
+ }
+ };
+
+ onMouseDown: EventHandler = (e) => {
+ dragEventFor = eventsFor.mouse; // on touchscreen laptops we could switch back to mouse
+
+ return this.handleDragStart(e);
+ };
+
+ onMouseUp: EventHandler = (e) => {
+ dragEventFor = eventsFor.mouse;
+
+ return this.handleDragStop(e);
+ };
+
+ // Same as onMouseDown (start drag), but now consider this a touch device.
+ onTouchStart: EventHandler = (e) => {
+ // We're on a touch device now, so change the event handlers
+ dragEventFor = eventsFor.touch;
+
+ return this.handleDragStart(e);
+ };
+
+ onTouchEnd: EventHandler = (e) => {
+ // We're on a touch device now, so change the event handlers
+ dragEventFor = eventsFor.touch;
+
+ return this.handleDragStop(e);
+ };
+
+ onContextMenu: EventHandler = (e) => {
+ // Prevent context menu for touch elements
+ e.preventDefault();
+ return false;
+ };
+
+ preventScroll: EventHandler = (e) => {
+ // Prevent scrolling during drag
+ if (this.dragging) {
+ e.preventDefault();
+ }
+ };
+
+ render(): React.Element {
+ // Reuse the child provided
+ // This makes it flexible to use whatever element is wanted (div, ul, etc)
+ return React.cloneElement(React.Children.only(this.props.children), {
+ // Note: mouseMove handler is attached to document so it will still function
+ // when the user drags quickly and leaves the bounds of the element.
+ onMouseDown: this.onMouseDown,
+ onMouseUp: this.onMouseUp,
+ // onTouchStart is added on `componentDidMount` so they can be added with
+ // {passive: false}, which allows it to cancel. See
+ // https://developers.google.com/web/updates/2017/01/scrolling-intervention
+ onTouchEnd: this.onTouchEnd,
+ });
}
- };
-
- onMouseDown: EventHandler = (e) => {
- dragEventFor = eventsFor.mouse; // on touchscreen laptops we could switch back to mouse
-
- return this.handleDragStart(e);
- };
-
- onMouseUp: EventHandler = (e) => {
- dragEventFor = eventsFor.mouse;
-
- return this.handleDragStop(e);
- };
-
- // Same as onMouseDown (start drag), but now consider this a touch device.
- onTouchStart: EventHandler = (e) => {
- // We're on a touch device now, so change the event handlers
- dragEventFor = eventsFor.touch;
-
- return this.handleDragStart(e);
- };
-
- onTouchEnd: EventHandler = (e) => {
- // We're on a touch device now, so change the event handlers
- dragEventFor = eventsFor.touch;
-
- return this.handleDragStop(e);
- };
-
- render(): React.Element {
- // Reuse the child provided
- // This makes it flexible to use whatever element is wanted (div, ul, etc)
- return React.cloneElement(React.Children.only(this.props.children), {
- // Note: mouseMove handler is attached to document so it will still function
- // when the user drags quickly and leaves the bounds of the element.
- onMouseDown: this.onMouseDown,
- onMouseUp: this.onMouseUp,
- // onTouchStart is added on `componentDidMount` so they can be added with
- // {passive: false}, which allows it to cancel. See
- // https://developers.google.com/web/updates/2017/01/scrolling-intervention
- onTouchEnd: this.onTouchEnd
- });
- }
}
diff --git a/package.json b/package.json
index 9dc93af1..9ddc9103 100644
--- a/package.json
+++ b/package.json
@@ -1,106 +1,107 @@
{
- "name": "react-draggable",
- "version": "4.5.0",
- "description": "React draggable component",
- "main": "build/cjs/cjs.js",
- "unpkg": "build/web/react-draggable.min.js",
- "scripts": {
- "test": "make test",
- "test-phantom": "make test-phantom",
- "test-debug": "karma start --browsers=Chrome --single-run=false --auto-watch=true",
- "test-firefox": "karma start --browsers=Firefox --single-run=false --auto-watch=true",
- "test-ie": "karma start --browsers=IE --single-run=false --auto-watch=true",
- "dev": "make dev",
- "build": "make clean build",
- "lint": "make lint",
- "flow": "flow"
- },
- "files": [
- "/build",
- "/typings",
- "/web/react-draggable.min.js",
- "/web/react-draggable.min.js.map"
- ],
- "typings": "./typings/index.d.ts",
- "types": "./typings/index.d.ts",
- "repository": {
- "type": "git",
- "url": "https://github.com/react-grid-layout/react-draggable.git"
- },
- "keywords": [
- "react",
- "draggable",
- "react-component"
- ],
- "author": "Matt Zabriskie",
- "contributors": [
- "Samuel Reed (http://strml.net/)"
- ],
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/react-grid-layout/react-draggable/issues"
- },
- "homepage": "https://github.com/react-grid-layout/react-draggable",
- "devDependencies": {
- "@types/react": "^18.2.23",
- "@types/react-dom": "^18.2.8",
- "@babel/cli": "^7.27.2",
- "@babel/core": "^7.27.4",
- "@babel/eslint-parser": "^7.27.5",
- "@babel/plugin-transform-class-properties": "^7.27.1",
- "@babel/plugin-transform-flow-comments": "^7.27.3",
- "@babel/preset-env": "^7.27.2",
- "@babel/preset-flow": "^7.27.1",
- "@babel/preset-react": "^7.27.1",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "^9.29.0",
- "@types/node": "^24.0.4",
- "assert": "^2.1.0",
- "babel-loader": "^10.0.0",
- "babel-plugin-transform-inline-environment-variables": "^0.4.4",
- "eslint": "^9.29.0",
- "eslint-plugin-react": "^7.37.5",
- "flow-bin": "^0.217.0",
- "globals": "^16.2.0",
- "jasmine-core": "^5.8.0",
- "karma": "^6.4.4",
- "karma-chrome-launcher": "^3.2.0",
- "karma-cli": "2.0.0",
- "karma-firefox-launcher": "^2.1.3",
- "karma-ie-launcher": "^1.0.0",
- "karma-jasmine": "^5.1.0",
- "karma-phantomjs-launcher": "^1.0.4",
- "karma-phantomjs-shim": "^1.5.0",
- "karma-webpack": "^5.0.1",
- "lodash": "^4.17.4",
- "phantomjs-prebuilt": "^2.1.16",
- "pre-commit": "^1.2.2",
- "process": "^0.11.10",
- "puppeteer": "^24.10.2",
- "react": "^16.13.1",
- "react-dom": "^16.13.1",
- "react-frame-component": "^5.2.7",
- "react-test-renderer": "^16.13.1",
- "semver": "^7.7.2",
- "static-server": "^3.0.0",
- "typescript": "^5.8.3",
- "webpack": "^5.99.9",
- "webpack-cli": "^6.0.1",
- "webpack-dev-server": "^5.2.2"
- },
- "resolutions": {
- "minimist": "^1.2.5"
- },
- "precommit": [
- "lint",
- "test"
- ],
- "dependencies": {
- "clsx": "^2.1.1",
- "prop-types": "^15.8.1"
- },
- "peerDependencies": {
- "react": ">= 16.3.0",
- "react-dom": ">= 16.3.0"
- }
-}
\ No newline at end of file
+ "name": "@zsobecki_futurum/react-draggable",
+ "version": "4.6.2",
+ "description": "React draggable component with better support for touch events",
+ "main": "build/cjs/cjs.js",
+ "unpkg": "build/web/react-draggable.min.js",
+ "scripts": {
+ "test": "make test",
+ "test-phantom": "make test-phantom",
+ "test-debug": "karma start --browsers=Chrome --single-run=false --auto-watch=true",
+ "test-firefox": "karma start --browsers=Firefox --single-run=false --auto-watch=true",
+ "test-ie": "karma start --browsers=IE --single-run=false --auto-watch=true",
+ "dev": "make dev",
+ "build": "make clean build",
+ "lint": "make lint",
+ "flow": "flow"
+ },
+ "files": [
+ "/build",
+ "/typings",
+ "/web/react-draggable.min.js",
+ "/web/react-draggable.min.js.map"
+ ],
+ "typings": "./typings/index.d.ts",
+ "types": "./typings/index.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/zsobecki-futurum/react-draggable.git"
+ },
+ "keywords": [
+ "react",
+ "draggable",
+ "react-component"
+ ],
+ "author": "Matt Zabriskie",
+ "contributors": [
+ "Samuel Reed (http://strml.net/)",
+ "Zbyszko Sobecki "
+ ],
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/zsobecki-futurum/react-draggable/issues"
+ },
+ "homepage": "https://github.com/zsobecki-futurum/react-draggable",
+ "devDependencies": {
+ "@types/react": "^18.2.23",
+ "@types/react-dom": "^18.2.8",
+ "@babel/cli": "^7.27.2",
+ "@babel/core": "^7.27.4",
+ "@babel/eslint-parser": "^7.27.5",
+ "@babel/plugin-transform-class-properties": "^7.27.1",
+ "@babel/plugin-transform-flow-comments": "^7.27.3",
+ "@babel/preset-env": "^7.27.2",
+ "@babel/preset-flow": "^7.27.1",
+ "@babel/preset-react": "^7.27.1",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "^9.29.0",
+ "@types/node": "^24.0.4",
+ "assert": "^2.1.0",
+ "babel-loader": "^10.0.0",
+ "babel-plugin-transform-inline-environment-variables": "^0.4.4",
+ "eslint": "^9.29.0",
+ "eslint-plugin-react": "^7.37.5",
+ "flow-bin": "^0.217.0",
+ "globals": "^16.2.0",
+ "jasmine-core": "^5.8.0",
+ "karma": "^6.4.4",
+ "karma-chrome-launcher": "^3.2.0",
+ "karma-cli": "2.0.0",
+ "karma-firefox-launcher": "^2.1.3",
+ "karma-ie-launcher": "^1.0.0",
+ "karma-jasmine": "^5.1.0",
+ "karma-phantomjs-launcher": "^1.0.4",
+ "karma-phantomjs-shim": "^1.5.0",
+ "karma-webpack": "^5.0.1",
+ "lodash": "^4.17.4",
+ "phantomjs-prebuilt": "^2.1.16",
+ "pre-commit": "^1.2.2",
+ "process": "^0.11.10",
+ "puppeteer": "^24.10.2",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "react-frame-component": "^5.2.7",
+ "react-test-renderer": "^16.13.1",
+ "semver": "^7.7.2",
+ "static-server": "^3.0.0",
+ "typescript": "^5.8.3",
+ "webpack": "^5.99.9",
+ "webpack-cli": "^6.0.1",
+ "webpack-dev-server": "^5.2.2"
+ },
+ "resolutions": {
+ "minimist": "^1.2.5"
+ },
+ "precommit": [
+ "lint",
+ "test"
+ ],
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0",
+ "react-dom": ">= 16.3.0"
+ }
+}
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 14d29b8c..19cabfa8 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -1,68 +1,82 @@
declare module 'react-draggable' {
- import * as React from 'react';
+ import * as React from 'react'
- export interface DraggableBounds {
- left?: number
- right?: number
- top?: number
- bottom?: number
- }
+ export interface DraggableBounds {
+ left?: number
+ right?: number
+ top?: number
+ bottom?: number
+ }
- export interface DraggableProps extends DraggableCoreProps {
- axis: 'both' | 'x' | 'y' | 'none',
- bounds: DraggableBounds | string | false ,
- defaultClassName: string,
- defaultClassNameDragging: string,
- defaultClassNameDragged: string,
- defaultPosition: ControlPosition,
- positionOffset: PositionOffsetControlPosition,
- position: ControlPosition
- }
+ export interface DraggableProps extends DraggableCoreProps {
+ axis: 'both' | 'x' | 'y' | 'none'
+ bounds: DraggableBounds | string | false
+ defaultClassName: string
+ defaultClassNameDragging: string
+ defaultClassNameDragged: string
+ defaultPosition: ControlPosition
+ positionOffset: PositionOffsetControlPosition
+ position: ControlPosition
+ }
- export type DraggableEvent = React.MouseEvent
- | React.TouchEvent
- | MouseEvent
- | TouchEvent
+ export type DraggableEvent =
+ | React.MouseEvent
+ | React.TouchEvent
+ | MouseEvent
+ | TouchEvent
- export type DraggableEventHandler = (
- e: DraggableEvent,
- data: DraggableData
- ) => void | false;
+ export type DraggableEventHandler = (
+ e: DraggableEvent,
+ data: DraggableData
+ ) => void | false
- export interface DraggableData {
- node: HTMLElement,
- x: number, y: number,
- deltaX: number, deltaY: number,
- lastX: number, lastY: number
- }
+ export interface DraggableData {
+ node: HTMLElement
+ x: number
+ y: number
+ deltaX: number
+ deltaY: number
+ lastX: number
+ lastY: number
+ }
- export type ControlPosition = {x: number, y: number};
+ export type ControlPosition = { x: number; y: number }
- export type PositionOffsetControlPosition = {x: number|string, y: number|string};
+ export type PositionOffsetControlPosition = {
+ x: number | string
+ y: number | string
+ }
- export interface DraggableCoreProps {
- allowAnyClick: boolean,
- allowMobileScroll: boolean,
- cancel: string,
- children?: React.ReactNode,
- disabled: boolean,
- enableUserSelectHack: boolean,
- offsetParent: HTMLElement,
- grid: [number, number],
- handle: string,
- nodeRef?: React.RefObject,
- onStart: DraggableEventHandler,
- onDrag: DraggableEventHandler,
- onStop: DraggableEventHandler,
- onMouseDown: (e: MouseEvent) => void,
- scale: number
- }
+ export interface DraggableCoreProps {
+ allowAnyClick: boolean
+ allowMobileScroll: boolean
+ cancel: string
+ children?: React.ReactNode
+ disabled: boolean
+ enableUserSelectHack: boolean
+ offsetParent: HTMLElement
+ grid: [number, number]
+ handle: string
+ nodeRef?: React.RefObject
+ onStart: DraggableEventHandler
+ onDrag: DraggableEventHandler
+ onStop: DraggableEventHandler
+ onMouseDown: (e: MouseEvent) => void
+ scale: number
+ mobileDragDelay?: number
+ }
- export default class Draggable extends React.Component, {}> {
- static defaultProps : DraggableProps;
- }
+ export default class Draggable extends React.Component<
+ Partial,
+ {}
+ > {
+ static defaultProps: DraggableProps
+ }
- export class DraggableCore extends React.Component, {}> {
- static defaultProps : DraggableCoreProps;
- }
+ export class DraggableCore extends React.Component<
+ Partial,
+ {}
+ > {
+ static defaultProps: DraggableCoreProps
+ }
}