import { type RefObject, useEffect } from 'react';
import { Platform, type View } from 'react-native';
import { isMenuNavKey, nextMenuIndex } from '../lib/menu-keyboard';
/**
* Move focus between a menu's `menuitem` rows with the arrow keys — the web half
* of menu keyboard navigation.
*
* react-native-web renders each `Pressable` row as a focusable `
` (its own key handler already turns Enter/Space into a
* press), so the only missing piece is roving focus: Tab gets the user *into*
* the list, but nothing then lets them walk it. This adds that, ArrowUp/Down
* cycling and Home/End jumping, scoped to keydown events that bubble up from a
* row inside `containerRef` — so a key pressed while focus sits on the trigger
* or the backdrop outside the menu is left alone.
*
* A no-op on native (touch menus have no focus ring to rove) and whenever the
* list carries no `role="menu"` (the caller passes `active={false}` for
* `role="none"`).
*/
export function useMenuKeyboardNavigation(containerRef: RefObject, active: boolean): void {
// biome-ignore lint/plugin: roving focus is an external DOM side effect — key handling and imperative focus moves have no declarative RN equivalent.
useEffect(() => {
if (Platform.OS !== 'web' || !active) return;
// biome-ignore lint/plugin: on web RNW renders the View to a DOM element, so the ref is really an HTMLElement; RN's View type can't express that.
const node = containerRef.current as unknown as HTMLElement | null;
if (!node?.querySelectorAll) return;
// Focusable menuitems, in DOM order. A disabled row is `aria-disabled` and
// `tabindex="-1"`, and a hidden row can't take focus — both are skipped.
const items = () =>
[...node.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')].filter(
(el) => el.tabIndex !== -1 && (el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0),
);
const onKeyDown = (event: KeyboardEvent) => {
if (!isMenuNavKey(event.key)) return;
const list = items();
if (list.length === 0) return;
const current = document.activeElement instanceof HTMLElement ? list.indexOf(document.activeElement) : -1;
const next = nextMenuIndex(current, list.length, event.key);
const row = list[next];
if (!row) return;
event.preventDefault();
row.focus({ preventScroll: true });
};
node.addEventListener('keydown', onKeyDown);
return () => node.removeEventListener('keydown', onKeyDown);
}, [containerRef, active]);
}