Skip to content
File

Blob: src/client/components/editor/controllers/slash/overlay.ts

typescript50 lines
1import type { Editor } from "@tiptap/core";
2import type { SuggestionKeyDownProps } from "@tiptap/suggestion";
3import { mountEditorRenderer } from "../menu/renderer";
4import { SlashMenuPanel, type SlashMenuPanelHandle, type SlashMenuPanelProps } from "./panel";
5import type { ResolvedSlashMenuItem } from "./items";
6 
7interface SlashMenuOverlayOpts {
8 contextElement?: HTMLElement | null;
9 items: ResolvedSlashMenuItem[];
10 command: (item: ResolvedSlashMenuItem) => void;
11 clientRect: (() => DOMRect | null) | null;
12 onClose?: () => void;
13}
14 
15export interface SlashMenuOverlayHandle {
16 updateProps(opts: Partial<SlashMenuOverlayOpts>): void;
17 onKeyDown(props: SuggestionKeyDownProps): boolean;
18 destroy(): void;
19}
20 
21export function mountSlashMenu(editor: Editor, opts: SlashMenuOverlayOpts): SlashMenuOverlayHandle {
22 const component = mountEditorRenderer<SlashMenuPanelHandle, SlashMenuPanelProps>(editor, SlashMenuPanel, {
23 items: opts.items,
24 command: (item: ResolvedSlashMenuItem) => opts.command(item),
25 clientRect: opts.clientRect,
26 contextElement: opts.contextElement ?? editor.view.dom,
27 onClose: opts.onClose,
28 });
29 
30 return {
31 updateProps(next) {
32 component.updateProps({
33 ...(next.items !== undefined && { items: next.items }),
34 ...(next.command !== undefined && {
35 command: (item: ResolvedSlashMenuItem) => next.command!(item),
36 }),
37 ...(next.clientRect !== undefined && { clientRect: next.clientRect }),
38 ...(next.contextElement !== undefined && { contextElement: next.contextElement }),
39 ...(next.onClose !== undefined && { onClose: next.onClose }),
40 });
41 },
42 onKeyDown(props) {
43 return component.ref?.onKeyDown(props) ?? false;
44 },
45 destroy() {
46 component.destroy();
47 },
48 };
49}