UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

73 lines (65 loc) 3.63 kB
'use strict'; var React = require('react'); var utils = require('@primer/behaviors/utils'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefault(React); const useMenuInitialFocus = (open, containerRef, anchorRef) => { /** * We need to pick the first element to focus based on how the menu was opened, * however, we need to wait for the menu to be open to set focus. * This is why we use set openingKey in state and have 2 effects */ const [openingGesture, setOpeningGesture] = React__default.default.useState(undefined); React__default.default.useEffect(function inferOpeningKey() { const anchorElement = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current; const clickHandler = event => { // event.detail === 0 means onClick was fired by Enter/Space key // https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail if (event.detail !== 0) setOpeningGesture('mouse-click'); }; const keydownHandler = event => { if (['Space', 'Enter', 'ArrowDown', 'ArrowUp'].includes(event.code)) { setOpeningGesture(event.code); } }; anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.addEventListener('click', clickHandler); anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.addEventListener('keydown', keydownHandler); return () => { anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.removeEventListener('click', clickHandler); anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.removeEventListener('keydown', keydownHandler); }; }, [anchorRef]); /** * Pick the first element to focus based on the key used to open the Menu * Click: anchor * ArrowDown | Space | Enter: first element * ArrowUp: last element */ React__default.default.useEffect(function moveFocusOnOpen() { if (!open || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) return; // wait till the menu is open const iterable = utils.iterateFocusableElements(containerRef.current); if (openingGesture === 'mouse-click') { if (anchorRef !== null && anchorRef !== void 0 && anchorRef.current) anchorRef.current.focus();else throw new Error('For focus management, please attach anchorRef'); } else if (openingGesture && ['ArrowDown', 'Space', 'Enter'].includes(openingGesture)) { const firstElement = iterable.next().value; /** We push imperative focus to the next tick to prevent React's batching */ setTimeout(() => firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus()); } else if ('ArrowUp' === openingGesture) { const elements = [...iterable]; const lastElement = elements[elements.length - 1]; setTimeout(() => lastElement.focus()); } else { /** if the menu was not opened with the anchor, we default to the first element * for example: with keyboard shortcut (see stories/fixtures) */ const firstElement = iterable.next().value; setTimeout(() => firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus()); } }, // we don't want containerRef in dependencies // because re-renders to containerRef while it's open should not fire initialMenuFocus // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps [open, openingGesture, anchorRef]); }; exports.useMenuInitialFocus = useMenuInitialFocus;