UNPKG

react-draggable

Version:
380 lines (328 loc) 12.7 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 _classnames = _interopRequireDefault(require("classnames")); var _domFns = require("./utils/domFns"); var _positionFns = require("./utils/positionFns"); var _shims = require("./utils/shims"); var _DraggableCore = _interopRequireDefault(require("./DraggableCore")); var _log = _interopRequireDefault(require("./utils/log")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 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; } // // Define <Draggable> // class Draggable extends _react.default.Component { // React 16.3+ // Arity (props, state) static getDerivedStateFromProps({ position } /*: DraggableProps*/ , { prevPropsPosition } /*: DraggableState*/ ) { // 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)) { (0, _log.default)('Draggable: getDerivedStateFromProps %j', { position, prevPropsPosition }); return { x: position.x, y: position.y, prevPropsPosition: { ...position } }; } return null; } constructor(props /*: DraggableProps*/ ) { super(props); _defineProperty(this, "onDragStart", (e, coreData) => { (0, _log.default)('Draggable: onDragStart: %j', coreData); // Short-circuit if user's callback killed it. const shouldStart = this.props.onStart(e, (0, _positionFns.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 }); }); _defineProperty(this, "onDrag", (e, coreData) => { if (!this.state.dragging) return false; (0, _log.default)('Draggable: onDrag: %j', coreData); const uiData = (0, _positionFns.createDraggableData)(this, coreData); const newState /*: $Shape<DraggableState>*/ = { x: uiData.x, y: uiData.y }; // 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] = (0, _positionFns.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); }); _defineProperty(this, "onDragStop", (e, coreData) => { if (!this.state.dragging) return false; // Short-circuit if user's callback killed it. const shouldStop = this.props.onStop(e, (0, _positionFns.createDraggableData)(this, coreData)); if (shouldStop === false) return false; (0, _log.default)('Draggable: onDragStop: %j', coreData); const newState /*: $Shape<DraggableState>*/ = { 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); }); this.state = { // Whether or not we are currently dragging. dragging: 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, prevPropsPosition: { ...props.position }, // Used for compensating for out-of-bounds drags slackX: 0, slackY: 0, // 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 <Draggable>, 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' && _reactDom.default.findDOMNode(this) instanceof window.SVGElement) { this.setState({ isElementSVG: true }); } } componentWillUnmount() { this.setState({ dragging: false }); // prevents invariant if unmounted while dragging } render() /*: ReactElement<any>*/ { 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: (0, _positionFns.canDragX)(this) && draggable ? this.state.x : validPosition.x, // Set top if vertical drag is enabled y: (0, _positionFns.canDragY)(this) && draggable ? this.state.y : validPosition.y }; // If this element was SVG, we use the `transform` attribute. if (this.state.isElementSVG) { svgTransform = (0, _domFns.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 <span> so <Draggable> // has a clean slate. style = (0, _domFns.createCSSTransform)(transformOpts, positionOffset); } // Mark with class while dragging const className = (0, _classnames.default)(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.default.createElement(_DraggableCore.default, _extends({}, draggableCoreProps, { onStart: this.onDragStart, onDrag: this.onDrag, onStop: this.onDragStop }), _react.default.cloneElement(_react.default.Children.only(children), { className: className, style: { ...children.props.style, ...style }, transform: svgTransform })); } } exports.default = Draggable; _defineProperty(Draggable, "displayName", 'Draggable'); _defineProperty(Draggable, "propTypes", { // Accepts all props <DraggableCore> accepts. ..._DraggableCore.default.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.default.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 ( * <Draggable bounds={{right: 300, bottom: 300}}> * <div>Content</div> * </Draggable> * ); * } * }); * ``` */ bounds: _propTypes.default.oneOfType([_propTypes.default.shape({ left: _propTypes.default.number, right: _propTypes.default.number, top: _propTypes.default.number, bottom: _propTypes.default.number }), _propTypes.default.string, _propTypes.default.oneOf([false])]), defaultClassName: _propTypes.default.string, defaultClassNameDragging: _propTypes.default.string, defaultClassNameDragged: _propTypes.default.string, /** * `defaultPosition` specifies the x and y that the dragged item should start at * * Example: * * ```jsx * let App = React.createClass({ * render: function () { * return ( * <Draggable defaultPosition={{x: 25, y: 25}}> * <div>I start with transformX: 25px and transformY: 25px;</div> * </Draggable> * ); * } * }); * ``` */ defaultPosition: _propTypes.default.shape({ x: _propTypes.default.number, y: _propTypes.default.number }), positionOffset: _propTypes.default.shape({ x: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), y: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.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 ( * <Draggable position={{x: 25, y: 25}}> * <div>I start with transformX: 25px and transformY: 25px;</div> * </Draggable> * ); * } * }); * ``` */ position: _propTypes.default.shape({ x: _propTypes.default.number, y: _propTypes.default.number }), /** * These properties should be defined on the child, not here. */ className: _shims.dontSetMe, style: _shims.dontSetMe, transform: _shims.dontSetMe }); _defineProperty(Draggable, "defaultProps", { ..._DraggableCore.default.defaultProps, axis: 'both', bounds: false, defaultClassName: 'react-draggable', defaultClassNameDragging: 'react-draggable-dragging', defaultClassNameDragged: 'react-draggable-dragged', defaultPosition: { x: 0, y: 0 }, position: null, scale: 1 });