@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
51 lines (50 loc) • 2.83 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useEffect, useState } from "react";
import { useListboxContext } from "./context.js";
import { getTabbables } from "@1771technologies/lytenyte-shared";
import { useCombinedRefs } from "@1771technologies/lytenyte-core/yinternal";
export const Panel = forwardRef(function Panel(props, forwarded) {
const [ref, setRef] = useState();
const combinedRef = useCombinedRefs(setRef, forwarded);
const ctx = useListboxContext();
useEffect(() => {
if (!ref)
return;
const controller = new AbortController();
const isVert = ctx.orientation === "vertical";
const nextKey = isVert ? "ArrowDown" : ctx.rtl ? "ArrowLeft" : "ArrowRight";
const prevKey = isVert ? "ArrowUp" : ctx.rtl ? "ArrowRight" : "ArrowLeft";
ref.addEventListener("keydown", (ev) => {
if (ev.key === "Tab") {
if (ref.contains(document.activeElement) || document.activeElement === ref) {
ref.inert = true;
setTimeout(() => {
ref.inert = false;
}, 10);
}
}
if (ev.key === nextKey) {
const items = getTabbables(ref).filter((c) => c.getAttribute("data-ln-listbox-item") === "true");
const currentIndex = items.findIndex((c) => c.contains(document.activeElement) || c === document.activeElement);
const focusItem = currentIndex === -1 ? items[0] : (items[currentIndex + 1] ?? items[0]);
if (!focusItem)
return;
focusItem.focus();
ev.preventDefault();
ev.stopPropagation();
}
if (ev.key === prevKey) {
const items = getTabbables(ref).filter((c) => c.getAttribute("data-ln-listbox-item") === "true");
const currentIndex = items.findIndex((c) => c.contains(document.activeElement) || c === document.activeElement);
const focusItem = currentIndex === -1 ? items.at(-1) : (items[currentIndex - 1] ?? items.at(-1));
if (!focusItem)
return;
focusItem.focus();
ev.preventDefault();
ev.stopPropagation();
}
}, { signal: controller.signal });
return () => controller.abort();
}, [ctx.orientation, ctx.rtl, ref]);
return (_jsxs("div", { "aria-label": "Generic listbox with keyboard navigable items", ...props, ref: combinedRef, "data-ln-listbox-panel": true, role: "listbox", tabIndex: 0, children: [props.children, _jsx("div", { "aria-label": "a presentational item that can be ignored", tabIndex: 0, onFocus: () => ref?.focus(), role: "option" })] }));
});