@primer/react
Version:
An implementation of GitHub's Primer Design System using React
69 lines (68 loc) • 2.92 kB
JavaScript
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 };