UNPKG

@wordpress/block-editor

Version:
201 lines (200 loc) 6.27 kB
// packages/block-editor/src/components/writing-flow/use-arrow-nav.js import { computeCaretRect, focus, isHorizontalEdge, isVerticalEdge, placeCaretAtHorizontalEdge, placeCaretAtVerticalEdge, isRTL } from "@wordpress/dom"; import { UP, DOWN, LEFT, RIGHT } from "@wordpress/keycodes"; import { useDispatch, useSelect } from "@wordpress/data"; import { useRefEffect } from "@wordpress/compose"; import { getBlockClientId, isInSameBlock } from "../../utils/dom"; import { store as blockEditorStore } from "../../store"; function isNavigationCandidate(element, keyCode, hasModifier) { const isVertical = keyCode === UP || keyCode === DOWN; const { tagName } = element; const elementType = element.getAttribute("type"); if (isVertical && !hasModifier) { if (tagName === "INPUT") { const verticalInputTypes = [ "date", "datetime-local", "month", "number", "range", "time", "week" ]; return !verticalInputTypes.includes(elementType); } return true; } if (tagName === "INPUT") { const simpleInputTypes = [ "button", "checkbox", "number", "color", "file", "image", "radio", "reset", "submit" ]; return simpleInputTypes.includes(elementType); } return tagName !== "TEXTAREA"; } function getClosestTabbable(target, isReverse, containerElement, onlyVertical) { let focusableNodes = focus.focusable.find(containerElement); if (isReverse) { focusableNodes.reverse(); } focusableNodes = focusableNodes.slice( focusableNodes.indexOf(target) + 1 ); let targetRect; if (onlyVertical) { targetRect = target.getBoundingClientRect(); } function isTabCandidate(node) { if (node.closest("[inert]")) { return; } if (node.children.length === 1 && isInSameBlock(node, node.firstElementChild) && node.firstElementChild.getAttribute("contenteditable") === "true") { return; } if (!focus.tabbable.isTabbableIndex(node)) { return false; } if (node.isContentEditable && node.contentEditable !== "true") { return false; } if (onlyVertical) { const nodeRect = node.getBoundingClientRect(); if (nodeRect.left >= targetRect.right || nodeRect.right <= targetRect.left) { return false; } } return true; } return focusableNodes.find(isTabCandidate); } function useArrowNav() { const { getMultiSelectedBlocksStartClientId, getMultiSelectedBlocksEndClientId, getSettings, hasMultiSelection, __unstableIsFullySelected } = useSelect(blockEditorStore); const { selectBlock } = useDispatch(blockEditorStore); return useRefEffect((node) => { let verticalRect; function onMouseDown() { verticalRect = null; } function isClosestTabbableABlock(target, isReverse) { const closestTabbable = getClosestTabbable( target, isReverse, node ); return closestTabbable && getBlockClientId(closestTabbable); } function onKeyDown(event) { if (event.defaultPrevented) { return; } const { keyCode, target, shiftKey, ctrlKey, altKey, metaKey } = event; const isUp = keyCode === UP; const isDown = keyCode === DOWN; const isLeft = keyCode === LEFT; const isRight = keyCode === RIGHT; const isReverse = isUp || isLeft; const isHorizontal = isLeft || isRight; const isVertical = isUp || isDown; const isNav = isHorizontal || isVertical; const hasModifier = shiftKey || ctrlKey || altKey || metaKey; const isNavEdge = isVertical ? isVerticalEdge : isHorizontalEdge; const { ownerDocument } = node; const { defaultView } = ownerDocument; if (!isNav) { return; } if (hasMultiSelection()) { if (shiftKey) { return; } if (!__unstableIsFullySelected()) { return; } event.preventDefault(); if (isReverse) { selectBlock(getMultiSelectedBlocksStartClientId()); } else { selectBlock(getMultiSelectedBlocksEndClientId(), -1); } return; } if (!isNavigationCandidate(target, keyCode, hasModifier)) { return; } if (!isVertical) { verticalRect = null; } else if (!verticalRect) { verticalRect = computeCaretRect(defaultView); } const isReverseDir = isRTL(target) ? !isReverse : isReverse; const { keepCaretInsideBlock } = getSettings(); if (shiftKey) { if (isClosestTabbableABlock(target, isReverse) && isNavEdge(target, isReverse)) { node.contentEditable = true; node.focus(); } } else if (isVertical && isVerticalEdge(target, isReverse) && // When Alt is pressed, only intercept if the caret is also at // the horizontal edge. (altKey ? isHorizontalEdge(target, isReverseDir) : true) && !keepCaretInsideBlock) { const closestTabbable = getClosestTabbable( target, isReverse, node, true ); if (closestTabbable) { placeCaretAtVerticalEdge( closestTabbable, // When Alt is pressed, place the caret at the furthest // horizontal edge and the furthest vertical edge. altKey ? !isReverse : isReverse, altKey ? void 0 : verticalRect ); event.preventDefault(); } } else if (isHorizontal && defaultView.getSelection().isCollapsed && isHorizontalEdge(target, isReverseDir) && !keepCaretInsideBlock) { const closestTabbable = getClosestTabbable( target, isReverseDir, node ); placeCaretAtHorizontalEdge(closestTabbable, isReverse); event.preventDefault(); } } node.addEventListener("mousedown", onMouseDown); node.addEventListener("keydown", onKeyDown); return () => { node.removeEventListener("mousedown", onMouseDown); node.removeEventListener("keydown", onKeyDown); }; }, []); } export { useArrowNav as default, getClosestTabbable, isNavigationCandidate }; //# sourceMappingURL=use-arrow-nav.js.map