UNPKG

@wordpress/block-editor

Version:
214 lines (180 loc) 7.41 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = useBlockDropZone; exports.getDropTargetPosition = getDropTargetPosition; var _data = require("@wordpress/data"); var _element = require("@wordpress/element"); var _compose = require("@wordpress/compose"); var _i18n = require("@wordpress/i18n"); var _blocks = require("@wordpress/blocks"); var _useOnBlockDrop = _interopRequireDefault(require("../use-on-block-drop")); var _math = require("../../utils/math"); var _store = require("../../store"); var _lockUnlock = require("../../lock-unlock"); /** * WordPress dependencies */ /** * Internal dependencies */ /** @typedef {import('../../utils/math').WPPoint} WPPoint */ /** @typedef {import('../use-on-block-drop/types').WPDropOperation} WPDropOperation */ /** * The orientation of a block list. * * @typedef {'horizontal'|'vertical'|undefined} WPBlockListOrientation */ /** * The insert position when dropping a block. * * @typedef {'before'|'after'} WPInsertPosition */ /** * @typedef {Object} WPBlockData * @property {boolean} isUnmodifiedDefaultBlock Is the block unmodified default block. * @property {() => DOMRect} getBoundingClientRect Get the bounding client rect of the block. * @property {number} blockIndex The index of the block. */ /** * Get the drop target position from a given drop point and the orientation. * * @param {WPBlockData[]} blocksData The block data list. * @param {WPPoint} position The position of the item being dragged. * @param {WPBlockListOrientation} orientation The orientation of the block list. * @return {[number, WPDropOperation]} The drop target position. */ function getDropTargetPosition(blocksData, position, orientation = 'vertical') { const allowedEdges = orientation === 'horizontal' ? ['left', 'right'] : ['top', 'bottom']; const isRightToLeft = (0, _i18n.isRTL)(); let nearestIndex = 0; let insertPosition = 'before'; let minDistance = Infinity; blocksData.forEach(({ isUnmodifiedDefaultBlock, getBoundingClientRect, blockIndex }) => { const rect = getBoundingClientRect(); let [distance, edge] = (0, _math.getDistanceToNearestEdge)(position, rect, allowedEdges); // Prioritize the element if the point is inside of an unmodified default block. if (isUnmodifiedDefaultBlock && (0, _math.isPointContainedByRect)(position, rect)) { distance = 0; } if (distance < minDistance) { // Where the dropped block will be inserted on the nearest block. insertPosition = edge === 'bottom' || !isRightToLeft && edge === 'right' || isRightToLeft && edge === 'left' ? 'after' : 'before'; // Update the currently known best candidate. minDistance = distance; nearestIndex = blockIndex; } }); const adjacentIndex = nearestIndex + (insertPosition === 'after' ? 1 : -1); const isNearestBlockUnmodifiedDefaultBlock = !!blocksData[nearestIndex]?.isUnmodifiedDefaultBlock; const isAdjacentBlockUnmodifiedDefaultBlock = !!blocksData[adjacentIndex]?.isUnmodifiedDefaultBlock; // If both blocks are not unmodified default blocks then just insert between them. if (!isNearestBlockUnmodifiedDefaultBlock && !isAdjacentBlockUnmodifiedDefaultBlock) { // If the user is dropping to the trailing edge of the block // add 1 to the index to represent dragging after. const insertionIndex = insertPosition === 'after' ? nearestIndex + 1 : nearestIndex; return [insertionIndex, 'insert']; } // Otherwise, replace the nearest unmodified default block. return [isNearestBlockUnmodifiedDefaultBlock ? nearestIndex : adjacentIndex, 'replace']; } /** * @typedef {Object} WPBlockDropZoneConfig * @property {string} rootClientId The root client id for the block list. */ /** * A React hook that can be used to make a block list handle drag and drop. * * @param {WPBlockDropZoneConfig} dropZoneConfig configuration data for the drop zone. */ function useBlockDropZone({ // An undefined value represents a top-level block. Default to an empty // string for this so that `targetRootClientId` can be easily compared to // values returned by the `getRootBlockClientId` selector, which also uses // an empty string to represent top-level blocks. rootClientId: targetRootClientId = '' } = {}) { const registry = (0, _data.useRegistry)(); const [dropTarget, setDropTarget] = (0, _element.useState)({ index: null, operation: 'insert' }); const isDisabled = (0, _data.useSelect)(select => { const { __unstableIsWithinBlockOverlay, __unstableHasActiveBlockOverlayActive, getBlockEditingMode } = (0, _lockUnlock.unlock)(select(_store.store)); const blockEditingMode = getBlockEditingMode(targetRootClientId); return blockEditingMode !== 'default' || __unstableHasActiveBlockOverlayActive(targetRootClientId) || __unstableIsWithinBlockOverlay(targetRootClientId); }, [targetRootClientId]); const { getBlockListSettings, getBlocks, getBlockIndex } = (0, _data.useSelect)(_store.store); const { showInsertionPoint, hideInsertionPoint } = (0, _data.useDispatch)(_store.store); const onBlockDrop = (0, _useOnBlockDrop.default)(targetRootClientId, dropTarget.index, { operation: dropTarget.operation }); const throttled = (0, _compose.useThrottle)((0, _element.useCallback)((event, ownerDocument) => { const blocks = getBlocks(targetRootClientId); // The block list is empty, don't show the insertion point but still allow dropping. if (blocks.length === 0) { registry.batch(() => { setDropTarget({ index: 0, operation: 'insert' }); showInsertionPoint(targetRootClientId, 0, { operation: 'insert' }); }); return; } const blocksData = blocks.map(block => { const clientId = block.clientId; return { isUnmodifiedDefaultBlock: (0, _blocks.isUnmodifiedDefaultBlock)(block), getBoundingClientRect: () => ownerDocument.getElementById(`block-${clientId}`).getBoundingClientRect(), blockIndex: getBlockIndex(clientId) }; }); const [targetIndex, operation] = getDropTargetPosition(blocksData, { x: event.clientX, y: event.clientY }, getBlockListSettings(targetRootClientId)?.orientation); registry.batch(() => { setDropTarget({ index: targetIndex, operation }); showInsertionPoint(targetRootClientId, targetIndex, { operation }); }); }, [getBlocks, targetRootClientId, getBlockListSettings, registry, showInsertionPoint, getBlockIndex]), 200); return (0, _compose.__experimentalUseDropZone)({ isDisabled, onDrop: onBlockDrop, onDragOver(event) { // `currentTarget` is only available while the event is being // handled, so get it now and pass it to the thottled function. // https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget throttled(event, event.currentTarget.ownerDocument); }, onDragLeave() { throttled.cancel(); hideInsertionPoint(); }, onDragEnd() { throttled.cancel(); hideInsertionPoint(); } }); } //# sourceMappingURL=index.js.map