UNPKG

@wordpress/compose

Version:
95 lines (88 loc) 3.06 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _dom = require("@wordpress/dom"); var _useRefEffect = _interopRequireDefault(require("../use-ref-effect")); /** * WordPress dependencies */ /** * Internal dependencies */ /** * In Dialogs/modals, the tabbing must be constrained to the content of * the wrapper element. This hook adds the behavior to the returned ref. * * @return {import('react').RefCallback<Element>} Element Ref. * * @example * ```js * import { useConstrainedTabbing } from '@wordpress/compose'; * * const ConstrainedTabbingExample = () => { * const constrainedTabbingRef = useConstrainedTabbing() * return ( * <div ref={ constrainedTabbingRef }> * <Button /> * <Button /> * </div> * ); * } * ``` */ function useConstrainedTabbing() { return (0, _useRefEffect.default)((/** @type {HTMLElement} */node) => { function onKeyDown(/** @type {KeyboardEvent} */event) { const { key, shiftKey, target } = event; if (key !== 'Tab') { return; } const action = shiftKey ? 'findPrevious' : 'findNext'; const nextElement = _dom.focus.tabbable[action](/** @type {HTMLElement} */target) || null; // When the target element contains the element that is about to // receive focus, for example when the target is a tabbable // container, browsers may disagree on where to move focus next. // In this case we can't rely on native browsers behavior. We need // to manage focus instead. // See https://github.com/WordPress/gutenberg/issues/46041. if (/** @type {HTMLElement} */target.contains(nextElement)) { event.preventDefault(); nextElement?.focus(); return; } // If the element that is about to receive focus is inside the // area, rely on native browsers behavior and let tabbing follow // the native tab sequence. if (node.contains(nextElement)) { return; } // If the element that is about to receive focus is outside the // area, move focus to a div and insert it at the start or end of // the area, depending on the direction. Without preventing default // behaviour, the browser will then move focus to the next element. const domAction = shiftKey ? 'append' : 'prepend'; const { ownerDocument } = node; const trap = ownerDocument.createElement('div'); trap.tabIndex = -1; node[domAction](trap); // Remove itself when the trap loses focus. trap.addEventListener('blur', () => node.removeChild(trap)); trap.focus(); } node.addEventListener('keydown', onKeyDown); return () => { node.removeEventListener('keydown', onKeyDown); }; }, []); } var _default = exports.default = useConstrainedTabbing; //# sourceMappingURL=index.js.map