@wordpress/block-editor
Version:
307 lines (306 loc) • 10.5 kB
JavaScript
// 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