UNPKG

@atlaskit/pragmatic-drag-and-drop-react-beautiful-dnd-migration

Version:

An optional Pragmatic drag and drop package that enables rapid migration from react-beautiful-dnd to Pragmatic drag and drop

459 lines (447 loc) 17.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.DragDropContext = DragDropContext; exports.resetServerContext = resetServerContext; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); var _useCapturedDimensions = require("../hooks/use-captured-dimensions"); var _useCleanupFn = require("../hooks/use-cleanup-fn"); var _attributes = require("../utils/attributes"); var _findDragHandle = require("../utils/find-drag-handle"); var _getClosestPositionedElement = require("../utils/get-closest-positioned-element"); var _cancelDrag = require("./cancel-drag"); var _draggableLocation = require("./draggable-location"); var _droppableRegistry = require("./droppable-registry"); var _errorBoundary = require("./error-boundary"); var _getDestination = require("./get-destination"); var _useHiddenTextElement = _interopRequireDefault(require("./hooks/use-hidden-text-element")); var _useKeyboardControls2 = require("./hooks/use-keyboard-controls"); var _usePointerControls = require("./hooks/use-pointer-controls"); var _useStyleMarshal = _interopRequireDefault(require("./hooks/use-style-marshal")); var _internalContext = require("./internal-context"); var _lifecycleContext = require("./lifecycle-context"); var _liveRegion = require("./live-region"); var _rbdInvariant = require("./rbd-invariant"); var _screenReader = require("./screen-reader"); var _useScheduler2 = require("./use-scheduler"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } // eslint-disable-next-line import/no-extraneous-dependencies /** * The instance count is used for selectors when querying the document. * * Ideally, in the future, this can be removed completely. */ var instanceCount = 0; function resetServerContext() { instanceCount = 0; } function getContextId() { return "".concat(instanceCount++); } function getOffset(args) { var offsetElement = (0, _getClosestPositionedElement.getClosestPositionedElement)(args); return { top: offsetElement.offsetTop, left: offsetElement.offsetLeft }; } function DragDropContext(_ref) { var children = _ref.children, _ref$dragHandleUsageI = _ref.dragHandleUsageInstructions, dragHandleUsageInstructions = _ref$dragHandleUsageI === void 0 ? _screenReader.defaultDragHandleUsageInstructions : _ref$dragHandleUsageI, nonce = _ref.nonce, onBeforeCapture = _ref.onBeforeCapture, onBeforeDragStart = _ref.onBeforeDragStart, onDragStart = _ref.onDragStart, onDragUpdate = _ref.onDragUpdate, onDragEnd = _ref.onDragEnd; var _useState = (0, _react.useState)(getContextId), _useState2 = (0, _slicedToArray2.default)(_useState, 1), contextId = _useState2[0]; (0, _useHiddenTextElement.default)({ contextId: contextId, text: dragHandleUsageInstructions }); var lifecycle = (0, _lifecycleContext.useLifecycle)(); var _useScheduler = (0, _useScheduler2.useScheduler)(), schedule = _useScheduler.schedule, flush = _useScheduler.flush; var dragStateRef = (0, _react.useRef)({ isDragging: false }); var getDragState = (0, _react.useCallback)(function () { return dragStateRef.current; }, []); var droppableRegistry = (0, _droppableRegistry.useDroppableRegistry)(); var getClosestEnabledDraggableLocation = (0, _react.useCallback)(function (_ref2) { var droppableId = _ref2.droppableId; var droppable = droppableRegistry.getEntry({ droppableId: droppableId }); while (droppable !== null && droppable.isDropDisabled) { var _droppable = droppable, parentDroppableId = _droppable.parentDroppableId; droppable = parentDroppableId ? droppableRegistry.getEntry({ droppableId: parentDroppableId }) : null; } if (droppable === null) { return null; } return { droppableId: droppable.droppableId, index: 0 }; }, [droppableRegistry]); (0, _react.useEffect)(function () { /** * If there is a drag when the context unmounts, cancel it. */ return function () { var _getDragState = getDragState(), isDragging = _getDragState.isDragging; if (isDragging) { (0, _cancelDrag.cancelPointerDrag)(); } }; }, [getDragState]); var updateDrag = (0, _react.useCallback)(function (_ref3) { var targetLocation = _ref3.targetLocation, _ref3$isImmediate = _ref3.isImmediate, isImmediate = _ref3$isImmediate === void 0 ? false : _ref3$isImmediate; if (!dragStateRef.current.isDragging) { /** * If there is no ongoing drag, then don't do anything. * * This should never occur, but treating it as a noop is more * reasonable than an invariant. */ return; } var _dragStateRef$current = dragStateRef.current, prevDestination = _dragStateRef$current.prevDestination, draggableId = _dragStateRef$current.draggableId, type = _dragStateRef$current.type, sourceLocation = _dragStateRef$current.sourceLocation; /** * Computes where it would actually move to */ var nextDestination = (0, _getDestination.getActualDestination)({ start: sourceLocation, target: targetLocation }); if ((0, _draggableLocation.isSameLocation)(prevDestination, nextDestination)) { return; } Object.assign(dragStateRef.current, { prevDestination: nextDestination, sourceLocation: sourceLocation, targetLocation: targetLocation }); var update = { mode: dragStateRef.current.mode, draggableId: draggableId, type: type, source: sourceLocation, destination: nextDestination, combine: null // not supported by migration layer }; var droppable = targetLocation ? droppableRegistry.getEntry({ droppableId: targetLocation.droppableId }) : null; /** * This event exists solely to ensure that the drop indicator updates * before the drag preview. */ lifecycle.dispatch('onPrePendingDragUpdate', { update: update, targetLocation: targetLocation }); lifecycle.dispatch('onPendingDragUpdate', { update: update, targetLocation: targetLocation, droppable: droppable }); function dispatchConsumerLifecycleEvent() { var _getProvided = (0, _screenReader.getProvided)('onDragUpdate', update), provided = _getProvided.provided, getMessage = _getProvided.getMessage; onDragUpdate === null || onDragUpdate === void 0 || onDragUpdate(update, provided); (0, _liveRegion.announce)(getMessage()); } if (isImmediate) { dispatchConsumerLifecycleEvent(); } else { schedule(dispatchConsumerLifecycleEvent); } }, [droppableRegistry, lifecycle, onDragUpdate, schedule]); var startDrag = (0, _react.useCallback)(function (_ref4) { var draggableId = _ref4.draggableId, type = _ref4.type, getSourceLocation = _ref4.getSourceLocation, sourceElement = _ref4.sourceElement, mode = _ref4.mode; if (dragStateRef.current.isDragging) { /** * If there is already an ongoing drag, then don't do anything. * * This should never occur, but treating it as a noop is more * reasonable than an invariant. */ return; } var before = { draggableId: draggableId, mode: mode }; // This is called in `onDragStart` rather than `onGenerateDragPreview` // to avoid a browser bug. Some DOM manipulations can cancel // the drag if they happen early in the drag. // <https://bugs.chromium.org/p/chromium/issues/detail?id=674882> onBeforeCapture === null || onBeforeCapture === void 0 || onBeforeCapture(before); var start = { mode: mode, draggableId: draggableId, type: type, source: getSourceLocation() }; /** * If the active element is a drag handle, then * we want to restore focus to it after the drag. * * This matches the behavior of `react-beautiful-dnd`. */ var _document = document, activeElement = _document.activeElement; var dragHandleDraggableId = activeElement instanceof HTMLElement && activeElement.hasAttribute(_attributes.attributes.dragHandle.draggableId) ? (0, _attributes.getAttribute)(activeElement, _attributes.attributes.dragHandle.draggableId) : null; var droppableId = start.source.droppableId; var droppable = droppableRegistry.getEntry({ droppableId: droppableId }); (0, _rbdInvariant.rbdInvariant)(droppable, "should have entry for droppable '".concat(droppableId, "'")); dragStateRef.current = { isDragging: true, mode: mode, draggableDimensions: (0, _useCapturedDimensions.getDraggableDimensions)(sourceElement), restoreFocusTo: dragHandleDraggableId, draggableId: draggableId, type: type, prevDestination: start.source, sourceLocation: start.source, targetLocation: start.source, draggableInitialOffsetInSourceDroppable: getOffset({ element: sourceElement, mode: droppable.mode }) }; onBeforeDragStart === null || onBeforeDragStart === void 0 || onBeforeDragStart(start); /** * This is used to signal to <Draggable> and <Droppable> elements * to update their state. * * This must be synchronous so that they have updated their state * by the time that `DragStart` is published. */ lifecycle.dispatch('onPendingDragStart', { start: start, droppable: droppable }); // rbd's `onDragStart` is called in the next event loop (via `setTimeout`) // // We can safely assume that the React state updates have occurred by // now, and that the updated `snapshot` has been provided. // <https://twitter.com/alexandereardon/status/1585784101885263872> schedule(function () { var start = { mode: mode, draggableId: draggableId, type: type, source: getSourceLocation() }; var _getProvided2 = (0, _screenReader.getProvided)('onDragStart', start), provided = _getProvided2.provided, getMessage = _getProvided2.getMessage; onDragStart === null || onDragStart === void 0 || onDragStart(start, provided); (0, _liveRegion.announce)(getMessage()); /** * If the droppable is initially disabled, then we publish an * immediate `DragUpdate` with a new non-disabled destination. * * This is typically `destination === null` but can be a parent * droppable if there are nested droppables. * * `react-beautiful-dnd` does this for mouse drags, * but not for keyboard drags. This is likely a bug, and the migration * layer will publish an update for all types of drags. * * This is scheduled so that state changes that occurred in the * rbd `onDragStart` will have taken effect. That is, * a synchronous `setIsDropDisabled(true)` call in the consumer's * `onDragStart` should result in an immediate update here. */ schedule(function () { var droppableId = start.source.droppableId; var droppable = droppableRegistry.getEntry({ droppableId: droppableId }); if (droppable !== null && droppable !== void 0 && droppable.isDropDisabled) { var targetLocation = getClosestEnabledDraggableLocation({ droppableId: droppableId }); updateDrag({ targetLocation: targetLocation, isImmediate: true }); } }); }); }, [droppableRegistry, getClosestEnabledDraggableLocation, lifecycle, onBeforeCapture, onBeforeDragStart, onDragStart, schedule, updateDrag]); var keyboardCleanupManager = (0, _useCleanupFn.useCleanupFn)(); var stopDrag = (0, _react.useCallback)(function (_ref5) { var reason = _ref5.reason; if (!dragStateRef.current.isDragging) { /** * If there is no ongoing drag, then don't do anything. * * This should never occur, but treating it as a noop is more * reasonable than an invariant. */ return; } keyboardCleanupManager.runCleanupFn(); /** * If this is a cancel, then an update to a null * destination will be made. (Unless it is already null) * * This is different to `react-beautiful-dnd` and exists * to standardize behavior between mouse and keyboard drags. * * This is required because of a behavior in native drag and * drop, where a `dragend` will fire exit events on every * drop target you are over. This results in an unavoidable * null destination update for mouse drags. */ if (reason === 'CANCEL') { updateDrag({ targetLocation: null }); } var _dragStateRef$current2 = dragStateRef.current, mode = _dragStateRef$current2.mode, restoreFocusTo = _dragStateRef$current2.restoreFocusTo, sourceLocation = _dragStateRef$current2.sourceLocation, targetLocation = _dragStateRef$current2.targetLocation, type = _dragStateRef$current2.type, draggableId = _dragStateRef$current2.draggableId; dragStateRef.current = { isDragging: false }; flush(); var destination = (0, _getDestination.getActualDestination)({ start: sourceLocation, target: targetLocation }); var result = { // We are saying all null destination drops count as a CANCEL reason: destination === null ? 'CANCEL' : 'DROP', type: type, source: sourceLocation, destination: destination, mode: mode, draggableId: draggableId, combine: null // not supported by migration layer }; /** * Tells <Draggable> instances to cleanup. */ lifecycle.dispatch('onBeforeDragEnd', { draggableId: draggableId }); var _getProvided3 = (0, _screenReader.getProvided)('onDragEnd', result), provided = _getProvided3.provided, getMessage = _getProvided3.getMessage; onDragEnd(result, provided); (0, _liveRegion.announce)(getMessage()); if (restoreFocusTo) { /** * The `requestAnimationFrame` matches `react-beautiful-dnd`. * * It is required to wait for React state updates to have taken effect. * Otherwise we might try to focus an element that no longer exists. */ requestAnimationFrame(function () { var dragHandle = (0, _findDragHandle.findDragHandle)({ contextId: contextId, draggableId: draggableId }); if (!dragHandle) { return; } dragHandle.focus(); }); } }, [contextId, flush, keyboardCleanupManager, lifecycle, onDragEnd, updateDrag]); var dragController = (0, _react.useMemo)(function () { return { getDragState: getDragState, startDrag: startDrag, updateDrag: updateDrag, stopDrag: stopDrag }; }, [getDragState, startDrag, stopDrag, updateDrag]); (0, _usePointerControls.usePointerControls)({ dragController: dragController, contextId: contextId }); var _useKeyboardControls = (0, _useKeyboardControls2.useKeyboardControls)({ dragController: dragController, droppableRegistry: droppableRegistry, contextId: contextId, setKeyboardCleanupFn: keyboardCleanupManager.setCleanupFn }), startKeyboardDrag = _useKeyboardControls.startKeyboardDrag; /** * If a droppable becomes disabled during a drag, then a new destination * should be found and published in a `DragUpdate`. */ var onDroppableUpdate = (0, _react.useCallback)(function (entry) { var _dragState$targetLoca; var dragState = dragStateRef.current; if (!dragState.isDragging) { return; } if (!entry.isDropDisabled) { return; } if (entry.droppableId !== ((_dragState$targetLoca = dragState.targetLocation) === null || _dragState$targetLoca === void 0 ? void 0 : _dragState$targetLoca.droppableId)) { return; } var targetLocation = getClosestEnabledDraggableLocation({ droppableId: entry.droppableId }); updateDrag({ targetLocation: targetLocation }); }, [getClosestEnabledDraggableLocation, updateDrag]); droppableRegistry.setUpdateListener(onDroppableUpdate); (0, _useStyleMarshal.default)({ contextId: contextId, nonce: nonce }); return /*#__PURE__*/_react.default.createElement(_errorBoundary.ErrorBoundary, { contextId: contextId, dragController: dragController }, /*#__PURE__*/_react.default.createElement(_lifecycleContext.LifecycleContextProvider, { lifecycle: lifecycle }, /*#__PURE__*/_react.default.createElement(_internalContext.DragDropContextProvider, { contextId: contextId, getDragState: getDragState, startKeyboardDrag: startKeyboardDrag, droppableRegistry: droppableRegistry }, children))); }