@primer/react
Version:
An implementation of GitHub's Primer Design System using React
82 lines (78 loc) • 2.59 kB
JavaScript
;
var React = require('react');
// adapted from details-menu web component https://github.com/github/details-menu-element
function useKeyboardNav(details, open, setOpen) {
const handleKeyDown = React.useCallback(event => {
const closeDetails = () => {
setOpen(false);
const summary = details.current.querySelector('summary');
if (summary) summary.focus();
};
const openDetails = () => {
setOpen(true);
};
const focusItem = next => {
const options = Array.from(details.current.querySelectorAll('[role^="menuitem"]:not([hidden]):not([disabled]):not([aria-disabled="true"])'));
const selected = document.activeElement;
const index = options.indexOf(selected);
const found = next ? options[index + 1] : options[index - 1];
const def = next ? options[0] : options[options.length - 1];
return found || def;
};
const isMenuItem = el => {
const role = el.getAttribute('role');
return role === 'menuitem' || role === 'menuitemcheckbox' || role === 'menuitemradio';
};
if (!(event instanceof KeyboardEvent)) return;
const isSummaryFocused = event.target instanceof Element && event.target.tagName === 'SUMMARY';
switch (event.key) {
case 'Escape':
if (open) {
closeDetails();
event.preventDefault();
event.stopPropagation();
}
break;
case 'ArrowDown':
{
if (isSummaryFocused && !open) {
openDetails();
}
const target = focusItem(true);
if (target) target.focus();
event.preventDefault();
}
break;
case 'ArrowUp':
{
if (isSummaryFocused && !open) {
openDetails();
}
const target = focusItem(false);
if (target) target.focus();
event.preventDefault();
}
break;
case ' ':
case 'Enter':
{
const selected = document.activeElement;
if (selected && isMenuItem(selected) && selected.closest('details') === details) {
event.preventDefault();
event.stopPropagation();
selected.click();
}
}
break;
}
}, [details, open, setOpen]);
React.useEffect(() => {
const current = details.current;
if (!current) return;
current.addEventListener('keydown', handleKeyDown);
return () => {
current.removeEventListener('keydown', handleKeyDown);
};
}, [details, handleKeyDown]);
}
module.exports = useKeyboardNav;