73 lines
1.7 KiB
TypeScript
73 lines
1.7 KiB
TypeScript
import { createEffect, createSignal, onCleanup } from "solid-js";
|
|
|
|
type DesktopMenuController = {
|
|
isOpen: () => boolean;
|
|
rootRef: HTMLDivElement | undefined;
|
|
menuRef: HTMLDivElement | undefined;
|
|
setRootRef: (element: HTMLDivElement) => void;
|
|
setMenuRef: (element: HTMLDivElement) => void;
|
|
closeMenu: VoidFunction;
|
|
toggleMenu: VoidFunction;
|
|
};
|
|
|
|
// Shared desktop popover behavior for top-bar menus.
|
|
export const createDesktopMenuController = (): DesktopMenuController => {
|
|
const [isOpen, setIsOpen] = createSignal(false);
|
|
let rootRef: HTMLDivElement | undefined;
|
|
let menuRef: HTMLDivElement | undefined;
|
|
|
|
const closeMenu = (): void => {
|
|
setIsOpen(false);
|
|
};
|
|
|
|
const toggleMenu = (): void => {
|
|
setIsOpen((open) => !open);
|
|
};
|
|
|
|
createEffect(() => {
|
|
if (!isOpen()) return;
|
|
|
|
const handlePointerDown = (event: PointerEvent): void => {
|
|
if (!rootRef) return;
|
|
|
|
const target = event.target;
|
|
if (target instanceof Node && !rootRef.contains(target)) {
|
|
closeMenu();
|
|
}
|
|
};
|
|
|
|
const handleKeyDown = (event: KeyboardEvent): void => {
|
|
if (event.key === "Escape") {
|
|
closeMenu();
|
|
}
|
|
};
|
|
|
|
document.addEventListener("pointerdown", handlePointerDown);
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
menuRef?.querySelector<HTMLButtonElement>("[role='menuitem']")?.focus();
|
|
|
|
onCleanup(() => {
|
|
document.removeEventListener("pointerdown", handlePointerDown);
|
|
document.removeEventListener("keydown", handleKeyDown);
|
|
});
|
|
});
|
|
|
|
return {
|
|
isOpen,
|
|
get rootRef() {
|
|
return rootRef;
|
|
},
|
|
get menuRef() {
|
|
return menuRef;
|
|
},
|
|
setRootRef: (element: HTMLDivElement): void => {
|
|
rootRef = element;
|
|
},
|
|
setMenuRef: (element: HTMLDivElement): void => {
|
|
menuRef = element;
|
|
},
|
|
closeMenu,
|
|
toggleMenu,
|
|
};
|
|
};
|