Files
2024-02-15 17:01:50 +00:00

100 lines
2.1 KiB
TypeScript

import {useCallback, useEffect, useState} from 'react';
export function useRovingTabIndex(items: any[]) {
const [tabIndex, setTabIndex] = useState<number | null>(null);
const onKeyDown = useCallback(
(evt: React.KeyboardEvent) => {
if (items.length === 0) {
return;
}
if (evt.key === 'Escape') {
setTabIndex(null);
}
if (evt.key === 'ArrowDown' || evt.key === 'Tab') {
evt.preventDefault();
if (tabIndex === items.length - 1 || tabIndex === null) {
setTabIndex(0);
} else {
setTabIndex((tabIndex ?? 0) + 1);
}
}
if (evt.key === 'ArrowUp' || (evt.key === 'Tab' && evt.shiftKey)) {
evt.preventDefault();
if (tabIndex === 0 || tabIndex === null) {
setTabIndex(items.length - 1);
} else {
setTabIndex((tabIndex ?? 0) - 1);
}
}
},
[tabIndex, items]
);
return {
tabIndex,
setTabIndex,
onKeyDown,
};
}
export function useKeyboardNavigation() {
const [menuRef, setMenuRef] = useState<HTMLDivElement | null>(null);
const items: {id: number; node: HTMLElement | null}[] = [];
const {tabIndex, setTabIndex, onKeyDown} = useRovingTabIndex(items);
useEffect(() => {
if (menuRef) {
if (tabIndex === null) {
menuRef.focus();
}
}
}, [menuRef, tabIndex]);
function getMenuProps() {
return {
tabIndex: -1,
ref: setMenuRef,
onKeyDown,
};
}
function getItemProps() {
const idx = items.length;
items.push({id: idx, node: null});
return {
tabIndex: tabIndex === idx ? 0 : -1,
ref: (node: HTMLElement | null) => {
if (items[idx]) {
if (tabIndex === idx) {
node?.focus();
}
items[idx].node = node;
}
},
onMouseOver: () => {
setTabIndex(idx);
},
onMouseEnter: () => {
setTabIndex(idx);
},
onKeyDown,
};
}
return {
menuRef,
getItemProps,
getMenuProps,
tabIndex,
setTabIndex,
};
}