UNPKG

@wordpress/block-editor

Version:
307 lines (306 loc) 10.5 kB
// packages/block-editor/src/components/list-view/use-list-view-drop-zone.js import { useSelect } from "@wordpress/data"; import { useState, useCallback, useEffect } from "@wordpress/element"; import { useThrottle, __experimentalUseDropZone as useDropZone, usePrevious } from "@wordpress/compose"; import { isRTL } from "@wordpress/i18n"; import { getDistanceToNearestEdge, isPointContainedByRect } from "../../utils/math.mjs"; import useOnBlockDrop from "../use-on-block-drop/index.mjs"; import { store as blockEditorStore } from "../../store/index.mjs"; var NESTING_LEVEL_INDENTATION = 24; function isUpGesture(point, rect, nestingLevel = 1, rtl = false) { const blockIndentPosition = rtl ? rect.right - nestingLevel * NESTING_LEVEL_INDENTATION : rect.left + nestingLevel * NESTING_LEVEL_INDENTATION; return rtl ? point.x > blockIndentPosition : point.x < blockIndentPosition; } function getDesiredRelativeParentLevel(point, rect, nestingLevel = 1, rtl = false) { const blockIndentPosition = rtl ? rect.right - nestingLevel * NESTING_LEVEL_INDENTATION : rect.left + nestingLevel * NESTING_LEVEL_INDENTATION; const distanceBetweenPointAndBlockIndentPosition = rtl ? blockIndentPosition - point.x : point.x - blockIndentPosition; const desiredParentLevel = Math.round( distanceBetweenPointAndBlockIndentPosition / NESTING_LEVEL_INDENTATION ); return Math.abs(desiredParentLevel); } function getCandidateBlockParents(candidateBlockData, blocksData) { const candidateBlockParents = []; let currentBlockData = candidateBlockData; while (currentBlockData) { candidateBlockParents.push({ ...currentBlockData }); currentBlockData = blocksData.find( (blockData) => blockData.clientId === currentBlockData.rootClientId ); } return candidateBlockParents; } function getNextNonDraggedBlock(blocksData, index) { const nextBlockData = blocksData[index + 1]; if (nextBlockData && nextBlockData.isDraggedBlock) { return getNextNonDraggedBlock(blocksData, index + 1); } return nextBlockData; } function isNestingGesture(point, rect, nestingLevel = 1, rtl = false) { const blockIndentPosition = rtl ? rect.right - nestingLevel * NESTING_LEVEL_INDENTATION : rect.left + nestingLevel * NESTING_LEVEL_INDENTATION; const isNestingHorizontalGesture = rtl ? point.x < blockIndentPosition - NESTING_LEVEL_INDENTATION : point.x > blockIndentPosition + NESTING_LEVEL_INDENTATION; return isNestingHorizontalGesture && point.y < rect.bottom; } var ALLOWED_DROP_EDGES = ["top", "bottom"]; function getListViewDropTarget(blocksData, position, rtl = false) { let candidateEdge; let candidateBlockData; let candidateDistance; let candidateRect; let candidateBlockIndex; for (let i = 0; i < blocksData.length; i++) { const blockData = blocksData[i]; if (blockData.isDraggedBlock) { continue; } const rect = blockData.element.getBoundingClientRect(); const [distance, edge] = getDistanceToNearestEdge( position, rect, ALLOWED_DROP_EDGES ); const isCursorWithinBlock = isPointContainedByRect(position, rect); if (candidateDistance === void 0 || distance < candidateDistance || isCursorWithinBlock) { candidateDistance = distance; const index = blocksData.indexOf(blockData); const previousBlockData = blocksData[index - 1]; if (edge === "top" && previousBlockData && previousBlockData.rootClientId === blockData.rootClientId && !previousBlockData.isDraggedBlock) { candidateBlockData = previousBlockData; candidateEdge = "bottom"; candidateRect = previousBlockData.element.getBoundingClientRect(); candidateBlockIndex = index - 1; } else { candidateBlockData = blockData; candidateEdge = edge; candidateRect = rect; candidateBlockIndex = index; } if (isCursorWithinBlock) { break; } } } if (!candidateBlockData) { return; } const candidateBlockParents = getCandidateBlockParents( candidateBlockData, blocksData ); const isDraggingBelow = candidateEdge === "bottom"; if (isDraggingBelow && candidateBlockData.canInsertDraggedBlocksAsChild && (candidateBlockData.innerBlockCount > 0 && candidateBlockData.isExpanded || isNestingGesture( position, candidateRect, candidateBlockParents.length, rtl ))) { const newBlockIndex = candidateBlockData.isExpanded ? 0 : candidateBlockData.innerBlockCount || 0; return { rootClientId: candidateBlockData.clientId, clientId: candidateBlockData.clientId, blockIndex: newBlockIndex, dropPosition: "inside" }; } if (isDraggingBelow && candidateBlockData.rootClientId && isUpGesture( position, candidateRect, candidateBlockParents.length, rtl )) { const nextBlock = getNextNonDraggedBlock( blocksData, candidateBlockIndex ); const currentLevel = candidateBlockData.nestingLevel; const nextLevel = nextBlock ? nextBlock.nestingLevel : 1; if (currentLevel && nextLevel) { const desiredRelativeLevel = getDesiredRelativeParentLevel( position, candidateRect, candidateBlockParents.length, rtl ); const targetParentIndex = Math.max( Math.min(desiredRelativeLevel, currentLevel - nextLevel), 0 ); if (candidateBlockParents[targetParentIndex]) { let newBlockIndex = candidateBlockData.blockIndex; if (candidateBlockParents[targetParentIndex].nestingLevel === nextBlock?.nestingLevel) { newBlockIndex = nextBlock?.blockIndex; } else { for (let i = candidateBlockIndex; i >= 0; i--) { const blockData = blocksData[i]; if (blockData.rootClientId === candidateBlockParents[targetParentIndex].rootClientId) { newBlockIndex = blockData.blockIndex + 1; break; } } } return { rootClientId: candidateBlockParents[targetParentIndex].rootClientId, clientId: candidateBlockData.clientId, blockIndex: newBlockIndex, dropPosition: candidateEdge }; } } } if (!candidateBlockData.canInsertDraggedBlocksAsSibling) { return; } const offset = isDraggingBelow ? 1 : 0; return { rootClientId: candidateBlockData.rootClientId, clientId: candidateBlockData.clientId, blockIndex: candidateBlockData.blockIndex + offset, dropPosition: candidateEdge }; } var EXPAND_THROTTLE_OPTIONS = { leading: false, // Don't call the function immediately on the first call. trailing: true // Do call the function on the last call. }; function useListViewDropZone({ dropZoneElement, expandedState, setExpandedState }) { const { getBlockRootClientId, getBlockIndex, getBlockCount, getDraggedBlockClientIds, canInsertBlocks } = useSelect(blockEditorStore); const [target, setTarget] = useState(); const { rootClientId: targetRootClientId, blockIndex: targetBlockIndex } = target || {}; const onBlockDrop = useOnBlockDrop(targetRootClientId, targetBlockIndex); const rtl = isRTL(); const previousRootClientId = usePrevious(targetRootClientId); const maybeExpandBlock = useCallback( (_expandedState, _target) => { const { rootClientId } = _target || {}; if (!rootClientId) { return; } if (_target?.dropPosition === "inside" && !_expandedState[rootClientId]) { setExpandedState({ type: "expand", clientIds: [rootClientId] }); } }, [setExpandedState] ); const throttledMaybeExpandBlock = useThrottle( maybeExpandBlock, 500, EXPAND_THROTTLE_OPTIONS ); useEffect(() => { if (target?.dropPosition !== "inside" || previousRootClientId !== target?.rootClientId) { throttledMaybeExpandBlock.cancel(); return; } throttledMaybeExpandBlock(expandedState, target); }, [ expandedState, previousRootClientId, target, throttledMaybeExpandBlock ]); const draggedBlockClientIds = getDraggedBlockClientIds(); const throttled = useThrottle( useCallback( (event, currentTarget) => { const position = { x: event.clientX, y: event.clientY }; const isBlockDrag = !!draggedBlockClientIds?.length; const blockElements = Array.from( currentTarget.querySelectorAll("[data-block]") ); const blocksData = blockElements.map((blockElement) => { const clientId = blockElement.dataset.block; const isExpanded = blockElement.dataset.expanded === "true"; const isDraggedBlock = blockElement.classList.contains("is-dragging"); const nestingLevel = parseInt( blockElement.getAttribute("aria-level"), 10 ); const rootClientId = getBlockRootClientId(clientId); return { clientId, isExpanded, rootClientId, blockIndex: getBlockIndex(clientId), element: blockElement, nestingLevel: nestingLevel || void 0, isDraggedBlock: isBlockDrag ? isDraggedBlock : false, innerBlockCount: getBlockCount(clientId), canInsertDraggedBlocksAsSibling: isBlockDrag ? canInsertBlocks( draggedBlockClientIds, rootClientId ) : true, canInsertDraggedBlocksAsChild: isBlockDrag ? canInsertBlocks(draggedBlockClientIds, clientId) : true }; }); const newTarget = getListViewDropTarget( blocksData, position, rtl ); if (newTarget) { setTarget(newTarget); } }, [ canInsertBlocks, draggedBlockClientIds, getBlockCount, getBlockIndex, getBlockRootClientId, rtl ] ), 50 ); const ref = useDropZone({ dropZoneElement, onDrop(event) { throttled.cancel(); if (target) { onBlockDrop(event); } setTarget(void 0); }, onDragLeave() { throttled.cancel(); setTarget(null); }, onDragOver(event) { throttled(event, event.currentTarget); }, onDragEnd() { throttled.cancel(); setTarget(void 0); } }); return { ref, target }; } export { NESTING_LEVEL_INDENTATION, useListViewDropZone as default, getListViewDropTarget }; //# sourceMappingURL=use-list-view-drop-zone.mjs.map