@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
53 lines (52 loc) • 2.25 kB
JavaScript
import { useEffect, useRef } from "react";
import { useSubmenuContext } from "./submenu/submenu-context.js";
import { getNearestMatching } from "@1771technologies/lytenyte-shared";
import { dispatchActivate, dispatchClose, dispatchDeactivate, getSubmenuRoots } from "./dom.js";
export function useMenu(el) {
const sub = useSubmenuContext();
const mouseOutTime = useRef(null);
useEffect(() => {
if (!el || sub)
return;
const controller = new AbortController();
document.addEventListener("keydown", (ev) => {
if (el.contains(document.activeElement))
return;
if (ev.key !== "ArrowDown" && ev.key !== "ArrowUp")
return;
const menu = Array.from(document.querySelectorAll("[data-ln-menu]")).at(-1);
if (!menu)
return;
const firstItem = menu.querySelector("[data-ln-menu-item");
firstItem.focus?.();
}, { signal: controller.signal });
el.addEventListener("mouseover", (ev) => {
const target = ev.target;
const item = getNearestMatching(target, (el) => el.getAttribute("data-ln-menu-item") === "true");
if (!item)
return;
if (item) {
const itemRoots = getSubmenuRoots(item);
const menus = el.querySelectorAll('[data-ln-submenu-root="true"]');
menus.forEach((m) => {
if (!itemRoots.includes(m))
dispatchClose(m);
});
}
if (mouseOutTime.current)
clearTimeout(mouseOutTime.current);
dispatchActivate(item);
}, { signal: controller.signal });
el.addEventListener("mouseout", (ev) => {
const target = ev.target;
const item = getNearestMatching(target, (el) => el.getAttribute("data-ln-menu-item") === "true");
if (!item)
return;
mouseOutTime.current = setTimeout(() => {
dispatchDeactivate(item);
mouseOutTime.current = null;
});
}, { signal: controller.signal });
return () => controller.abort();
}, [el, sub]);
}