@wordpress/block-editor
Version:
201 lines (200 loc) • 6.27 kB
JavaScript
// 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