UNPKG

react-draggable

Version:
490 lines (385 loc) 18.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _reactDom = _interopRequireDefault(require("react-dom")); var _domFns = require("./utils/domFns"); var _positionFns = require("./utils/positionFns"); var _shims = require("./utils/shims"); var _log = _interopRequireDefault(require("./utils/log")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // Simple abstraction for dragging events names. var eventsFor = { touch: { start: 'touchstart', move: 'touchmove', stop: 'touchend' }, mouse: { start: 'mousedown', move: 'mousemove', stop: 'mouseup' } }; // Default to mouse events. var dragEventFor = eventsFor.mouse; /*:: type DraggableCoreState = { dragging: boolean, lastX: number, lastY: number, touchIdentifier: ?number };*/ /*:: export type DraggableBounds = { left: number, right: number, top: number, bottom: number, };*/ /*:: export type DraggableData = { node: HTMLElement, x: number, y: number, deltaX: number, deltaY: number, lastX: number, lastY: number, };*/ /*:: export type DraggableEventHandler = (e: MouseEvent, data: DraggableData) => void;*/ /*:: export type ControlPosition = {x: number, y: number};*/ /*:: export type PositionOffsetControlPosition = {x: number|string, y: number|string};*/ /*:: export type DraggableCoreProps = { allowAnyClick: boolean, cancel: string, children: ReactElement<any>, disabled: boolean, enableUserSelectHack: boolean, offsetParent: HTMLElement, grid: [number, number], handle: string, onStart: DraggableEventHandler, onDrag: DraggableEventHandler, onStop: DraggableEventHandler, onMouseDown: (e: MouseEvent) => void, };*/ // // Define <DraggableCore>. // // <DraggableCore> is for advanced usage of <Draggable>. It maintains minimal internal state so it can // work well with libraries that require more control over the element. // var DraggableCore = /*#__PURE__*/ function (_React$Component) { _inherits(DraggableCore, _React$Component); function DraggableCore() { var _getPrototypeOf2; var _this; _classCallCheck(this, DraggableCore); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(DraggableCore)).call.apply(_getPrototypeOf2, [this].concat(args))); _defineProperty(_assertThisInitialized(_this), "state", { dragging: false, // Used while dragging to determine deltas. lastX: NaN, lastY: NaN, touchIdentifier: null }); _defineProperty(_assertThisInitialized(_this), "handleDragStart", function (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) var thisNode = _reactDom.default.findDOMNode(_assertThisInitialized(_this)); if (!thisNode || !thisNode.ownerDocument || !thisNode.ownerDocument.body) { throw new Error('<DraggableCore> not mounted on DragStart!'); } var ownerDocument = thisNode.ownerDocument; // 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 && !(0, _domFns.matchesSelectorAndParentsTo)(e.target, _this.props.handle, thisNode) || _this.props.cancel && (0, _domFns.matchesSelectorAndParentsTo)(e.target, _this.props.cancel, thisNode)) { return; } // 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. var touchIdentifier = (0, _domFns.getTouchIdentifier)(e); _this.setState({ touchIdentifier: touchIdentifier }); // Get the current drag point from the event. This is used as the offset. var position = (0, _positionFns.getControlPosition)(e, touchIdentifier, _assertThisInitialized(_this)); if (position == null) return; // not possible but satisfies flow var x = position.x, y = position.y; // Create an event object with all the data parents need to make a decision here. var coreEvent = (0, _positionFns.createCoreData)(_assertThisInitialized(_this), x, y); (0, _log.default)('DraggableCore: handleDragStart: %j', coreEvent); // Call event handler. If it returns explicit false, cancel. (0, _log.default)('calling', _this.props.onStart); var shouldUpdate = _this.props.onStart(e, coreEvent); if (shouldUpdate === 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) (0, _domFns.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.setState({ dragging: true, lastX: x, 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. (0, _domFns.addEvent)(ownerDocument, dragEventFor.move, _this.handleDrag); (0, _domFns.addEvent)(ownerDocument, dragEventFor.stop, _this.handleDragStop); }); _defineProperty(_assertThisInitialized(_this), "handleDrag", function (e) { // Prevent scrolling on mobile devices, like ipad/iphone. if (e.type === 'touchmove') e.preventDefault(); // Get the current drag point from the event. This is used as the offset. var position = (0, _positionFns.getControlPosition)(e, _this.state.touchIdentifier, _assertThisInitialized(_this)); if (position == null) return; var x = position.x, y = position.y; // Snap to grid if prop has been provided if (Array.isArray(_this.props.grid)) { var deltaX = x - _this.state.lastX, deltaY = y - _this.state.lastY; var _snapToGrid = (0, _positionFns.snapToGrid)(_this.props.grid, deltaX, deltaY); var _snapToGrid2 = _slicedToArray(_snapToGrid, 2); deltaX = _snapToGrid2[0]; deltaY = _snapToGrid2[1]; if (!deltaX && !deltaY) return; // skip useless drag x = _this.state.lastX + deltaX, y = _this.state.lastY + deltaY; } var coreEvent = (0, _positionFns.createCoreData)(_assertThisInitialized(_this), x, y); (0, _log.default)('DraggableCore: handleDrag: %j', coreEvent); // Call event handler. If it returns explicit false, trigger end. var shouldUpdate = _this.props.onDrag(e, coreEvent); if (shouldUpdate === false) { try { // $FlowIgnore _this.handleDragStop(new MouseEvent('mouseup')); } catch (err) { // Old browsers var 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.setState({ lastX: x, lastY: y }); }); _defineProperty(_assertThisInitialized(_this), "handleDragStop", function (e) { if (!_this.state.dragging) return; var position = (0, _positionFns.getControlPosition)(e, _this.state.touchIdentifier, _assertThisInitialized(_this)); if (position == null) return; var x = position.x, y = position.y; var coreEvent = (0, _positionFns.createCoreData)(_assertThisInitialized(_this), x, y); var thisNode = _reactDom.default.findDOMNode(_assertThisInitialized(_this)); if (thisNode) { // Remove user-select hack if (_this.props.enableUserSelectHack) (0, _domFns.removeUserSelectStyles)(thisNode.ownerDocument); } (0, _log.default)('DraggableCore: handleDragStop: %j', coreEvent); // Reset the el. _this.setState({ dragging: false, lastX: NaN, lastY: NaN }); // Call event handler _this.props.onStop(e, coreEvent); if (thisNode) { // Remove event handlers (0, _log.default)('DraggableCore: Removing handlers'); (0, _domFns.removeEvent)(thisNode.ownerDocument, dragEventFor.move, _this.handleDrag); (0, _domFns.removeEvent)(thisNode.ownerDocument, dragEventFor.stop, _this.handleDragStop); } }); _defineProperty(_assertThisInitialized(_this), "onMouseDown", function (e) { dragEventFor = eventsFor.mouse; // on touchscreen laptops we could switch back to mouse return _this.handleDragStart(e); }); _defineProperty(_assertThisInitialized(_this), "onMouseUp", function (e) { dragEventFor = eventsFor.mouse; return _this.handleDragStop(e); }); _defineProperty(_assertThisInitialized(_this), "onTouchStart", function (e) { // We're on a touch device now, so change the event handlers dragEventFor = eventsFor.touch; return _this.handleDragStart(e); }); _defineProperty(_assertThisInitialized(_this), "onTouchEnd", function (e) { // We're on a touch device now, so change the event handlers dragEventFor = eventsFor.touch; return _this.handleDragStop(e); }); return _this; } _createClass(DraggableCore, [{ key: "componentWillUnmount", value: function componentWillUnmount() { // 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. var thisNode = _reactDom.default.findDOMNode(this); if (thisNode) { var ownerDocument = thisNode.ownerDocument; (0, _domFns.removeEvent)(ownerDocument, eventsFor.mouse.move, this.handleDrag); (0, _domFns.removeEvent)(ownerDocument, eventsFor.touch.move, this.handleDrag); (0, _domFns.removeEvent)(ownerDocument, eventsFor.mouse.stop, this.handleDragStop); (0, _domFns.removeEvent)(ownerDocument, eventsFor.touch.stop, this.handleDragStop); if (this.props.enableUserSelectHack) (0, _domFns.removeUserSelectStyles)(ownerDocument); } } }, { key: "render", value: function render() { // Reuse the child provided // This makes it flexible to use whatever element is wanted (div, ul, etc) return _react.default.cloneElement(_react.default.Children.only(this.props.children), { style: (0, _domFns.styleHacks)(this.props.children.props.style), // 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, onTouchStart: this.onTouchStart, onMouseUp: this.onMouseUp, onTouchEnd: this.onTouchEnd }); } }]); return DraggableCore; }(_react.default.Component); exports.default = DraggableCore; _defineProperty(DraggableCore, "displayName", 'DraggableCore'); _defineProperty(DraggableCore, "propTypes", { /** * `allowAnyClick` allows dragging using any mouse button. * By default, we only accept the left button. * * Defaults to `false`. */ allowAnyClick: _propTypes.default.bool, /** * `disabled`, if true, stops the <Draggable> from dragging. All handlers, * with the exception of `onMouseDown`, will not fire. */ disabled: _propTypes.default.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.default.bool, /** * `offsetParent`, if set, uses the passed DOM node to compute drag offsets * instead of using the parent node. */ offsetParent: function offsetParent(props /*: DraggableCoreProps*/ , propName /*: $Keys<DraggableCoreProps>*/ ) { 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.default.arrayOf(_propTypes.default.number), /** * `handle` specifies a selector to be used as the handle that initiates drag. * * Example: * * ```jsx * let App = React.createClass({ * render: function () { * return ( * <Draggable handle=".handle"> * <div> * <div className="handle">Click me to drag</div> * <div>This is some other content</div> * </div> * </Draggable> * ); * } * }); * ``` */ handle: _propTypes.default.string, /** * `cancel` specifies a selector to be used to prevent drag initialization. * * Example: * * ```jsx * let App = React.createClass({ * render: function () { * return( * <Draggable cancel=".cancel"> * <div> * <div className="cancel">You can't drag from here</div> * <div>Dragging here works fine</div> * </div> * </Draggable> * ); * } * }); * ``` */ cancel: _propTypes.default.string, /** * Called when dragging starts. * If this function returns the boolean false, dragging will be canceled. */ onStart: _propTypes.default.func, /** * Called while dragging. * If this function returns the boolean false, dragging will be canceled. */ onDrag: _propTypes.default.func, /** * Called when dragging stops. * If this function returns the boolean false, the drag will remain active. */ onStop: _propTypes.default.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.default.func, /** * These properties should be defined on the child, not here. */ className: _shims.dontSetMe, style: _shims.dontSetMe, transform: _shims.dontSetMe }); _defineProperty(DraggableCore, "defaultProps", { allowAnyClick: false, // by default only accept left click cancel: null, disabled: false, enableUserSelectHack: true, offsetParent: null, handle: null, grid: null, transform: null, onStart: function onStart() {}, onDrag: function onDrag() {}, onStop: function onStop() {}, onMouseDown: function onMouseDown() {} });