90 lines
No EOL
2.6 KiB
TypeScript
90 lines
No EOL
2.6 KiB
TypeScript
import { Accessor, Component, createContext, createEffect, createMemo, createSignal, For, JSX, ParentComponent, splitProps, useContext } from "solid-js";
|
|
import { CommandType } from "./command";
|
|
import css from "./contextMenu.module.css";
|
|
|
|
interface ContextMenuType {
|
|
readonly commands: Accessor<CommandType[]>;
|
|
readonly target: Accessor<HTMLElement | undefined>;
|
|
show(element: HTMLElement): void;
|
|
hide(): void;
|
|
}
|
|
|
|
const ContextMenu = createContext<ContextMenuType>()
|
|
|
|
const Root: ParentComponent<{ commands: CommandType[] }> = (props) => {
|
|
const [target, setTarget] = createSignal<HTMLElement>();
|
|
|
|
const context = {
|
|
commands: createMemo(() => props.commands),
|
|
target,
|
|
show(element: HTMLElement) {
|
|
setTarget(element);
|
|
},
|
|
hide() {
|
|
setTarget(undefined);
|
|
},
|
|
};
|
|
|
|
return <ContextMenu.Provider value={context}>
|
|
{props.children}
|
|
</ContextMenu.Provider>
|
|
};
|
|
|
|
const Menu: Component<{ children: (command: CommandType) => JSX.Element }> = (props) => {
|
|
const context = useContext(ContextMenu)!;
|
|
const [root, setRoot] = createSignal<HTMLElement>();
|
|
|
|
createEffect(() => {
|
|
const target = context.target();
|
|
const menu = root();
|
|
|
|
if (!menu) {
|
|
return;
|
|
}
|
|
|
|
if (target) {
|
|
menu.showPopover();
|
|
}
|
|
else {
|
|
menu.hidePopover();
|
|
}
|
|
});
|
|
|
|
const onToggle = (e: ToggleEvent) => {
|
|
if (e.newState === 'closed') {
|
|
context.hide();
|
|
}
|
|
};
|
|
|
|
const onCommand = (command: CommandType) => (e: PointerEvent) => {
|
|
context.hide();
|
|
|
|
command();
|
|
};
|
|
|
|
return <menu ref={setRoot} class={css.menu} style={`position-anchor: ${context.target()?.style.getPropertyValue('anchor-name')};`} popover ontoggle={onToggle}>
|
|
<For each={context.commands()}>{
|
|
command => <li onpointerdown={onCommand(command)}>{props.children(command)}</li>
|
|
}</For>
|
|
</menu>;
|
|
};
|
|
|
|
const Handle: ParentComponent<Record<string, any>> = (props) => {
|
|
const [local, rest] = splitProps(props, ['children']);
|
|
|
|
const context = useContext(ContextMenu)!;
|
|
const [handle, setHandle] = createSignal<HTMLElement>();
|
|
|
|
return <span {...rest} ref={setHandle} style={`anchor-name: --context-menu-${createUniqueId()};`} oncontextmenu={(e) => {
|
|
e.preventDefault();
|
|
|
|
context.show(handle()!);
|
|
|
|
return false;
|
|
}}>{local.children}</span>;
|
|
};
|
|
|
|
let handleCounter = 0;
|
|
const createUniqueId = () => `handle-${handleCounter++}`
|
|
|
|
export const Context = { Root, Menu, Handle }; |