UNPKG

@wordpress/block-editor

Version:
183 lines (173 loc) 5.89 kB
/** * External dependencies */ import { runOnJS, useDerivedValue, useSharedValue } from 'react-native-reanimated'; /** * WordPress dependencies */ import { useSelect } from '@wordpress/data'; import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ import { store as blockEditorStore } from '../../store'; import { useBlockListContext } from '../block-list/block-list-context'; import { getDistanceToNearestEdge } from '../../utils/math'; import useOnBlockDrop from '../use-on-block-drop'; const UPDATE_TARGET_BLOCK_INDEX_THRESHOLD = 20; // In pixels /** @typedef {import('../../utils/math').WPPoint} WPPoint */ /** * The orientation of a block list. * * @typedef {'horizontal'|'vertical'|undefined} WPBlockListOrientation */ /** * Given a list of blocks layouts finds the index that a block should be dropped at. * * @param {Object} blocksLayouts Blocks layouts object. * @param {WPPoint} position The position of the item being dragged. * @param {WPBlockListOrientation} orientation The orientation of a block list. * @param {boolean} isRTL Check if current locale is RTL. * * @return {number|undefined} The block index that's closest to the drag position. */ export function getNearestBlockIndex(blocksLayouts, position, orientation, isRTL) { const allowedEdges = orientation === 'horizontal' ? ['left', 'right'] : ['top', 'bottom']; const isRightToLeft = isRTL; let candidateIndex; let candidateDistance; // Only enabled for root level blocks. blocksLayouts.forEach((element, index) => { const { x, y, width, height } = element; const rect = { x: element.x, y: element.y, top: y, right: x + width, bottom: y + height, left: x, width, height }; const [distance, edge] = getDistanceToNearestEdge(position, rect, allowedEdges); if (candidateDistance === undefined || distance < candidateDistance) { // If the user is dropping to the trailing edge of the block // add 1 to the index to represent dragging after. // Take RTL languages into account where the left edge is // the trailing edge. const isTrailingEdge = edge === 'bottom' || !isRightToLeft && edge === 'right' || isRightToLeft && edge === 'left'; const offset = isTrailingEdge ? 1 : 0; // Update the currently known best candidate. candidateDistance = distance; candidateIndex = index + offset; } }); return candidateIndex; } /** * @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. * * @return {Object} An object that contains `targetBlockIndex` and the event * handlers `onBlockDragOver`, `onBlockDragEnd` and `onBlockDrop`. */ export default 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 targetBlockIndex = useSharedValue(null); const dragPosition = { x: useSharedValue(0), y: useSharedValue(0) }; const prevDragPosition = { x: useSharedValue(0), y: useSharedValue(0) }; const { getBlockListSettings, getSettings } = useSelect(blockEditorStore); const { blocksLayouts, getBlockLayoutsOrderedByYCoord } = useBlockListContext(); const getSortedBlocksLayouts = useCallback(() => { return getBlockLayoutsOrderedByYCoord(blocksLayouts.current); // We use the value of `blocksLayouts` as the dependency. }, [blocksLayouts.current]); const isRTL = getSettings().isRTL; const onBlockDrop = useOnBlockDrop(); const updateTargetBlockIndex = useCallback(event => { const sortedBlockLayouts = getSortedBlocksLayouts(); const targetIndex = getNearestBlockIndex(sortedBlockLayouts, { x: event.x, y: event.y }, getBlockListSettings(targetRootClientId)?.orientation, isRTL); if (targetIndex !== null) { targetBlockIndex.value = targetIndex !== null && targetIndex !== void 0 ? targetIndex : 0; } }, [getSortedBlocksLayouts, getBlockListSettings, targetRootClientId, isRTL, targetBlockIndex]); useDerivedValue(() => { const x = dragPosition.x.value; const y = dragPosition.y.value; const prevX = prevDragPosition.x.value; const prevY = prevDragPosition.y.value; // `updateTargetBlockIndex` performs expensive calculations, so we throttle // the call using a offset threshold based on the dragging position. if (Math.abs(x - prevX) >= UPDATE_TARGET_BLOCK_INDEX_THRESHOLD || Math.abs(y - prevY) >= UPDATE_TARGET_BLOCK_INDEX_THRESHOLD) { runOnJS(updateTargetBlockIndex)({ x, y }); prevDragPosition.x.value = x; prevDragPosition.y.value = y; return true; } return false; }); return { onBlockDragOver({ x, y }) { dragPosition.x.value = x; dragPosition.y.value = y; }, onBlockDragOverWorklet({ x, y }) { 'worklet'; dragPosition.x.value = x; dragPosition.y.value = y; }, onBlockDragEnd() { targetBlockIndex.value = null; }, onBlockDrop: event => { if (targetBlockIndex.value !== null) { onBlockDrop({ ...event, targetRootClientId, targetBlockIndex: targetBlockIndex.value }); } }, targetBlockIndex }; } //# sourceMappingURL=index.native.js.map