import { createApp, type App } from "vue"; import SuggestionDropdown from "@/components/SuggestionDropdown.vue"; import type { SuggestionItem } from "@/components/SuggestionDropdown.vue"; import type { SuggestionProps, SuggestionKeyDownProps } from "@tiptap/suggestion"; interface DropdownExposed { onKeyDown: (e: KeyboardEvent) => boolean; } export function createSuggestionRenderer() { let app: App | null = null; let el: HTMLElement | null = null; let componentRef: DropdownExposed | null = null; let commandFn: ((item: SuggestionItem) => void) | null = null; function mountDropdown(items: SuggestionItem[]) { if (!el) return; if (app) app.unmount(); app = createApp(SuggestionDropdown, { items, command: (item: SuggestionItem) => commandFn?.(item), }); const instance = app.mount(el); componentRef = instance as unknown as DropdownExposed; } return { onStart(props: SuggestionProps) { commandFn = (item: SuggestionItem) => props.command(item); el = document.createElement("div"); el.style.position = "fixed"; el.style.zIndex = "100"; document.body.appendChild(el); mountDropdown(props.items as SuggestionItem[]); updatePosition(props); }, onUpdate(props: SuggestionProps) { commandFn = (item: SuggestionItem) => props.command(item); mountDropdown(props.items as SuggestionItem[]); updatePosition(props); }, onKeyDown(props: SuggestionKeyDownProps) { if (props.event.key === "Escape") { cleanup(); return true; } return componentRef?.onKeyDown(props.event) ?? false; }, onExit() { cleanup(); }, }; function updatePosition(props: SuggestionProps) { if (!el || !props.clientRect) return; const rect = props.clientRect(); if (!rect) return; el.style.top = rect.bottom + "px"; el.style.left = rect.left + "px"; } function cleanup() { if (app) { app.unmount(); app = null; } if (el) { el.remove(); el = null; } componentRef = null; commandFn = null; } }