UNPKG

@wordpress/block-editor

Version:
152 lines (151 loc) 5.62 kB
// packages/block-editor/src/components/writing-flow/use-tab-nav.js import { focus, isFormElement } from "@wordpress/dom"; import { TAB } from "@wordpress/keycodes"; import { useSelect, useDispatch } from "@wordpress/data"; import { useRefEffect, useMergeRefs } from "@wordpress/compose"; import { useRef } from "@wordpress/element"; import { store as blockEditorStore } from "../../store"; import { isInSameBlock, isInsideRootBlock } from "../../utils/dom"; import { unlock } from "../../lock-unlock"; import { jsx } from "react/jsx-runtime"; function useTabNav() { const containerRef = ( /** @type {typeof useRef<HTMLElement>} */ useRef() ); const focusCaptureBeforeRef = useRef(); const focusCaptureAfterRef = useRef(); const { hasMultiSelection, getSelectedBlockClientId, getBlockCount, getBlockOrder, getLastFocus, getSectionRootClientId, isZoomOut } = unlock(useSelect(blockEditorStore)); const { setLastFocus } = unlock(useDispatch(blockEditorStore)); const noCaptureRef = useRef(); function onFocusCapture(event) { const canvasElement = containerRef.current.ownerDocument === event.target.ownerDocument ? containerRef.current : containerRef.current.ownerDocument.defaultView.frameElement; if (noCaptureRef.current) { noCaptureRef.current = null; } else if (hasMultiSelection()) { containerRef.current.focus(); } else if (getSelectedBlockClientId()) { if (getLastFocus()?.current) { getLastFocus().current.focus(); } else { containerRef.current.querySelector( `[data-block="${getSelectedBlockClientId()}"]` ).focus(); } } else if (isZoomOut()) { const sectionRootClientId = getSectionRootClientId(); const sectionBlocks = getBlockOrder(sectionRootClientId); if (sectionBlocks.length) { containerRef.current.querySelector(`[data-block="${sectionBlocks[0]}"]`).focus(); } else if (sectionRootClientId) { containerRef.current.querySelector(`[data-block="${sectionRootClientId}"]`).focus(); } else { canvasElement.focus(); } } else { const isBefore = ( // eslint-disable-next-line no-bitwise event.target.compareDocumentPosition(canvasElement) & event.target.DOCUMENT_POSITION_FOLLOWING ); const tabbables = focus.tabbable.find(containerRef.current); if (tabbables.length) { const next = isBefore ? tabbables[0] : tabbables[tabbables.length - 1]; next.focus(); } } } const before = /* @__PURE__ */ jsx( "div", { ref: focusCaptureBeforeRef, tabIndex: "0", onFocus: onFocusCapture } ); const after = /* @__PURE__ */ jsx( "div", { ref: focusCaptureAfterRef, tabIndex: "0", onFocus: onFocusCapture } ); const ref = useRefEffect((node) => { function onKeyDown(event) { if (event.defaultPrevented) { return; } if (event.keyCode !== TAB) { return; } if ( // Bails in case the focus capture elements aren’t present. They // may be omitted to avoid silent tab stops in preview mode. // See: https://github.com/WordPress/gutenberg/pull/59317 !focusCaptureAfterRef.current || !focusCaptureBeforeRef.current ) { return; } const { target, shiftKey: isShift } = event; const direction = isShift ? "findPrevious" : "findNext"; const nextTabbable = focus.tabbable[direction](target); const currentBlock = target.closest("[data-block]"); const isElementPartOfSelectedBlock = currentBlock && nextTabbable && (isInSameBlock(currentBlock, nextTabbable) || isInsideRootBlock(currentBlock, nextTabbable)); if (isFormElement(nextTabbable) && isElementPartOfSelectedBlock) { return; } const next = isShift ? focusCaptureBeforeRef : focusCaptureAfterRef; noCaptureRef.current = true; next.current.focus({ preventScroll: true }); } function onFocusOut(event) { setLastFocus({ ...getLastFocus(), current: event.target }); const { ownerDocument: ownerDocument2 } = node; if (!event.relatedTarget && event.target.hasAttribute("data-block") && ownerDocument2.activeElement === ownerDocument2.body && getBlockCount() === 0) { node.focus(); } } function preventScrollOnTab(event) { if (event.keyCode !== TAB) { return; } if (event.target?.getAttribute("role") === "region") { return; } if (containerRef.current === event.target) { return; } const isShift = event.shiftKey; const direction = isShift ? "findPrevious" : "findNext"; const target = focus.tabbable[direction](event.target); if (target === focusCaptureBeforeRef.current || target === focusCaptureAfterRef.current) { event.preventDefault(); target.focus({ preventScroll: true }); } } const { ownerDocument } = node; const { defaultView } = ownerDocument; defaultView.addEventListener("keydown", preventScrollOnTab); node.addEventListener("keydown", onKeyDown); node.addEventListener("focusout", onFocusOut); return () => { defaultView.removeEventListener("keydown", preventScrollOnTab); node.removeEventListener("keydown", onKeyDown); node.removeEventListener("focusout", onFocusOut); }; }, []); const mergedRefs = useMergeRefs([containerRef, ref]); return [before, mergedRefs, after]; } export { useTabNav as default }; //# sourceMappingURL=use-tab-nav.js.map