UNPKG

@spotware/react-dnd-scrollzone

Version:

A cross browser solution to scrolling during drag and drop.

335 lines (263 loc) 12.7 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultVerticalStrength = exports.defaultHorizontalStrength = undefined; var _createClass = 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _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; }; exports.createHorizontalStrength = createHorizontalStrength; exports.createVerticalStrength = createVerticalStrength; exports.default = createScrollingComponent; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _DragDropContext = require('react-dnd/lib/DragDropContext'); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactDom = require('react-dom'); var _lodash = require('lodash.throttle'); var _lodash2 = _interopRequireDefault(_lodash); var _raf = require('raf'); var _raf2 = _interopRequireDefault(_raf); var _reactDisplayName = require('react-display-name'); var _reactDisplayName2 = _interopRequireDefault(_reactDisplayName); var _hoistNonReactStatics = require('hoist-non-react-statics'); var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics); var _util = require('./util'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var DEFAULT_BUFFER = 150; var useNewContextApi = _react.createContext !== undefined && _DragDropContext.Consumer !== undefined; function createDragDropMonitorWrapper(WrappedComponent) { return function DragDropMonitorWrapper(props) { return _react2.default.createElement( _DragDropContext.Consumer, null, function (_ref) { var dragDropManager = _ref.dragDropManager; return _react2.default.createElement(WrappedComponent, _extends({}, props, { dragDropManager: dragDropManager })); } ); }; } function createHorizontalStrength(_buffer) { return function defaultHorizontalStrength(_ref2, point) { var x = _ref2.x, w = _ref2.w, y = _ref2.y, h = _ref2.h; var buffer = Math.min(w / 2, _buffer); var inRange = point.x >= x && point.x <= x + w; var inBox = inRange && point.y >= y && point.y <= y + h; if (inBox) { if (point.x < x + buffer) { return (point.x - x - buffer) / buffer; } else if (point.x > x + w - buffer) { return -(x + w - point.x - buffer) / buffer; } } return 0; }; } function createVerticalStrength(_buffer) { return function defaultVerticalStrength(_ref3, point) { var y = _ref3.y, h = _ref3.h, x = _ref3.x, w = _ref3.w; var buffer = Math.min(h / 2, _buffer); var inRange = point.y >= y && point.y <= y + h; var inBox = inRange && point.x >= x && point.x <= x + w; if (inBox) { if (point.y < y + buffer) { return (point.y - y - buffer) / buffer; } else if (point.y > y + h - buffer) { return -(y + h - point.y - buffer) / buffer; } } return 0; }; } var defaultHorizontalStrength = exports.defaultHorizontalStrength = createHorizontalStrength(DEFAULT_BUFFER); var defaultVerticalStrength = exports.defaultVerticalStrength = createVerticalStrength(DEFAULT_BUFFER); function createScrollingComponent(WrappedComponent) { var ScrollingComponent = function (_Component) { _inherits(ScrollingComponent, _Component); function ScrollingComponent(props, ctx) { _classCallCheck(this, ScrollingComponent); var _this = _possibleConstructorReturn(this, (ScrollingComponent.__proto__ || Object.getPrototypeOf(ScrollingComponent)).call(this, props, ctx)); _this.handleEvent = function (evt) { if (_this.dragging && !_this.attached) { _this.attach(); _this.updateScrolling(evt); } }; _this.updateScrolling = (0, _lodash2.default)(function (evt) { var _this$container$getBo = _this.container.getBoundingClientRect(), x = _this$container$getBo.left, y = _this$container$getBo.top, w = _this$container$getBo.width, h = _this$container$getBo.height; var box = { x: x, y: y, w: w, h: h }; var coords = (0, _util.getCoords)(evt); // calculate strength _this.scaleX = _this.props.horizontalStrength(box, coords); _this.scaleY = _this.props.verticalStrength(box, coords); // start scrolling if we need to if (!_this.frame && (_this.scaleX || _this.scaleY)) { _this.startScrolling(); } }, 100, { trailing: false }); _this.scaleX = 0; _this.scaleY = 0; _this.frame = null; _this.attached = false; _this.dragging = false; return _this; } _createClass(ScrollingComponent, [{ key: 'componentDidMount', value: function componentDidMount() { var _this2 = this; var getScrollContainer = this.props.getScrollContainer; var wrappedNode = (0, _reactDom.findDOMNode)(this.wrappedInstance); this.container = getScrollContainer ? getScrollContainer(wrappedNode) : wrappedNode; this.container.addEventListener('dragover', this.handleEvent); // touchmove events don't seem to work across siblings, so we unfortunately // have to attach the listeners to the body window.document.body.addEventListener('touchmove', this.handleEvent); this.clearMonitorSubscription = this.getDragDropManager().getMonitor().subscribeToStateChange(function () { return _this2.handleMonitorChange(); }); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this.container.removeEventListener('dragover', this.handleEvent); window.document.body.removeEventListener('touchmove', this.handleEvent); this.clearMonitorSubscription(); this.stopScrolling(); } }, { key: 'getDragDropManager', value: function getDragDropManager() { return useNewContextApi ? this.props.dragDropManager : this.context.dragDropManager; } }, { key: 'handleMonitorChange', value: function handleMonitorChange() { var isDragging = this.getDragDropManager().getMonitor().isDragging(); if (!this.dragging && isDragging) { this.dragging = true; } else if (this.dragging && !isDragging) { this.dragging = false; this.stopScrolling(); } } }, { key: 'attach', value: function attach() { this.attached = true; window.document.body.addEventListener('dragover', this.updateScrolling); window.document.body.addEventListener('touchmove', this.updateScrolling); } }, { key: 'detach', value: function detach() { this.attached = false; window.document.body.removeEventListener('dragover', this.updateScrolling); window.document.body.removeEventListener('touchmove', this.updateScrolling); } // Update scaleX and scaleY every 100ms or so // and start scrolling if necessary }, { key: 'startScrolling', value: function startScrolling() { var _this3 = this; var i = 0; var tick = function tick() { var scaleX = _this3.scaleX, scaleY = _this3.scaleY, container = _this3.container; var _props = _this3.props, strengthMultiplier = _props.strengthMultiplier, onScrollChange = _props.onScrollChange; // stop scrolling if there's nothing to do if (strengthMultiplier === 0 || scaleX + scaleY === 0) { _this3.stopScrolling(); return; } // there's a bug in safari where it seems like we can't get // mousemove events from a container that also emits a scroll // event that same frame. So we double the strengthMultiplier and only adjust // the scroll position at 30fps if (i++ % 2) { var scrollLeft = container.scrollLeft, scrollTop = container.scrollTop, scrollWidth = container.scrollWidth, scrollHeight = container.scrollHeight, clientWidth = container.clientWidth, clientHeight = container.clientHeight; var newLeft = scaleX ? container.scrollLeft = (0, _util.intBetween)(0, scrollWidth - clientWidth, scrollLeft + scaleX * strengthMultiplier) : scrollLeft; var newTop = scaleY ? container.scrollTop = (0, _util.intBetween)(0, scrollHeight - clientHeight, scrollTop + scaleY * strengthMultiplier) : scrollTop; onScrollChange(newLeft, newTop); } _this3.frame = (0, _raf2.default)(tick); }; tick(); } }, { key: 'stopScrolling', value: function stopScrolling() { this.detach(); this.scaleX = 0; this.scaleY = 0; if (this.frame) { _raf2.default.cancel(this.frame); this.frame = null; } } }, { key: 'render', value: function render() { var _this4 = this; var _props2 = this.props, strengthMultiplier = _props2.strengthMultiplier, verticalStrength = _props2.verticalStrength, horizontalStrength = _props2.horizontalStrength, onScrollChange = _props2.onScrollChange, props = _objectWithoutProperties(_props2, ['strengthMultiplier', 'verticalStrength', 'horizontalStrength', 'onScrollChange']); return _react2.default.createElement(WrappedComponent, _extends({ ref: function ref(_ref4) { _this4.wrappedInstance = _ref4; } }, props)); } }]); return ScrollingComponent; }(_react.Component); ScrollingComponent.displayName = 'Scrolling(' + (0, _reactDisplayName2.default)(WrappedComponent) + ')'; ScrollingComponent.propTypes = { onScrollChange: _propTypes2.default.func, verticalStrength: _propTypes2.default.func, horizontalStrength: _propTypes2.default.func, strengthMultiplier: _propTypes2.default.number, getScrollContainer: _propTypes2.default.func }; ScrollingComponent.defaultProps = { onScrollChange: _util.noop, verticalStrength: defaultVerticalStrength, horizontalStrength: defaultHorizontalStrength, strengthMultiplier: 30 }; ScrollingComponent.contextTypes = useNewContextApi ? undefined : { dragDropManager: _propTypes2.default.object }; if (useNewContextApi) { var DragDropMonitorWrapper = createDragDropMonitorWrapper(ScrollingComponent); return (0, _hoistNonReactStatics2.default)(DragDropMonitorWrapper, WrappedComponent); } return (0, _hoistNonReactStatics2.default)(ScrollingComponent, WrappedComponent); }