UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

69 lines (68 loc) 2.92 kB
import React from "react"; import { iterateFocusableElements } from "@primer/behaviors/utils"; //#region src/hooks/useMenuInitialFocus.ts 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.useState(void 0); React.useEffect(function inferOpeningKey() { const anchorElement = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current; const clickHandler = (event) => { 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 || anchorElement.addEventListener("click", clickHandler); anchorElement === null || anchorElement === void 0 || anchorElement.addEventListener("keydown", keydownHandler); return () => { anchorElement === null || anchorElement === void 0 || anchorElement.removeEventListener("click", clickHandler); anchorElement === null || anchorElement === 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.useEffect(function moveFocusOnOpen() { if (!open || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) return; const iterable = 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()); } }, [ open, openingGesture, anchorRef ]); }; //#endregion export { useMenuInitialFocus };