@relevaince/mentions 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -13
- package/dist/index.d.mts +32 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js +160 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +160 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/MentionsInput.tsx","../src/hooks/useMentionsEditor.ts","../src/core/mentionExtension.ts","../src/core/suggestionPlugin.ts","../src/core/markdownSerializer.ts","../src/core/markdownParser.ts","../src/hooks/useSuggestion.ts","../src/components/SuggestionList.tsx","../src/utils/ariaHelpers.ts"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Components */\n/* ------------------------------------------------------------------ */\nexport { MentionsInput } from \"./components/MentionsInput\";\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\nexport type { MentionToken } from \"./types/MentionToken\";\nexport type {\n MentionProvider,\n MentionItem,\n} from \"./types/MentionProvider\";\nexport type { MentionsOutput } from \"./types/MentionsOutput\";\nexport type {\n MentionsInputProps,\n MentionsInputHandle,\n} from \"./types/MentionsInputProps\";\n\n/* ------------------------------------------------------------------ */\n/* Utilities */\n/* ------------------------------------------------------------------ */\nexport { serializeToMarkdown } from \"./core/markdownSerializer\";\nexport { parseFromMarkdown } from \"./core/markdownParser\";\n","import React, { forwardRef, useCallback, useImperativeHandle } from \"react\";\nimport { EditorContent } from \"@tiptap/react\";\nimport { useMentionsEditor } from \"../hooks/useMentionsEditor\";\nimport { useSuggestion } from \"../hooks/useSuggestion\";\nimport { SuggestionList } from \"./SuggestionList\";\nimport { comboboxAttrs } from \"../utils/ariaHelpers\";\nimport type {\n MentionsInputProps,\n MentionsInputHandle,\n} from \"../types/MentionsInputProps\";\n\nconst LISTBOX_ID = \"mentions-suggestion-listbox\";\n\n/**\n * `<MentionsInput>` — the single public component.\n *\n * A structured text editor with typed entity tokens.\n * Consumers register `providers` for each trigger character,\n * and receive structured output via `onChange` and `onSubmit`.\n *\n * Supports an imperative ref handle for programmatic control:\n * ```tsx\n * const ref = useRef<MentionsInputHandle>(null);\n * ref.current.clear();\n * ref.current.setContent(\"@[Marketing](ws_123) summarize\");\n * ref.current.focus();\n * ```\n */\nexport const MentionsInput = forwardRef<MentionsInputHandle, MentionsInputProps>(\n function MentionsInput(\n {\n value,\n providers,\n onChange,\n placeholder = \"Type a message...\",\n autoFocus = false,\n disabled = false,\n className,\n onSubmit,\n clearOnSubmit = true,\n maxLength,\n renderItem,\n renderChip,\n },\n ref,\n ) {\n const { uiState, actions, callbacksRef } = useSuggestion(providers);\n\n const { editor, clear, setContent, focus } = useMentionsEditor({\n providers,\n value,\n onChange,\n onSubmit,\n clearOnSubmit,\n placeholder,\n autoFocus,\n editable: !disabled,\n callbacksRef,\n });\n\n /* ---------------------------------------------------------------- */\n /* Expose imperative handle */\n /* ---------------------------------------------------------------- */\n\n useImperativeHandle(\n ref,\n () => ({ clear, setContent, focus }),\n [clear, setContent, focus],\n );\n\n const isExpanded = uiState.state !== \"idle\";\n\n const handleHover = useCallback((_index: number) => {\n // Direct setActiveIndex can be exposed if needed\n }, []);\n\n return (\n <div\n className={className}\n data-mentions-input=\"\"\n data-disabled={disabled ? \"\" : undefined}\n {...comboboxAttrs(isExpanded, LISTBOX_ID)}\n aria-activedescendant={\n isExpanded && uiState.items[uiState.activeIndex]\n ? `mention-option-${uiState.items[uiState.activeIndex].id}`\n : undefined\n }\n >\n <EditorContent editor={editor} />\n\n {isExpanded && (\n <SuggestionList\n items={uiState.items}\n activeIndex={uiState.activeIndex}\n breadcrumbs={uiState.breadcrumbs}\n loading={uiState.loading}\n trigger={uiState.trigger}\n clientRect={uiState.clientRect}\n onSelect={(item) => actions.select(item)}\n onHover={handleHover}\n onGoBack={actions.goBack}\n renderItem={renderItem}\n />\n )}\n </div>\n );\n },\n);\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport { Extension } from \"@tiptap/core\";\nimport { MentionNode } from \"../core/mentionExtension\";\nimport { createSuggestionExtension } from \"../core/suggestionPlugin\";\nimport {\n serializeToMarkdown,\n extractTokens,\n extractPlainText,\n} from \"../core/markdownSerializer\";\nimport { parseFromMarkdown } from \"../core/markdownParser\";\nimport type { MentionProvider } from \"../types/MentionProvider\";\nimport type { MentionsOutput } from \"../types/MentionsOutput\";\nimport type { SuggestionCallbacksRef } from \"../core/suggestionPlugin\";\n\n/* ------------------------------------------------------------------ */\n/* Helper: build MentionsOutput from the current editor */\n/* ------------------------------------------------------------------ */\n\nfunction buildOutput(editor: { getJSON: () => any }): MentionsOutput {\n const json = editor.getJSON();\n return {\n markdown: serializeToMarkdown(json),\n tokens: extractTokens(json),\n plainText: extractPlainText(json),\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Submit keyboard shortcut extension (Cmd+Enter) */\n/* ------------------------------------------------------------------ */\n\nfunction createSubmitExtension(\n onSubmitRef: React.RefObject<((output: MentionsOutput) => void) | undefined>,\n clearOnSubmitRef: React.RefObject<boolean>,\n) {\n return Extension.create({\n name: \"submitShortcut\",\n\n addKeyboardShortcuts() {\n return {\n \"Mod-Enter\": () => {\n if (onSubmitRef.current) {\n onSubmitRef.current(buildOutput(this.editor));\n if (clearOnSubmitRef.current) {\n this.editor.commands.clearContent(true);\n }\n }\n return true;\n },\n };\n },\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Enter key — submit (only when suggestions are NOT active) */\n/* ------------------------------------------------------------------ */\n\nfunction createEnterExtension(\n onSubmitRef: React.RefObject<((output: MentionsOutput) => void) | undefined>,\n clearOnSubmitRef: React.RefObject<boolean>,\n) {\n return Extension.create({\n name: \"enterSubmit\",\n priority: 50,\n\n addKeyboardShortcuts() {\n return {\n Enter: () => {\n if (onSubmitRef.current) {\n onSubmitRef.current(buildOutput(this.editor));\n if (clearOnSubmitRef.current) {\n this.editor.commands.clearContent(true);\n }\n }\n return true;\n },\n };\n },\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Hook */\n/* ------------------------------------------------------------------ */\n\ntype UseMentionsEditorOptions = {\n providers: MentionProvider[];\n value?: string;\n onChange?: (output: MentionsOutput) => void;\n onSubmit?: (output: MentionsOutput) => void;\n clearOnSubmit?: boolean;\n placeholder?: string;\n autoFocus?: boolean;\n editable?: boolean;\n callbacksRef: SuggestionCallbacksRef;\n};\n\nexport function useMentionsEditor({\n providers,\n value,\n onChange,\n onSubmit,\n clearOnSubmit = true,\n placeholder,\n autoFocus = false,\n editable = true,\n callbacksRef,\n}: UseMentionsEditorOptions) {\n // Stable refs for handlers that the PM plugin reads\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const onSubmitRef = useRef(onSubmit);\n onSubmitRef.current = onSubmit;\n\n const clearOnSubmitRef = useRef(clearOnSubmit);\n clearOnSubmitRef.current = clearOnSubmit;\n\n // Build initial content from markdown value (only once)\n const initialContent = useMemo(() => {\n if (!value) return undefined;\n return parseFromMarkdown(value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Extract trigger strings for memoisation key\n const triggersKey = providers.map((p) => p.trigger).join(\",\");\n const triggers = useMemo(\n () => providers.map((p) => p.trigger),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [triggersKey],\n );\n\n // Single suggestion extension — uses callbacksRef so always fresh\n const suggestionExtension = useMemo(\n () => createSuggestionExtension(triggers, callbacksRef),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [triggersKey],\n );\n\n // Submit extensions — use refs, stable across renders\n const submitExt = useMemo(\n () => createSubmitExtension(onSubmitRef, clearOnSubmitRef),\n [],\n );\n const enterExt = useMemo(\n () => createEnterExtension(onSubmitRef, clearOnSubmitRef),\n [],\n );\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({\n heading: false,\n blockquote: false,\n codeBlock: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n horizontalRule: false,\n }),\n Placeholder.configure({\n placeholder: placeholder ?? \"Type a message...\",\n }),\n MentionNode,\n suggestionExtension,\n submitExt,\n enterExt,\n ],\n content: initialContent,\n autofocus: autoFocus ? \"end\" : false,\n editable,\n editorProps: {\n attributes: {\n class: \"mentions-editor\",\n },\n },\n onUpdate: ({ editor }) => {\n onChangeRef.current?.(buildOutput(editor));\n },\n });\n\n // Sync editable state\n useEffect(() => {\n if (editor && editor.isEditable !== editable) {\n editor.setEditable(editable);\n }\n }, [editor, editable]);\n\n /* ---------------------------------------------------------------- */\n /* Imperative methods */\n /* ---------------------------------------------------------------- */\n\n const clear = useCallback(() => {\n editor?.commands.clearContent(true);\n }, [editor]);\n\n const setContent = useCallback(\n (markdown: string) => {\n if (!editor) return;\n const doc = parseFromMarkdown(markdown);\n editor.commands.setContent(doc);\n },\n [editor],\n );\n\n const focus = useCallback(() => {\n editor?.commands.focus(\"end\");\n }, [editor]);\n\n const getOutput = useCallback((): MentionsOutput | null => {\n if (!editor) return null;\n return buildOutput(editor);\n }, [editor]);\n\n return { editor, getOutput, clear, setContent, focus };\n}\n","import { mergeAttributes, Node } from \"@tiptap/core\";\nimport { NodeViewRendererProps } from \"@tiptap/core\";\nimport { ReactNodeViewRenderer } from \"@tiptap/react\";\n\nexport interface MentionNodeAttrs {\n id: string;\n label: string;\n entityType: string;\n /** When set, the mention came from a child level (e.g. file under workspace). Used for @rootLabel[label](id) display and serialization. */\n rootLabel?: string | null;\n}\n\n/**\n * Trigger-character prefix map.\n * Used when rendering a mention chip to prepend the correct character.\n */\nconst DEFAULT_PREFIXES: Record<string, string> = {\n workspace: \"@\",\n contract: \"@\",\n file: \"#\",\n web: \":\",\n};\n\n/**\n * Custom Tiptap Node extension for inline mention tokens.\n *\n * Each mention is an atomic inline node with `id`, `label`, and `entityType`\n * attributes. Renders as a `<span>` with `data-mention` and `data-type`\n * attributes for styling.\n */\nexport const MentionNode = Node.create({\n name: \"mention\",\n group: \"inline\",\n inline: true,\n atom: true,\n selectable: true,\n draggable: false,\n\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-id\"),\n renderHTML: (attributes) => ({ \"data-id\": attributes.id }),\n },\n label: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-label\"),\n renderHTML: (attributes) => ({ \"data-label\": attributes.label }),\n },\n entityType: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-type\"),\n renderHTML: (attributes) => ({ \"data-type\": attributes.entityType }),\n },\n rootLabel: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-root-label\"),\n renderHTML: (attributes) =>\n attributes.rootLabel\n ? { \"data-root-label\": attributes.rootLabel }\n : {},\n },\n };\n },\n\n parseHTML() {\n return [{ tag: 'span[data-mention]' }];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const entityType = node.attrs.entityType as string;\n const label = node.attrs.label as string;\n const prefix = DEFAULT_PREFIXES[entityType] ?? \"@\";\n // In the input box show only prefix + label; rootLabel is used only for markdown serialization.\n const display = `${prefix}${label}`;\n\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n \"data-mention\": \"\",\n class: \"mention-chip\",\n }),\n display,\n ];\n },\n\n renderText({ node }) {\n const entityType = node.attrs.entityType as string;\n const label = node.attrs.label as string;\n const prefix = DEFAULT_PREFIXES[entityType] ?? \"@\";\n return `${prefix}${label}`;\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () =>\n this.editor.commands.command(({ tr, state }) => {\n let isMention = false;\n const { selection } = state;\n const { empty, anchor } = selection;\n\n if (!empty) return false;\n\n state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n if (node.type.name === this.name) {\n isMention = true;\n tr.insertText(\"\", pos, pos + node.nodeSize);\n }\n });\n\n return isMention;\n }),\n };\n },\n});\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport type { EditorView } from \"@tiptap/pm/view\";\nimport type { MentionItem, MentionProvider } from \"../types/MentionProvider\";\n\n/* ------------------------------------------------------------------ */\n/* Suggestion state types */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionState =\n | \"idle\"\n | \"loading\"\n | \"showing\"\n | \"drilling\"\n | \"inserting\";\n\n/* ------------------------------------------------------------------ */\n/* Callbacks the React layer implements */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionCallbacks = {\n onStart: (props: SuggestionCallbackProps) => void;\n onUpdate: (props: SuggestionCallbackProps) => void;\n onExit: () => void;\n onKeyDown: (props: { event: KeyboardEvent }) => boolean;\n};\n\nexport type SuggestionCallbackProps = {\n query: string;\n trigger: string;\n clientRect: (() => DOMRect | null) | null;\n range: { from: number; to: number };\n command: (attrs: Record<string, unknown>) => void;\n};\n\n/* ------------------------------------------------------------------ */\n/* Trigger detection */\n/* ------------------------------------------------------------------ */\n\ntype TriggerMatch = {\n trigger: string;\n query: string;\n from: number;\n to: number;\n};\n\nfunction detectTrigger(\n text: string,\n cursorPos: number,\n docStartPos: number,\n triggers: string[],\n): TriggerMatch | null {\n const relCursor = cursorPos - docStartPos;\n const before = text.slice(0, relCursor);\n\n for (let i = before.length - 1; i >= 0; i--) {\n const ch = before[i];\n if (ch === \"\\n\") return null;\n\n for (const trigger of triggers) {\n if (before.substring(i, i + trigger.length) === trigger) {\n if (i === 0 || /\\s/.test(before[i - 1])) {\n const query = before.slice(i + trigger.length);\n return { trigger, query, from: docStartPos + i, to: cursorPos };\n }\n }\n }\n }\n\n return null;\n}\n\n/* ------------------------------------------------------------------ */\n/* Plugin key */\n/* ------------------------------------------------------------------ */\n\nconst suggestionPluginKey = new PluginKey(\"mentionSuggestion\");\n\n/* ------------------------------------------------------------------ */\n/* Ref-based callback container (avoids stale closure issues) */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionCallbacksRef = { current: SuggestionCallbacks };\n\n/* ------------------------------------------------------------------ */\n/* Build the single multi-trigger suggestion extension */\n/* ------------------------------------------------------------------ */\n\nexport function createSuggestionExtension(\n triggers: string[],\n callbacksRef: SuggestionCallbacksRef,\n) {\n return Extension.create({\n name: \"mentionSuggestion\",\n\n addProseMirrorPlugins() {\n const editor = this.editor;\n let active = false;\n let lastQuery: string | null = null;\n let lastTrigger: string | null = null;\n\n const getClientRect = (\n view: EditorView,\n from: number,\n ): (() => DOMRect | null) => {\n return () => {\n try {\n const coords = view.coordsAtPos(from);\n return new DOMRect(\n coords.left,\n coords.top,\n 0,\n coords.bottom - coords.top,\n );\n } catch {\n return null;\n }\n };\n };\n\n const makeCommand = (range: { from: number; to: number }) => {\n return (attrs: Record<string, unknown>) => {\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: \"mention\",\n attrs: {\n id: attrs.id,\n label: attrs.label,\n entityType: attrs.entityType,\n rootLabel: attrs.rootLabel ?? null,\n },\n },\n { type: \"text\", text: \" \" },\n ])\n .run();\n };\n };\n\n const plugin = new Plugin({\n key: suggestionPluginKey,\n\n props: {\n handleKeyDown(_view, event) {\n if (!active) return false;\n // Always read from the ref — never a stale closure\n return callbacksRef.current.onKeyDown({ event });\n },\n },\n\n view() {\n return {\n update(view, _prevState) {\n const { state } = view;\n const { selection } = state;\n\n if (!selection.empty) {\n if (active) {\n active = false;\n lastQuery = null;\n lastTrigger = null;\n callbacksRef.current.onExit();\n }\n return;\n }\n\n const $pos = selection.$from;\n const textBlock = $pos.parent;\n\n if (!textBlock.isTextblock) {\n if (active) {\n active = false;\n lastQuery = null;\n lastTrigger = null;\n callbacksRef.current.onExit();\n }\n return;\n }\n\n const blockStart = $pos.start();\n const blockText = textBlock.textContent;\n const cursorPos = $pos.pos;\n\n const match = detectTrigger(blockText, cursorPos, blockStart, triggers);\n\n if (match) {\n const range = { from: match.from, to: match.to };\n const props: SuggestionCallbackProps = {\n query: match.query,\n trigger: match.trigger,\n clientRect: getClientRect(view, match.from),\n range,\n command: makeCommand(range),\n };\n\n if (!active) {\n active = true;\n lastQuery = match.query;\n lastTrigger = match.trigger;\n callbacksRef.current.onStart(props);\n } else if (\n match.query !== lastQuery ||\n match.trigger !== lastTrigger\n ) {\n // Only fire onUpdate when something actually changed\n lastQuery = match.query;\n lastTrigger = match.trigger;\n callbacksRef.current.onUpdate(props);\n }\n } else {\n if (active) {\n active = false;\n lastQuery = null;\n lastTrigger = null;\n callbacksRef.current.onExit();\n }\n }\n },\n\n destroy() {\n if (active) {\n callbacksRef.current.onExit();\n }\n },\n };\n },\n });\n\n return [plugin];\n },\n });\n}\n","import type { JSONContent } from \"@tiptap/core\";\nimport type { MentionToken } from \"../types/MentionToken\";\n\n/**\n * Serialize a Tiptap JSON document to a markdown string.\n *\n * Mention nodes are encoded as `@[label](id)` or `@rootLabel[label](id)` when the mention has a root (e.g. file under workspace).\n * All other text passes through verbatim.\n */\nexport function serializeToMarkdown(doc: JSONContent): string {\n if (!doc.content) return \"\";\n\n const parts: string[] = [];\n\n for (const block of doc.content) {\n if (block.type === \"paragraph\") {\n parts.push(serializeParagraph(block));\n } else if (block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nfunction serializeParagraph(node: JSONContent): string {\n if (!node.content) return \"\";\n\n return node.content\n .map((child) => {\n if (child.type === \"mention\") {\n const { id, label, rootLabel } = child.attrs ?? {};\n if (rootLabel != null && rootLabel !== \"\") {\n return `@${rootLabel}[${label}](${id})`;\n }\n return `@[${label}](${id})`;\n }\n return child.text ?? \"\";\n })\n .join(\"\");\n}\n\n/**\n * Extract all `MentionToken`s from a Tiptap JSON document, in document order.\n */\nexport function extractTokens(doc: JSONContent): MentionToken[] {\n const tokens: MentionToken[] = [];\n\n function walk(node: JSONContent) {\n if (node.type === \"mention\" && node.attrs) {\n tokens.push({\n id: node.attrs.id as string,\n type: node.attrs.entityType as string,\n label: node.attrs.label as string,\n });\n }\n if (node.content) {\n for (const child of node.content) {\n walk(child);\n }\n }\n }\n\n walk(doc);\n return tokens;\n}\n\n/**\n * Extract plain text from a Tiptap JSON document.\n * Mention nodes are replaced with their label (no trigger prefix in plain text).\n */\nexport function extractPlainText(doc: JSONContent): string {\n if (!doc.content) return \"\";\n\n const parts: string[] = [];\n\n for (const block of doc.content) {\n if (block.type === \"paragraph\") {\n parts.push(extractParagraphText(block));\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nfunction extractParagraphText(node: JSONContent): string {\n if (!node.content) return \"\";\n\n return node.content\n .map((child) => {\n if (child.type === \"mention\") {\n return child.attrs?.label ?? \"\";\n }\n return child.text ?? \"\";\n })\n .join(\"\");\n}\n","import type { JSONContent } from \"@tiptap/core\";\n\n/**\n * Regex matching mention tokens:\n * - `@[label](id)` or `@[label](type:id)`\n * - `@rootLabel[label](id)` when the mention has a root (e.g. file under workspace).\n * Groups: (1) optional rootLabel, (2) label, (3) optional entityType, (4) id.\n */\nconst MENTION_RE = /@(?:([^\\[]+)\\[)?\\[([^\\]]+)\\]\\((?:([^:)]+):)?([^)]+)\\)/g;\n\n/**\n * Parse a markdown string (with `@[label](id)`, `@[label](type:id)`, or `@rootLabel[label](id)` tokens)\n * into a Tiptap-compatible JSON document.\n */\nexport function parseFromMarkdown(markdown: string): JSONContent {\n const lines = markdown.split(\"\\n\");\n\n const content: JSONContent[] = lines.map((line) => ({\n type: \"paragraph\",\n content: parseLine(line),\n }));\n\n return { type: \"doc\", content };\n}\n\nfunction parseLine(line: string): JSONContent[] {\n const nodes: JSONContent[] = [];\n let lastIndex = 0;\n\n // Reset regex state\n MENTION_RE.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = MENTION_RE.exec(line)) !== null) {\n const fullMatch = match[0];\n const rootLabel = match[1] ?? null;\n const label = match[2];\n const entityType = match[3] ?? \"unknown\";\n const id = match[4];\n\n // Text before this mention\n if (match.index > lastIndex) {\n nodes.push({\n type: \"text\",\n text: line.slice(lastIndex, match.index),\n });\n }\n\n // Mention node\n nodes.push({\n type: \"mention\",\n attrs: {\n id,\n label,\n entityType,\n rootLabel,\n },\n });\n\n lastIndex = match.index + fullMatch.length;\n }\n\n // Trailing text\n if (lastIndex < line.length) {\n nodes.push({\n type: \"text\",\n text: line.slice(lastIndex),\n });\n }\n\n // Empty line — ProseMirror needs at least one node\n if (nodes.length === 0) {\n nodes.push({ type: \"text\", text: \"\" });\n }\n\n return nodes;\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type { MentionItem, MentionProvider } from \"../types/MentionProvider\";\nimport type {\n SuggestionCallbackProps,\n SuggestionCallbacks,\n SuggestionCallbacksRef,\n SuggestionState,\n} from \"../core/suggestionPlugin\";\n\n/* ------------------------------------------------------------------ */\n/* Public state exposed to the UI layer */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionUIState = {\n state: SuggestionState;\n items: MentionItem[];\n breadcrumbs: MentionItem[];\n activeIndex: number;\n loading: boolean;\n clientRect: (() => DOMRect | null) | null;\n trigger: string | null;\n query: string;\n};\n\nexport type SuggestionActions = {\n navigateUp: () => void;\n navigateDown: () => void;\n select: (item?: MentionItem) => void;\n goBack: () => void;\n close: () => void;\n};\n\nconst IDLE_STATE: SuggestionUIState = {\n state: \"idle\",\n items: [],\n breadcrumbs: [],\n activeIndex: 0,\n loading: false,\n clientRect: null,\n trigger: null,\n query: \"\",\n};\n\n/* ------------------------------------------------------------------ */\n/* Hook */\n/* ------------------------------------------------------------------ */\n\nexport function useSuggestion(providers: MentionProvider[]) {\n const [uiState, setUIState] = useState<SuggestionUIState>(IDLE_STATE);\n\n // --- Refs for values that callbacks need to read (avoids stale closures) ---\n const stateRef = useRef(uiState);\n stateRef.current = uiState;\n\n const providersRef = useRef(providers);\n providersRef.current = providers;\n\n const commandRef = useRef<((attrs: Record<string, unknown>) => void) | null>(\n null,\n );\n const providerRef = useRef<MentionProvider | null>(null);\n\n /* ---------------------------------------------------------------- */\n /* Fetch items from the provider */\n /* ---------------------------------------------------------------- */\n\n const fetchItems = useCallback(\n async (\n provider: MentionProvider,\n query: string,\n parent?: MentionItem,\n ) => {\n setUIState((prev) => ({ ...prev, loading: true, state: \"loading\" }));\n\n try {\n const items =\n parent && provider.getChildren\n ? await provider.getChildren(parent, query)\n : await provider.getRootItems(query);\n\n setUIState((prev) => ({\n ...prev,\n items,\n loading: false,\n state: \"showing\",\n activeIndex: 0,\n }));\n } catch {\n setUIState((prev) => ({\n ...prev,\n items: [],\n loading: false,\n state: \"showing\",\n }));\n }\n },\n [],\n );\n\n /* ---------------------------------------------------------------- */\n /* Suggestion plugin callbacks */\n /* ---------------------------------------------------------------- */\n\n const onStart = useCallback(\n (props: SuggestionCallbackProps) => {\n const provider = providersRef.current.find(\n (p) => p.trigger === props.trigger,\n );\n if (!provider) return;\n\n providerRef.current = provider;\n commandRef.current = props.command;\n\n setUIState({\n state: \"loading\",\n items: [],\n breadcrumbs: [],\n activeIndex: 0,\n loading: true,\n clientRect: props.clientRect,\n trigger: props.trigger,\n query: props.query,\n });\n\n fetchItems(provider, props.query);\n },\n [fetchItems],\n );\n\n const onUpdate = useCallback(\n (props: SuggestionCallbackProps) => {\n const provider = providerRef.current;\n if (!provider) return;\n\n commandRef.current = props.command;\n\n setUIState((prev) => ({\n ...prev,\n clientRect: props.clientRect,\n query: props.query,\n }));\n\n // Only refetch at root level — nested levels manage their own queries\n // Read from ref to avoid stale closure\n if (stateRef.current.breadcrumbs.length === 0) {\n fetchItems(provider, props.query);\n }\n },\n [fetchItems],\n );\n\n const onExit = useCallback(() => {\n providerRef.current = null;\n commandRef.current = null;\n setUIState(IDLE_STATE);\n }, []);\n\n /* ---------------------------------------------------------------- */\n /* Navigation actions */\n /* ---------------------------------------------------------------- */\n\n const navigateUp = useCallback(() => {\n setUIState((prev) => ({\n ...prev,\n activeIndex: Math.max(0, prev.activeIndex - 1),\n }));\n }, []);\n\n const navigateDown = useCallback(() => {\n setUIState((prev) => ({\n ...prev,\n activeIndex: Math.min(prev.items.length - 1, prev.activeIndex + 1),\n }));\n }, []);\n\n const select = useCallback(\n (item?: MentionItem) => {\n const current = stateRef.current;\n const selected = item ?? current.items[current.activeIndex];\n if (!selected) return;\n\n const provider = providerRef.current;\n\n // If the item has children and the provider supports it, drill down\n if (selected.hasChildren && provider?.getChildren) {\n setUIState((prev) => ({\n ...prev,\n state: \"drilling\",\n breadcrumbs: [...prev.breadcrumbs, selected],\n items: [],\n activeIndex: 0,\n query: \"\",\n }));\n\n fetchItems(provider, \"\", selected);\n return;\n }\n\n // Otherwise insert the mention (include root label when selected from a child level)\n if (commandRef.current) {\n const rootLabel =\n current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : null;\n commandRef.current({\n id: selected.id,\n label: selected.label,\n entityType: selected.type,\n rootLabel,\n });\n }\n },\n [fetchItems],\n );\n\n const goBack = useCallback(() => {\n const provider = providerRef.current;\n if (!provider) return;\n\n setUIState((prev) => {\n const newBreadcrumbs = prev.breadcrumbs.slice(0, -1);\n const parent = newBreadcrumbs[newBreadcrumbs.length - 1];\n\n if (parent) {\n fetchItems(provider, \"\", parent);\n } else {\n fetchItems(provider, prev.query);\n }\n\n return {\n ...prev,\n breadcrumbs: newBreadcrumbs,\n items: [],\n activeIndex: 0,\n state: \"loading\" as const,\n };\n });\n }, [fetchItems]);\n\n const close = useCallback(() => {\n setUIState(IDLE_STATE);\n }, []);\n\n /* ---------------------------------------------------------------- */\n /* Keyboard handler */\n /* ---------------------------------------------------------------- */\n\n const onKeyDown = useCallback(\n ({ event }: { event: KeyboardEvent }): boolean => {\n const current = stateRef.current;\n if (current.state === \"idle\") return false;\n\n switch (event.key) {\n case \"ArrowUp\":\n event.preventDefault();\n navigateUp();\n return true;\n case \"ArrowDown\":\n event.preventDefault();\n navigateDown();\n return true;\n case \"Enter\": {\n event.preventDefault();\n const selectedItem = current.items[current.activeIndex];\n if (selectedItem) {\n select(selectedItem);\n }\n return true;\n }\n case \"ArrowRight\": {\n const activeItem = current.items[current.activeIndex];\n if (activeItem?.hasChildren) {\n event.preventDefault();\n select(activeItem);\n return true;\n }\n return false;\n }\n case \"ArrowLeft\":\n if (current.breadcrumbs.length > 0) {\n event.preventDefault();\n goBack();\n return true;\n }\n return false;\n case \"Escape\":\n event.preventDefault();\n close();\n return true;\n default:\n return false;\n }\n },\n [navigateUp, navigateDown, select, goBack, close],\n );\n\n /* ---------------------------------------------------------------- */\n /* Build a ref that always points to the latest callbacks. */\n /* The ProseMirror plugin reads from this ref — no stale closures. */\n /* ---------------------------------------------------------------- */\n\n const callbacksRef: SuggestionCallbacksRef = useRef<SuggestionCallbacks>({\n onStart,\n onUpdate,\n onExit,\n onKeyDown,\n });\n\n // Update on every render so the PM plugin always sees fresh functions\n callbacksRef.current = { onStart, onUpdate, onExit, onKeyDown };\n\n const actions: SuggestionActions = {\n navigateUp,\n navigateDown,\n select,\n goBack,\n close,\n };\n\n return { uiState, actions, callbacksRef };\n}\n","import React, { useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { MentionItem } from \"../types/MentionProvider\";\nimport { listboxAttrs, optionAttrs } from \"../utils/ariaHelpers\";\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionListProps = {\n items: MentionItem[];\n activeIndex: number;\n breadcrumbs: MentionItem[];\n loading: boolean;\n trigger: string | null;\n clientRect: (() => DOMRect | null) | null;\n onSelect: (item: MentionItem) => void;\n onHover: (index: number) => void;\n onGoBack: () => void;\n renderItem?: (item: MentionItem, depth: number) => ReactNode;\n};\n\nconst LISTBOX_ID = \"mentions-suggestion-listbox\";\n\n/* ------------------------------------------------------------------ */\n/* Component */\n/* ------------------------------------------------------------------ */\n\nexport function SuggestionList({\n items,\n activeIndex,\n breadcrumbs,\n loading,\n trigger,\n clientRect,\n onSelect,\n onHover,\n onGoBack,\n renderItem,\n}: SuggestionListProps) {\n const listRef = useRef<HTMLDivElement>(null);\n const depth = breadcrumbs.length;\n\n // Scroll the active item into view\n useEffect(() => {\n if (!listRef.current) return;\n const active = listRef.current.querySelector('[aria-selected=\"true\"]');\n active?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n // Position the popover based on clientRect\n const style = usePopoverPosition(clientRect);\n\n if (items.length === 0 && !loading) return null;\n\n return (\n <div\n data-suggestions=\"\"\n data-trigger={trigger}\n style={style}\n ref={listRef}\n >\n {/* Breadcrumbs for nested navigation */}\n {breadcrumbs.length > 0 && (\n <div data-suggestion-breadcrumb=\"\">\n <button\n type=\"button\"\n data-suggestion-back=\"\"\n onClick={onGoBack}\n aria-label=\"Go back\"\n >\n ←\n </button>\n {breadcrumbs.map((crumb, i) => (\n <span key={crumb.id} data-suggestion-breadcrumb-item=\"\">\n {i > 0 && <span data-suggestion-breadcrumb-sep=\"\">/</span>}\n {crumb.label}\n </span>\n ))}\n </div>\n )}\n\n {/* Loading indicator */}\n {loading && (\n <div data-suggestion-loading=\"\">Loading...</div>\n )}\n\n {/* Item list */}\n {!loading && (\n <div {...listboxAttrs(LISTBOX_ID, `${trigger ?? \"\"} suggestions`)}>\n {items.map((item, index) => {\n const isActive = index === activeIndex;\n const itemId = `mention-option-${item.id}`;\n\n return (\n <div\n key={item.id}\n {...optionAttrs(itemId, isActive, index)}\n data-suggestion-item=\"\"\n data-suggestion-item-active={isActive ? \"\" : undefined}\n data-has-children={item.hasChildren ? \"\" : undefined}\n onMouseEnter={() => onHover(index)}\n onClick={() => onSelect(item)}\n >\n {renderItem ? (\n renderItem(item, depth)\n ) : (\n <DefaultSuggestionItem item={item} />\n )}\n </div>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Default item render */\n/* ------------------------------------------------------------------ */\n\nfunction DefaultSuggestionItem({ item }: { item: MentionItem }) {\n return (\n <>\n {item.icon && (\n <span data-suggestion-item-icon=\"\">{item.icon}</span>\n )}\n <span data-suggestion-item-label=\"\">{item.label}</span>\n {item.description && (\n <span data-suggestion-item-description=\"\">{item.description}</span>\n )}\n {item.hasChildren && (\n <span data-suggestion-item-chevron=\"\" aria-hidden=\"true\">\n ›\n </span>\n )}\n </>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Popover positioning */\n/* ------------------------------------------------------------------ */\n\nfunction usePopoverPosition(\n clientRect: (() => DOMRect | null) | null,\n): React.CSSProperties {\n if (!clientRect) {\n return { display: \"none\" };\n }\n\n const rect = clientRect();\n if (!rect) {\n return { display: \"none\" };\n }\n\n return {\n position: \"fixed\",\n left: `${rect.left}px`,\n top: `${rect.bottom + 4}px`,\n zIndex: 50,\n };\n}\n","/**\n * Generates ARIA attributes for the suggestion combobox pattern.\n */\n\nexport function comboboxAttrs(expanded: boolean, listboxId: string) {\n return {\n role: \"combobox\" as const,\n \"aria-haspopup\": \"listbox\" as const,\n \"aria-expanded\": expanded,\n \"aria-owns\": expanded ? listboxId : undefined,\n };\n}\n\nexport function listboxAttrs(id: string, label: string) {\n return {\n id,\n role: \"listbox\" as const,\n \"aria-label\": label,\n };\n}\n\nexport function optionAttrs(\n id: string,\n selected: boolean,\n index: number,\n) {\n return {\n id,\n role: \"option\" as const,\n \"aria-selected\": selected,\n \"aria-posinset\": index + 1,\n };\n}\n"],"mappings":"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","names":["import_react","import_react","import_core","import_core","StarterKit","Placeholder","editor","import_react","import_react","import_jsx_runtime","LISTBOX_ID","MentionsInput"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/MentionsInput.tsx","../src/hooks/useMentionsEditor.ts","../src/core/mentionExtension.ts","../src/core/suggestionPlugin.ts","../src/core/markdownSerializer.ts","../src/core/markdownParser.ts","../src/hooks/useSuggestion.ts","../src/components/SuggestionList.tsx","../src/utils/ariaHelpers.ts"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Components */\n/* ------------------------------------------------------------------ */\nexport { MentionsInput } from \"./components/MentionsInput\";\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\nexport type { MentionToken } from \"./types/MentionToken\";\nexport type {\n MentionProvider,\n MentionItem,\n} from \"./types/MentionProvider\";\nexport type { MentionsOutput } from \"./types/MentionsOutput\";\nexport type {\n MentionsInputProps,\n MentionsInputHandle,\n} from \"./types/MentionsInputProps\";\n\n/* ------------------------------------------------------------------ */\n/* Utilities */\n/* ------------------------------------------------------------------ */\nexport { serializeToMarkdown } from \"./core/markdownSerializer\";\nexport { parseFromMarkdown, extractFromMarkdown } from \"./core/markdownParser\";\n","import React, { forwardRef, useCallback, useImperativeHandle } from \"react\";\nimport { EditorContent } from \"@tiptap/react\";\nimport { useMentionsEditor } from \"../hooks/useMentionsEditor\";\nimport { useSuggestion } from \"../hooks/useSuggestion\";\nimport { SuggestionList } from \"./SuggestionList\";\nimport { comboboxAttrs } from \"../utils/ariaHelpers\";\nimport type {\n MentionsInputProps,\n MentionsInputHandle,\n} from \"../types/MentionsInputProps\";\n\nconst LISTBOX_ID = \"mentions-suggestion-listbox\";\n\n/**\n * `<MentionsInput>` — the single public component.\n *\n * A structured text editor with typed entity tokens.\n * Consumers register `providers` for each trigger character,\n * and receive structured output via `onChange` and `onSubmit`.\n *\n * Supports an imperative ref handle for programmatic control:\n * ```tsx\n * const ref = useRef<MentionsInputHandle>(null);\n * ref.current.clear();\n * ref.current.setContent(\"@[Marketing](ws_123) summarize\");\n * ref.current.focus();\n * ```\n */\nexport const MentionsInput = forwardRef<MentionsInputHandle, MentionsInputProps>(\n function MentionsInput(\n {\n value,\n providers,\n onChange,\n placeholder = \"Type a message...\",\n autoFocus = false,\n disabled = false,\n className,\n onSubmit,\n clearOnSubmit = true,\n maxLength,\n renderItem,\n renderChip,\n },\n ref,\n ) {\n const { uiState, actions, callbacksRef } = useSuggestion(providers);\n\n const { editor, clear, setContent, focus } = useMentionsEditor({\n providers,\n value,\n onChange,\n onSubmit,\n clearOnSubmit,\n placeholder,\n autoFocus,\n editable: !disabled,\n callbacksRef,\n });\n\n /* ---------------------------------------------------------------- */\n /* Expose imperative handle */\n /* ---------------------------------------------------------------- */\n\n useImperativeHandle(\n ref,\n () => ({ clear, setContent, focus }),\n [clear, setContent, focus],\n );\n\n const isExpanded = uiState.state !== \"idle\";\n\n const handleHover = useCallback((index: number) => {\n // Hover sets the active index visually (handled via CSS :hover for now)\n void index;\n }, []);\n\n return (\n <div\n className={className}\n data-mentions-input=\"\"\n data-disabled={disabled ? \"\" : undefined}\n {...comboboxAttrs(isExpanded, LISTBOX_ID)}\n aria-activedescendant={\n isExpanded && uiState.items[uiState.activeIndex]\n ? `mention-option-${uiState.items[uiState.activeIndex].id}`\n : undefined\n }\n >\n <EditorContent editor={editor} />\n\n {isExpanded && (\n <SuggestionList\n items={uiState.items}\n activeIndex={uiState.activeIndex}\n breadcrumbs={uiState.breadcrumbs}\n loading={uiState.loading}\n trigger={uiState.trigger}\n clientRect={uiState.clientRect}\n onSelect={(item) => actions.select(item)}\n onHover={handleHover}\n onGoBack={actions.goBack}\n onSearchNested={actions.searchNested}\n onNavigateUp={actions.navigateUp}\n onNavigateDown={actions.navigateDown}\n onClose={actions.close}\n renderItem={renderItem}\n />\n )}\n </div>\n );\n },\n);\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport { Extension } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport { MentionNode } from \"../core/mentionExtension\";\nimport { createSuggestionExtension } from \"../core/suggestionPlugin\";\nimport {\n serializeToMarkdown,\n extractTokens,\n extractPlainText,\n} from \"../core/markdownSerializer\";\nimport { parseFromMarkdown } from \"../core/markdownParser\";\nimport type { MentionProvider } from \"../types/MentionProvider\";\nimport type { MentionsOutput } from \"../types/MentionsOutput\";\nimport type { SuggestionCallbacksRef } from \"../core/suggestionPlugin\";\n\n/* ------------------------------------------------------------------ */\n/* Helper: build MentionsOutput from the current editor */\n/* ------------------------------------------------------------------ */\n\nfunction buildOutput(editor: { getJSON: () => any }): MentionsOutput {\n const json = editor.getJSON();\n return {\n markdown: serializeToMarkdown(json),\n tokens: extractTokens(json),\n plainText: extractPlainText(json),\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Submit keyboard shortcut extension (Cmd/Ctrl+Enter) */\n/* ------------------------------------------------------------------ */\n\nfunction createSubmitExtension(\n onSubmitRef: React.RefObject<((output: MentionsOutput) => void) | undefined>,\n clearOnSubmitRef: React.RefObject<boolean>,\n) {\n return Extension.create({\n name: \"submitShortcut\",\n priority: 150,\n\n addKeyboardShortcuts() {\n return {\n \"Mod-Enter\": () => {\n if (onSubmitRef.current) {\n onSubmitRef.current(buildOutput(this.editor));\n if (clearOnSubmitRef.current) {\n this.editor.commands.clearContent(true);\n }\n }\n return true;\n },\n };\n },\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Enter key handling via ProseMirror plugin */\n/* */\n/* Priority 150 ensures this runs AFTER the suggestion plugin (200) */\n/* but BEFORE the base keymap (~0) which would split the block. */\n/* */\n/* • Enter → submit message */\n/* • Shift+Enter → new line (split block) */\n/* ------------------------------------------------------------------ */\n\nconst enterSubmitPluginKey = new PluginKey(\"enterSubmit\");\n\nfunction createEnterExtension(\n onSubmitRef: React.RefObject<((output: MentionsOutput) => void) | undefined>,\n clearOnSubmitRef: React.RefObject<boolean>,\n) {\n return Extension.create({\n name: \"enterSubmit\",\n priority: 150,\n\n addProseMirrorPlugins() {\n const editor = this.editor;\n\n return [\n new Plugin({\n key: enterSubmitPluginKey,\n props: {\n handleKeyDown(_view, event) {\n if (event.key !== \"Enter\") return false;\n\n // Shift+Enter → new line\n if (event.shiftKey) {\n editor.commands.splitBlock();\n return true;\n }\n\n // Mod+Enter is handled by the submitShortcut extension\n if (event.metaKey || event.ctrlKey) return false;\n\n // Plain Enter → submit\n if (onSubmitRef.current) {\n onSubmitRef.current(buildOutput(editor));\n if (clearOnSubmitRef.current) {\n editor.commands.clearContent(true);\n }\n }\n return true;\n },\n },\n }),\n ];\n },\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Hook */\n/* ------------------------------------------------------------------ */\n\ntype UseMentionsEditorOptions = {\n providers: MentionProvider[];\n value?: string;\n onChange?: (output: MentionsOutput) => void;\n onSubmit?: (output: MentionsOutput) => void;\n clearOnSubmit?: boolean;\n placeholder?: string;\n autoFocus?: boolean;\n editable?: boolean;\n callbacksRef: SuggestionCallbacksRef;\n};\n\nexport function useMentionsEditor({\n providers,\n value,\n onChange,\n onSubmit,\n clearOnSubmit = true,\n placeholder,\n autoFocus = false,\n editable = true,\n callbacksRef,\n}: UseMentionsEditorOptions) {\n // Stable refs for handlers that the PM plugin reads\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const onSubmitRef = useRef(onSubmit);\n onSubmitRef.current = onSubmit;\n\n const clearOnSubmitRef = useRef(clearOnSubmit);\n clearOnSubmitRef.current = clearOnSubmit;\n\n // Build initial content from markdown value (only once)\n const initialContent = useMemo(() => {\n if (!value) return undefined;\n return parseFromMarkdown(value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Extract trigger strings for memoisation key\n const triggersKey = providers.map((p) => p.trigger).join(\",\");\n const triggers = useMemo(\n () => providers.map((p) => p.trigger),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [triggersKey],\n );\n\n // Single suggestion extension — uses callbacksRef so always fresh\n const suggestionExtension = useMemo(\n () => createSuggestionExtension(triggers, callbacksRef),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [triggersKey],\n );\n\n // Submit extensions — use refs, stable across renders\n const submitExt = useMemo(\n () => createSubmitExtension(onSubmitRef, clearOnSubmitRef),\n [],\n );\n const enterExt = useMemo(\n () => createEnterExtension(onSubmitRef, clearOnSubmitRef),\n [],\n );\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({\n heading: false,\n blockquote: false,\n codeBlock: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n horizontalRule: false,\n hardBreak: false,\n }),\n Placeholder.configure({\n placeholder: placeholder ?? \"Type a message...\",\n showOnlyCurrent: false,\n }),\n MentionNode,\n suggestionExtension,\n submitExt,\n enterExt,\n ],\n content: initialContent,\n autofocus: autoFocus ? \"end\" : false,\n editable,\n editorProps: {\n attributes: {\n class: \"mentions-editor\",\n },\n },\n onUpdate: ({ editor }) => {\n onChangeRef.current?.(buildOutput(editor));\n },\n });\n\n // Sync editable state\n useEffect(() => {\n if (editor && editor.isEditable !== editable) {\n editor.setEditable(editable);\n }\n }, [editor, editable]);\n\n /* ---------------------------------------------------------------- */\n /* Imperative methods */\n /* ---------------------------------------------------------------- */\n\n const clear = useCallback(() => {\n editor?.commands.clearContent(true);\n }, [editor]);\n\n const setContent = useCallback(\n (markdown: string) => {\n if (!editor) return;\n const doc = parseFromMarkdown(markdown);\n editor.commands.setContent(doc);\n },\n [editor],\n );\n\n const focus = useCallback(() => {\n editor?.commands.focus(\"end\");\n }, [editor]);\n\n const getOutput = useCallback((): MentionsOutput | null => {\n if (!editor) return null;\n return buildOutput(editor);\n }, [editor]);\n\n return { editor, getOutput, clear, setContent, focus };\n}\n","import { mergeAttributes, Node } from \"@tiptap/core\";\nimport { NodeViewRendererProps } from \"@tiptap/core\";\nimport { ReactNodeViewRenderer } from \"@tiptap/react\";\n\nexport interface MentionNodeAttrs {\n id: string;\n label: string;\n entityType: string;\n /** When set, the mention came from a child level (e.g. file under workspace). Used for @rootLabel[label](id) display and serialization. */\n rootLabel?: string | null;\n}\n\n/**\n * Trigger-character prefix map.\n * Used when rendering a mention chip to prepend the correct character.\n */\nconst DEFAULT_PREFIXES: Record<string, string> = {\n workspace: \"@\",\n contract: \"@\",\n file: \"#\",\n web: \":\",\n};\n\n/**\n * Custom Tiptap Node extension for inline mention tokens.\n *\n * Each mention is an atomic inline node with `id`, `label`, and `entityType`\n * attributes. Renders as a `<span>` with `data-mention` and `data-type`\n * attributes for styling.\n */\nexport const MentionNode = Node.create({\n name: \"mention\",\n group: \"inline\",\n inline: true,\n atom: true,\n selectable: true,\n draggable: false,\n\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-id\"),\n renderHTML: (attributes) => ({ \"data-id\": attributes.id }),\n },\n label: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-label\"),\n renderHTML: (attributes) => ({ \"data-label\": attributes.label }),\n },\n entityType: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-type\"),\n renderHTML: (attributes) => ({ \"data-type\": attributes.entityType }),\n },\n rootLabel: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"data-root-label\"),\n renderHTML: (attributes) =>\n attributes.rootLabel\n ? { \"data-root-label\": attributes.rootLabel }\n : {},\n },\n };\n },\n\n parseHTML() {\n return [{ tag: 'span[data-mention]' }];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const entityType = node.attrs.entityType as string;\n const label = node.attrs.label as string;\n const prefix = DEFAULT_PREFIXES[entityType] ?? \"@\";\n // In the input box show only prefix + label; rootLabel is used only for markdown serialization.\n const display = `${prefix}${label}`;\n\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n \"data-mention\": \"\",\n class: \"mention-chip\",\n }),\n display,\n ];\n },\n\n renderText({ node }) {\n const entityType = node.attrs.entityType as string;\n const label = node.attrs.label as string;\n const prefix = DEFAULT_PREFIXES[entityType] ?? \"@\";\n return `${prefix}${label}`;\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () =>\n this.editor.commands.command(({ tr, state }) => {\n let isMention = false;\n const { selection } = state;\n const { empty, anchor } = selection;\n\n if (!empty) return false;\n\n state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n if (node.type.name === this.name) {\n isMention = true;\n tr.insertText(\"\", pos, pos + node.nodeSize);\n }\n });\n\n return isMention;\n }),\n };\n },\n});\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport type { EditorView } from \"@tiptap/pm/view\";\nimport type { MentionItem, MentionProvider } from \"../types/MentionProvider\";\n\n/* ------------------------------------------------------------------ */\n/* Suggestion state types */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionState =\n | \"idle\"\n | \"loading\"\n | \"showing\"\n | \"drilling\"\n | \"inserting\";\n\n/* ------------------------------------------------------------------ */\n/* Callbacks the React layer implements */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionCallbacks = {\n onStart: (props: SuggestionCallbackProps) => void;\n onUpdate: (props: SuggestionCallbackProps) => void;\n onExit: () => void;\n onKeyDown: (props: { event: KeyboardEvent }) => boolean;\n};\n\nexport type SuggestionCallbackProps = {\n query: string;\n trigger: string;\n clientRect: (() => DOMRect | null) | null;\n range: { from: number; to: number };\n command: (attrs: Record<string, unknown>) => void;\n};\n\n/* ------------------------------------------------------------------ */\n/* Trigger detection */\n/* ------------------------------------------------------------------ */\n\ntype TriggerMatch = {\n trigger: string;\n query: string;\n from: number;\n to: number;\n};\n\nfunction detectTrigger(\n text: string,\n cursorPos: number,\n docStartPos: number,\n triggers: string[],\n): TriggerMatch | null {\n const relCursor = cursorPos - docStartPos;\n const before = text.slice(0, relCursor);\n\n for (let i = before.length - 1; i >= 0; i--) {\n const ch = before[i];\n if (ch === \"\\n\") return null;\n\n for (const trigger of triggers) {\n if (before.substring(i, i + trigger.length) === trigger) {\n if (i === 0 || /\\s/.test(before[i - 1])) {\n const query = before.slice(i + trigger.length);\n return { trigger, query, from: docStartPos + i, to: cursorPos };\n }\n }\n }\n }\n\n return null;\n}\n\n/* ------------------------------------------------------------------ */\n/* Plugin key */\n/* ------------------------------------------------------------------ */\n\nconst suggestionPluginKey = new PluginKey(\"mentionSuggestion\");\n\n/* ------------------------------------------------------------------ */\n/* Ref-based callback container (avoids stale closure issues) */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionCallbacksRef = { current: SuggestionCallbacks };\n\n/* ------------------------------------------------------------------ */\n/* Build the single multi-trigger suggestion extension */\n/* ------------------------------------------------------------------ */\n\nexport function createSuggestionExtension(\n triggers: string[],\n callbacksRef: SuggestionCallbacksRef,\n) {\n return Extension.create({\n name: \"mentionSuggestion\",\n priority: 200,\n\n addProseMirrorPlugins() {\n const editor = this.editor;\n let active = false;\n let lastQuery: string | null = null;\n let lastTrigger: string | null = null;\n\n const getClientRect = (\n view: EditorView,\n from: number,\n ): (() => DOMRect | null) => {\n return () => {\n try {\n const coords = view.coordsAtPos(from);\n return new DOMRect(\n coords.left,\n coords.top,\n 0,\n coords.bottom - coords.top,\n );\n } catch {\n return null;\n }\n };\n };\n\n const makeCommand = (range: { from: number; to: number }) => {\n return (attrs: Record<string, unknown>) => {\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: \"mention\",\n attrs: {\n id: attrs.id,\n label: attrs.label,\n entityType: attrs.entityType,\n rootLabel: attrs.rootLabel ?? null,\n },\n },\n { type: \"text\", text: \" \" },\n ])\n .run();\n };\n };\n\n const plugin = new Plugin({\n key: suggestionPluginKey,\n\n props: {\n handleKeyDown(_view, event) {\n if (!active) return false;\n // Always read from the ref — never a stale closure\n return callbacksRef.current.onKeyDown({ event });\n },\n },\n\n view() {\n return {\n update(view, _prevState) {\n const { state } = view;\n const { selection } = state;\n\n if (!selection.empty) {\n if (active) {\n active = false;\n lastQuery = null;\n lastTrigger = null;\n callbacksRef.current.onExit();\n }\n return;\n }\n\n const $pos = selection.$from;\n const textBlock = $pos.parent;\n\n if (!textBlock.isTextblock) {\n if (active) {\n active = false;\n lastQuery = null;\n lastTrigger = null;\n callbacksRef.current.onExit();\n }\n return;\n }\n\n const blockStart = $pos.start();\n const blockText = textBlock.textContent;\n const cursorPos = $pos.pos;\n\n const match = detectTrigger(blockText, cursorPos, blockStart, triggers);\n\n if (match) {\n const range = { from: match.from, to: match.to };\n const props: SuggestionCallbackProps = {\n query: match.query,\n trigger: match.trigger,\n clientRect: getClientRect(view, match.from),\n range,\n command: makeCommand(range),\n };\n\n if (!active) {\n active = true;\n lastQuery = match.query;\n lastTrigger = match.trigger;\n callbacksRef.current.onStart(props);\n } else if (\n match.query !== lastQuery ||\n match.trigger !== lastTrigger\n ) {\n // Only fire onUpdate when something actually changed\n lastQuery = match.query;\n lastTrigger = match.trigger;\n callbacksRef.current.onUpdate(props);\n }\n } else {\n if (active) {\n active = false;\n lastQuery = null;\n lastTrigger = null;\n callbacksRef.current.onExit();\n }\n }\n },\n\n destroy() {\n if (active) {\n callbacksRef.current.onExit();\n }\n },\n };\n },\n });\n\n return [plugin];\n },\n });\n}\n","import type { JSONContent } from \"@tiptap/core\";\nimport type { MentionToken } from \"../types/MentionToken\";\n\n/**\n * Serialize a Tiptap JSON document to a markdown string.\n *\n * Mention nodes are encoded as `@[label](id)` or `@rootLabel[label](id)` when the mention has a root (e.g. file under workspace).\n * All other text passes through verbatim.\n */\nexport function serializeToMarkdown(doc: JSONContent): string {\n if (!doc.content) return \"\";\n\n const parts: string[] = [];\n\n for (const block of doc.content) {\n if (block.type === \"paragraph\") {\n parts.push(serializeParagraph(block));\n } else if (block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nfunction serializeParagraph(node: JSONContent): string {\n if (!node.content) return \"\";\n\n return node.content\n .map((child) => {\n if (child.type === \"mention\") {\n const { id, label, rootLabel } = child.attrs ?? {};\n if (rootLabel != null && rootLabel !== \"\") {\n return `@${rootLabel}[${label}](${id})`;\n }\n return `@[${label}](${id})`;\n }\n return child.text ?? \"\";\n })\n .join(\"\");\n}\n\n/**\n * Extract all `MentionToken`s from a Tiptap JSON document, in document order.\n */\nexport function extractTokens(doc: JSONContent): MentionToken[] {\n const tokens: MentionToken[] = [];\n\n function walk(node: JSONContent) {\n if (node.type === \"mention\" && node.attrs) {\n tokens.push({\n id: node.attrs.id as string,\n type: node.attrs.entityType as string,\n label: node.attrs.label as string,\n });\n }\n if (node.content) {\n for (const child of node.content) {\n walk(child);\n }\n }\n }\n\n walk(doc);\n return tokens;\n}\n\n/**\n * Extract plain text from a Tiptap JSON document.\n * Mention nodes are replaced with their label (no trigger prefix in plain text).\n */\nexport function extractPlainText(doc: JSONContent): string {\n if (!doc.content) return \"\";\n\n const parts: string[] = [];\n\n for (const block of doc.content) {\n if (block.type === \"paragraph\") {\n parts.push(extractParagraphText(block));\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nfunction extractParagraphText(node: JSONContent): string {\n if (!node.content) return \"\";\n\n return node.content\n .map((child) => {\n if (child.type === \"mention\") {\n return child.attrs?.label ?? \"\";\n }\n return child.text ?? \"\";\n })\n .join(\"\");\n}\n","import type { JSONContent } from \"@tiptap/core\";\nimport { extractTokens, extractPlainText } from \"./markdownSerializer\";\nimport type { MentionToken } from \"../types/MentionToken\";\n\n/**\n * Regex matching mention tokens:\n * - `@[label](id)` or `@[label](type:id)`\n * - `@rootLabel[label](id)` when the mention has a root (e.g. file under workspace).\n * Groups: (1) optional rootLabel, (2) label, (3) optional entityType, (4) id.\n */\nconst MENTION_RE = /@(?:([^\\[]+)\\[)?\\[([^\\]]+)\\]\\((?:([^:)]+):)?([^)]+)\\)/g;\n\n/**\n * Parse a markdown string (with `@[label](id)`, `@[label](type:id)`, or `@rootLabel[label](id)` tokens)\n * into a Tiptap-compatible JSON document.\n */\nexport function parseFromMarkdown(markdown: string): JSONContent {\n const lines = markdown.split(\"\\n\");\n\n const content: JSONContent[] = lines.map((line) => ({\n type: \"paragraph\",\n content: parseLine(line),\n }));\n\n return { type: \"doc\", content };\n}\n\nfunction parseLine(line: string): JSONContent[] {\n const nodes: JSONContent[] = [];\n let lastIndex = 0;\n\n // Reset regex state\n MENTION_RE.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = MENTION_RE.exec(line)) !== null) {\n const fullMatch = match[0];\n const rootLabel = match[1] ?? null;\n const label = match[2];\n const entityType = match[3] ?? \"unknown\";\n const id = match[4];\n\n // Text before this mention\n if (match.index > lastIndex) {\n nodes.push({\n type: \"text\",\n text: line.slice(lastIndex, match.index),\n });\n }\n\n // Mention node\n nodes.push({\n type: \"mention\",\n attrs: {\n id,\n label,\n entityType,\n rootLabel,\n },\n });\n\n lastIndex = match.index + fullMatch.length;\n }\n\n // Trailing text\n if (lastIndex < line.length) {\n nodes.push({\n type: \"text\",\n text: line.slice(lastIndex),\n });\n }\n\n // Empty line — ProseMirror needs at least one node\n if (nodes.length === 0) {\n nodes.push({ type: \"text\", text: \"\" });\n }\n\n return nodes;\n}\n\n/* ------------------------------------------------------------------ */\n/* Convenience helper: markdown → tokens + plain text */\n/* ------------------------------------------------------------------ */\n\n/**\n * Parse a markdown string and extract structured data from it.\n *\n * This is a convenience wrapper that combines `parseFromMarkdown`,\n * `extractTokens`, and `extractPlainText` into a single call.\n *\n * @example\n * ```ts\n * const { tokens, plainText } = extractFromMarkdown(\n * \"Check @Marketing[Q4 Strategy.pdf](file_1) for details\"\n * );\n * // tokens → [{ id: \"file_1\", type: \"unknown\", label: \"Q4 Strategy.pdf\" }]\n * // plainText → \"Check Q4 Strategy.pdf for details\"\n * ```\n */\nexport function extractFromMarkdown(markdown: string): {\n tokens: MentionToken[];\n plainText: string;\n} {\n const doc = parseFromMarkdown(markdown);\n return {\n tokens: extractTokens(doc),\n plainText: extractPlainText(doc),\n };\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type { MentionItem, MentionProvider } from \"../types/MentionProvider\";\nimport type {\n SuggestionCallbackProps,\n SuggestionCallbacks,\n SuggestionCallbacksRef,\n SuggestionState,\n} from \"../core/suggestionPlugin\";\n\n/* ------------------------------------------------------------------ */\n/* Public state exposed to the UI layer */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionUIState = {\n state: SuggestionState;\n items: MentionItem[];\n breadcrumbs: MentionItem[];\n activeIndex: number;\n loading: boolean;\n clientRect: (() => DOMRect | null) | null;\n trigger: string | null;\n query: string;\n};\n\nexport type SuggestionActions = {\n navigateUp: () => void;\n navigateDown: () => void;\n select: (item?: MentionItem) => void;\n goBack: () => void;\n close: () => void;\n /** Search within the current nested level (driven by the dropdown search input). */\n searchNested: (query: string) => void;\n};\n\nconst IDLE_STATE: SuggestionUIState = {\n state: \"idle\",\n items: [],\n breadcrumbs: [],\n activeIndex: 0,\n loading: false,\n clientRect: null,\n trigger: null,\n query: \"\",\n};\n\n/* ------------------------------------------------------------------ */\n/* Hook */\n/* ------------------------------------------------------------------ */\n\nexport function useSuggestion(providers: MentionProvider[]) {\n const [uiState, setUIState] = useState<SuggestionUIState>(IDLE_STATE);\n\n // --- Refs for values that callbacks need to read (avoids stale closures) ---\n const stateRef = useRef(uiState);\n stateRef.current = uiState;\n\n const providersRef = useRef(providers);\n providersRef.current = providers;\n\n const commandRef = useRef<((attrs: Record<string, unknown>) => void) | null>(\n null,\n );\n const providerRef = useRef<MentionProvider | null>(null);\n\n /* ---------------------------------------------------------------- */\n /* Fetch items from the provider */\n /* ---------------------------------------------------------------- */\n\n const fetchItems = useCallback(\n async (\n provider: MentionProvider,\n query: string,\n parent?: MentionItem,\n useSearchAll?: boolean,\n ) => {\n setUIState((prev) => ({ ...prev, loading: true, state: \"loading\" }));\n\n try {\n let items: MentionItem[];\n\n if (useSearchAll && provider.searchAll) {\n items = await provider.searchAll(query);\n } else if (parent && provider.getChildren) {\n items = await provider.getChildren(parent, query);\n } else {\n items = await provider.getRootItems(query);\n }\n\n setUIState((prev) => ({\n ...prev,\n items,\n loading: false,\n state: \"showing\",\n activeIndex: 0,\n }));\n } catch {\n setUIState((prev) => ({\n ...prev,\n items: [],\n loading: false,\n state: \"showing\",\n }));\n }\n },\n [],\n );\n\n /* ---------------------------------------------------------------- */\n /* Suggestion plugin callbacks */\n /* ---------------------------------------------------------------- */\n\n const onStart = useCallback(\n (props: SuggestionCallbackProps) => {\n const provider = providersRef.current.find(\n (p) => p.trigger === props.trigger,\n );\n if (!provider) return;\n\n providerRef.current = provider;\n commandRef.current = props.command;\n\n setUIState({\n state: \"loading\",\n items: [],\n breadcrumbs: [],\n activeIndex: 0,\n loading: true,\n clientRect: props.clientRect,\n trigger: props.trigger,\n query: props.query,\n });\n\n // Use searchAll for non-empty queries when available\n if (props.query.trim() && provider.searchAll) {\n fetchItems(provider, props.query, undefined, true);\n } else {\n fetchItems(provider, props.query);\n }\n },\n [fetchItems],\n );\n\n const onUpdate = useCallback(\n (props: SuggestionCallbackProps) => {\n const provider = providerRef.current;\n if (!provider) return;\n\n commandRef.current = props.command;\n const current = stateRef.current;\n\n // If at a nested level and the editor query changed, collapse back to root\n if (current.breadcrumbs.length > 0) {\n setUIState((prev) => ({\n ...prev,\n breadcrumbs: [],\n clientRect: props.clientRect,\n query: props.query,\n activeIndex: 0,\n }));\n } else {\n setUIState((prev) => ({\n ...prev,\n clientRect: props.clientRect,\n query: props.query,\n }));\n }\n\n // Always refetch at root level (collapsed or not)\n if (props.query.trim() && provider.searchAll) {\n fetchItems(provider, props.query, undefined, true);\n } else {\n fetchItems(provider, props.query);\n }\n },\n [fetchItems],\n );\n\n const onExit = useCallback(() => {\n providerRef.current = null;\n commandRef.current = null;\n setUIState(IDLE_STATE);\n }, []);\n\n /* ---------------------------------------------------------------- */\n /* Navigation actions */\n /* ---------------------------------------------------------------- */\n\n const navigateUp = useCallback(() => {\n setUIState((prev) => ({\n ...prev,\n activeIndex: Math.max(0, prev.activeIndex - 1),\n }));\n }, []);\n\n const navigateDown = useCallback(() => {\n setUIState((prev) => ({\n ...prev,\n activeIndex: Math.min(prev.items.length - 1, prev.activeIndex + 1),\n }));\n }, []);\n\n const select = useCallback(\n (item?: MentionItem) => {\n const current = stateRef.current;\n const selected = item ?? current.items[current.activeIndex];\n if (!selected) return;\n\n const provider = providerRef.current;\n\n // If the item has children and the provider supports it, drill down\n if (selected.hasChildren && provider?.getChildren) {\n setUIState((prev) => ({\n ...prev,\n state: \"drilling\",\n breadcrumbs: [...prev.breadcrumbs, selected],\n items: [],\n activeIndex: 0,\n query: \"\",\n }));\n\n fetchItems(provider, \"\", selected);\n return;\n }\n\n // Otherwise insert the mention\n if (commandRef.current) {\n // Derive rootLabel: from breadcrumbs (drill navigation) or from item itself (flat search)\n const rootLabel =\n current.breadcrumbs.length > 0\n ? current.breadcrumbs[0].label\n : selected.rootLabel ?? null;\n commandRef.current({\n id: selected.id,\n label: selected.label,\n entityType: selected.type,\n rootLabel,\n });\n }\n },\n [fetchItems],\n );\n\n const goBack = useCallback(() => {\n const provider = providerRef.current;\n if (!provider) return;\n\n setUIState((prev) => {\n const newBreadcrumbs = prev.breadcrumbs.slice(0, -1);\n const parent = newBreadcrumbs[newBreadcrumbs.length - 1];\n\n if (parent) {\n fetchItems(provider, \"\", parent);\n } else {\n fetchItems(provider, prev.query);\n }\n\n return {\n ...prev,\n breadcrumbs: newBreadcrumbs,\n items: [],\n activeIndex: 0,\n state: \"loading\" as const,\n };\n });\n }, [fetchItems]);\n\n const close = useCallback(() => {\n setUIState(IDLE_STATE);\n }, []);\n\n /* ---------------------------------------------------------------- */\n /* Nested search (driven by the dropdown's search input) */\n /* ---------------------------------------------------------------- */\n\n const searchNested = useCallback(\n (query: string) => {\n const provider = providerRef.current;\n if (!provider) return;\n\n const current = stateRef.current;\n const parent = current.breadcrumbs[current.breadcrumbs.length - 1];\n if (parent) {\n fetchItems(provider, query, parent);\n }\n },\n [fetchItems],\n );\n\n /* ---------------------------------------------------------------- */\n /* Keyboard handler */\n /* ---------------------------------------------------------------- */\n\n const onKeyDown = useCallback(\n ({ event }: { event: KeyboardEvent }): boolean => {\n const current = stateRef.current;\n if (current.state === \"idle\") return false;\n\n switch (event.key) {\n case \"ArrowUp\":\n event.preventDefault();\n navigateUp();\n return true;\n case \"ArrowDown\":\n event.preventDefault();\n navigateDown();\n return true;\n case \"Enter\": {\n event.preventDefault();\n const selectedItem = current.items[current.activeIndex];\n if (selectedItem) {\n select(selectedItem);\n }\n return true;\n }\n case \"ArrowRight\": {\n const activeItem = current.items[current.activeIndex];\n if (activeItem?.hasChildren) {\n event.preventDefault();\n select(activeItem);\n return true;\n }\n return false;\n }\n case \"ArrowLeft\":\n if (current.breadcrumbs.length > 0) {\n event.preventDefault();\n goBack();\n return true;\n }\n return false;\n case \"Escape\":\n event.preventDefault();\n close();\n return true;\n default:\n return false;\n }\n },\n [navigateUp, navigateDown, select, goBack, close],\n );\n\n /* ---------------------------------------------------------------- */\n /* Build a ref that always points to the latest callbacks. */\n /* The ProseMirror plugin reads from this ref — no stale closures. */\n /* ---------------------------------------------------------------- */\n\n const callbacksRef: SuggestionCallbacksRef = useRef<SuggestionCallbacks>({\n onStart,\n onUpdate,\n onExit,\n onKeyDown,\n });\n\n // Update on every render so the PM plugin always sees fresh functions\n callbacksRef.current = { onStart, onUpdate, onExit, onKeyDown };\n\n const actions: SuggestionActions = {\n navigateUp,\n navigateDown,\n select,\n goBack,\n close,\n searchNested,\n };\n\n return { uiState, actions, callbacksRef };\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { MentionItem } from \"../types/MentionProvider\";\nimport { listboxAttrs, optionAttrs } from \"../utils/ariaHelpers\";\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\n\nexport type SuggestionListProps = {\n items: MentionItem[];\n activeIndex: number;\n breadcrumbs: MentionItem[];\n loading: boolean;\n trigger: string | null;\n clientRect: (() => DOMRect | null) | null;\n onSelect: (item: MentionItem) => void;\n onHover: (index: number) => void;\n onGoBack: () => void;\n /** Called when the user types in the nested search input. */\n onSearchNested?: (query: string) => void;\n /** Navigate the active index up. */\n onNavigateUp?: () => void;\n /** Navigate the active index down. */\n onNavigateDown?: () => void;\n /** Close the suggestion dropdown. */\n onClose?: () => void;\n renderItem?: (item: MentionItem, depth: number) => ReactNode;\n};\n\nconst LISTBOX_ID = \"mentions-suggestion-listbox\";\n\n/* ------------------------------------------------------------------ */\n/* Component */\n/* ------------------------------------------------------------------ */\n\nexport function SuggestionList({\n items,\n activeIndex,\n breadcrumbs,\n loading,\n trigger,\n clientRect,\n onSelect,\n onHover,\n onGoBack,\n onSearchNested,\n onNavigateUp,\n onNavigateDown,\n onClose,\n renderItem,\n}: SuggestionListProps) {\n const listRef = useRef<HTMLDivElement>(null);\n const searchInputRef = useRef<HTMLInputElement>(null);\n const depth = breadcrumbs.length;\n const [nestedQuery, setNestedQuery] = useState(\"\");\n\n // Reset nested query when breadcrumbs change (drill/back)\n const breadcrumbKey = breadcrumbs.map((b) => b.id).join(\"/\");\n const prevBreadcrumbKey = useRef(breadcrumbKey);\n useEffect(() => {\n if (prevBreadcrumbKey.current !== breadcrumbKey) {\n setNestedQuery(\"\");\n prevBreadcrumbKey.current = breadcrumbKey;\n }\n }, [breadcrumbKey]);\n\n // Auto-focus the search input when entering a nested level\n useEffect(() => {\n if (breadcrumbs.length > 0 && searchInputRef.current) {\n // Small delay to ensure the input is rendered\n requestAnimationFrame(() => searchInputRef.current?.focus());\n }\n }, [breadcrumbKey, breadcrumbs.length]);\n\n // Scroll the active item into view\n useEffect(() => {\n if (!listRef.current) return;\n const active = listRef.current.querySelector('[aria-selected=\"true\"]');\n active?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n // Position the popover based on clientRect\n const style = usePopoverPosition(clientRect);\n\n if (items.length === 0 && !loading) return null;\n\n /** Handle keyboard events inside the nested search input. */\n const handleSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n onNavigateDown?.();\n break;\n case \"ArrowUp\":\n e.preventDefault();\n onNavigateUp?.();\n break;\n case \"Enter\": {\n e.preventDefault();\n e.stopPropagation();\n const selectedItem = items[activeIndex];\n if (selectedItem) onSelect(selectedItem);\n break;\n }\n case \"Escape\":\n e.preventDefault();\n onClose?.();\n break;\n case \"Backspace\":\n if (nestedQuery === \"\") {\n e.preventDefault();\n onGoBack();\n }\n break;\n // No default — let all other keys pass through to the input\n }\n };\n\n return (\n <div\n data-suggestions=\"\"\n data-trigger={trigger}\n style={style}\n ref={listRef}\n >\n {/* Breadcrumbs for nested navigation */}\n {breadcrumbs.length > 0 && (\n <div data-suggestion-breadcrumb=\"\">\n <button\n type=\"button\"\n data-suggestion-back=\"\"\n onClick={onGoBack}\n aria-label=\"Go back\"\n >\n ←\n </button>\n {breadcrumbs.map((crumb, i) => (\n <span key={crumb.id} data-suggestion-breadcrumb-item=\"\">\n {i > 0 && <span data-suggestion-breadcrumb-sep=\"\">/</span>}\n {crumb.label}\n </span>\n ))}\n </div>\n )}\n\n {/* Search input for nested levels */}\n {breadcrumbs.length > 0 && (\n <div data-suggestion-search=\"\">\n <input\n ref={searchInputRef}\n type=\"text\"\n data-suggestion-search-input=\"\"\n placeholder=\"Search...\"\n value={nestedQuery}\n onChange={(e) => {\n const q = e.target.value;\n setNestedQuery(q);\n onSearchNested?.(q);\n }}\n onKeyDown={handleSearchKeyDown}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </div>\n )}\n\n {/* Loading indicator */}\n {loading && (\n <div data-suggestion-loading=\"\">Loading...</div>\n )}\n\n {/* Item list */}\n {!loading && (\n <div {...listboxAttrs(LISTBOX_ID, `${trigger ?? \"\"} suggestions`)}>\n {items.map((item, index) => {\n const isActive = index === activeIndex;\n const itemId = `mention-option-${item.id}`;\n\n return (\n <div\n key={item.id}\n {...optionAttrs(itemId, isActive, index)}\n data-suggestion-item=\"\"\n data-suggestion-item-active={isActive ? \"\" : undefined}\n data-has-children={item.hasChildren ? \"\" : undefined}\n onMouseEnter={() => onHover(index)}\n onClick={() => onSelect(item)}\n >\n {renderItem ? (\n renderItem(item, depth)\n ) : (\n <DefaultSuggestionItem item={item} />\n )}\n </div>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Default item render */\n/* ------------------------------------------------------------------ */\n\nfunction DefaultSuggestionItem({ item }: { item: MentionItem }) {\n return (\n <>\n {item.icon && (\n <span data-suggestion-item-icon=\"\">{item.icon}</span>\n )}\n <span data-suggestion-item-label=\"\">{item.label}</span>\n {item.description && (\n <span data-suggestion-item-description=\"\">{item.description}</span>\n )}\n {item.hasChildren && (\n <span data-suggestion-item-chevron=\"\" aria-hidden=\"true\">\n ›\n </span>\n )}\n </>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Popover positioning */\n/* ------------------------------------------------------------------ */\n\nfunction usePopoverPosition(\n clientRect: (() => DOMRect | null) | null,\n): React.CSSProperties {\n if (!clientRect) {\n return { display: \"none\" };\n }\n\n const rect = clientRect();\n if (!rect) {\n return { display: \"none\" };\n }\n\n return {\n position: \"fixed\",\n left: `${rect.left}px`,\n top: `${rect.bottom + 4}px`,\n zIndex: 50,\n };\n}\n","/**\n * Generates ARIA attributes for the suggestion combobox pattern.\n */\n\nexport function comboboxAttrs(expanded: boolean, listboxId: string) {\n return {\n role: \"combobox\" as const,\n \"aria-haspopup\": \"listbox\" as const,\n \"aria-expanded\": expanded,\n \"aria-owns\": expanded ? listboxId : undefined,\n };\n}\n\nexport function listboxAttrs(id: string, label: string) {\n return {\n id,\n role: \"listbox\" as const,\n \"aria-label\": label,\n };\n}\n\nexport function optionAttrs(\n id: string,\n selected: boolean,\n index: number,\n) {\n return {\n id,\n role: \"option\" as const,\n \"aria-selected\": selected,\n \"aria-posinset\": index + 1,\n };\n}\n"],"mappings":"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","names":["import_react","import_react","import_core","import_state","import_core","StarterKit","Placeholder","editor","import_react","import_react","import_jsx_runtime","LISTBOX_ID","MentionsInput"]}
|
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { useEditor } from "@tiptap/react";
|
|
|
8
8
|
import StarterKit from "@tiptap/starter-kit";
|
|
9
9
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
10
10
|
import { Extension as Extension2 } from "@tiptap/core";
|
|
11
|
+
import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
|
|
11
12
|
|
|
12
13
|
// src/core/mentionExtension.ts
|
|
13
14
|
import { mergeAttributes, Node } from "@tiptap/core";
|
|
@@ -114,6 +115,7 @@ var suggestionPluginKey = new PluginKey("mentionSuggestion");
|
|
|
114
115
|
function createSuggestionExtension(triggers, callbacksRef) {
|
|
115
116
|
return Extension.create({
|
|
116
117
|
name: "mentionSuggestion",
|
|
118
|
+
priority: 200,
|
|
117
119
|
addProseMirrorPlugins() {
|
|
118
120
|
const editor = this.editor;
|
|
119
121
|
let active = false;
|
|
@@ -342,6 +344,13 @@ function parseLine(line) {
|
|
|
342
344
|
}
|
|
343
345
|
return nodes;
|
|
344
346
|
}
|
|
347
|
+
function extractFromMarkdown(markdown) {
|
|
348
|
+
const doc = parseFromMarkdown(markdown);
|
|
349
|
+
return {
|
|
350
|
+
tokens: extractTokens(doc),
|
|
351
|
+
plainText: extractPlainText(doc)
|
|
352
|
+
};
|
|
353
|
+
}
|
|
345
354
|
|
|
346
355
|
// src/hooks/useMentionsEditor.ts
|
|
347
356
|
function buildOutput(editor) {
|
|
@@ -355,6 +364,7 @@ function buildOutput(editor) {
|
|
|
355
364
|
function createSubmitExtension(onSubmitRef, clearOnSubmitRef) {
|
|
356
365
|
return Extension2.create({
|
|
357
366
|
name: "submitShortcut",
|
|
367
|
+
priority: 150,
|
|
358
368
|
addKeyboardShortcuts() {
|
|
359
369
|
return {
|
|
360
370
|
"Mod-Enter": () => {
|
|
@@ -370,22 +380,35 @@ function createSubmitExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
370
380
|
}
|
|
371
381
|
});
|
|
372
382
|
}
|
|
383
|
+
var enterSubmitPluginKey = new PluginKey2("enterSubmit");
|
|
373
384
|
function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
374
385
|
return Extension2.create({
|
|
375
386
|
name: "enterSubmit",
|
|
376
|
-
priority:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
387
|
+
priority: 150,
|
|
388
|
+
addProseMirrorPlugins() {
|
|
389
|
+
const editor = this.editor;
|
|
390
|
+
return [
|
|
391
|
+
new Plugin2({
|
|
392
|
+
key: enterSubmitPluginKey,
|
|
393
|
+
props: {
|
|
394
|
+
handleKeyDown(_view, event) {
|
|
395
|
+
if (event.key !== "Enter") return false;
|
|
396
|
+
if (event.shiftKey) {
|
|
397
|
+
editor.commands.splitBlock();
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
if (event.metaKey || event.ctrlKey) return false;
|
|
401
|
+
if (onSubmitRef.current) {
|
|
402
|
+
onSubmitRef.current(buildOutput(editor));
|
|
403
|
+
if (clearOnSubmitRef.current) {
|
|
404
|
+
editor.commands.clearContent(true);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return true;
|
|
384
408
|
}
|
|
385
409
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
};
|
|
410
|
+
})
|
|
411
|
+
];
|
|
389
412
|
}
|
|
390
413
|
});
|
|
391
414
|
}
|
|
@@ -438,10 +461,12 @@ function useMentionsEditor({
|
|
|
438
461
|
bulletList: false,
|
|
439
462
|
orderedList: false,
|
|
440
463
|
listItem: false,
|
|
441
|
-
horizontalRule: false
|
|
464
|
+
horizontalRule: false,
|
|
465
|
+
hardBreak: false
|
|
442
466
|
}),
|
|
443
467
|
Placeholder.configure({
|
|
444
|
-
placeholder: placeholder ?? "Type a message..."
|
|
468
|
+
placeholder: placeholder ?? "Type a message...",
|
|
469
|
+
showOnlyCurrent: false
|
|
445
470
|
}),
|
|
446
471
|
MentionNode,
|
|
447
472
|
suggestionExtension,
|
|
@@ -509,10 +534,17 @@ function useSuggestion(providers) {
|
|
|
509
534
|
);
|
|
510
535
|
const providerRef = useRef2(null);
|
|
511
536
|
const fetchItems = useCallback2(
|
|
512
|
-
async (provider, query, parent) => {
|
|
537
|
+
async (provider, query, parent, useSearchAll) => {
|
|
513
538
|
setUIState((prev) => ({ ...prev, loading: true, state: "loading" }));
|
|
514
539
|
try {
|
|
515
|
-
|
|
540
|
+
let items;
|
|
541
|
+
if (useSearchAll && provider.searchAll) {
|
|
542
|
+
items = await provider.searchAll(query);
|
|
543
|
+
} else if (parent && provider.getChildren) {
|
|
544
|
+
items = await provider.getChildren(parent, query);
|
|
545
|
+
} else {
|
|
546
|
+
items = await provider.getRootItems(query);
|
|
547
|
+
}
|
|
516
548
|
setUIState((prev) => ({
|
|
517
549
|
...prev,
|
|
518
550
|
items,
|
|
@@ -549,7 +581,11 @@ function useSuggestion(providers) {
|
|
|
549
581
|
trigger: props.trigger,
|
|
550
582
|
query: props.query
|
|
551
583
|
});
|
|
552
|
-
|
|
584
|
+
if (props.query.trim() && provider.searchAll) {
|
|
585
|
+
fetchItems(provider, props.query, void 0, true);
|
|
586
|
+
} else {
|
|
587
|
+
fetchItems(provider, props.query);
|
|
588
|
+
}
|
|
553
589
|
},
|
|
554
590
|
[fetchItems]
|
|
555
591
|
);
|
|
@@ -558,12 +594,25 @@ function useSuggestion(providers) {
|
|
|
558
594
|
const provider = providerRef.current;
|
|
559
595
|
if (!provider) return;
|
|
560
596
|
commandRef.current = props.command;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
597
|
+
const current = stateRef.current;
|
|
598
|
+
if (current.breadcrumbs.length > 0) {
|
|
599
|
+
setUIState((prev) => ({
|
|
600
|
+
...prev,
|
|
601
|
+
breadcrumbs: [],
|
|
602
|
+
clientRect: props.clientRect,
|
|
603
|
+
query: props.query,
|
|
604
|
+
activeIndex: 0
|
|
605
|
+
}));
|
|
606
|
+
} else {
|
|
607
|
+
setUIState((prev) => ({
|
|
608
|
+
...prev,
|
|
609
|
+
clientRect: props.clientRect,
|
|
610
|
+
query: props.query
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
if (props.query.trim() && provider.searchAll) {
|
|
614
|
+
fetchItems(provider, props.query, void 0, true);
|
|
615
|
+
} else {
|
|
567
616
|
fetchItems(provider, props.query);
|
|
568
617
|
}
|
|
569
618
|
},
|
|
@@ -605,7 +654,7 @@ function useSuggestion(providers) {
|
|
|
605
654
|
return;
|
|
606
655
|
}
|
|
607
656
|
if (commandRef.current) {
|
|
608
|
-
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : null;
|
|
657
|
+
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
609
658
|
commandRef.current({
|
|
610
659
|
id: selected.id,
|
|
611
660
|
label: selected.label,
|
|
@@ -639,6 +688,18 @@ function useSuggestion(providers) {
|
|
|
639
688
|
const close = useCallback2(() => {
|
|
640
689
|
setUIState(IDLE_STATE);
|
|
641
690
|
}, []);
|
|
691
|
+
const searchNested = useCallback2(
|
|
692
|
+
(query) => {
|
|
693
|
+
const provider = providerRef.current;
|
|
694
|
+
if (!provider) return;
|
|
695
|
+
const current = stateRef.current;
|
|
696
|
+
const parent = current.breadcrumbs[current.breadcrumbs.length - 1];
|
|
697
|
+
if (parent) {
|
|
698
|
+
fetchItems(provider, query, parent);
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
[fetchItems]
|
|
702
|
+
);
|
|
642
703
|
const onKeyDown = useCallback2(
|
|
643
704
|
({ event }) => {
|
|
644
705
|
const current = stateRef.current;
|
|
@@ -698,13 +759,14 @@ function useSuggestion(providers) {
|
|
|
698
759
|
navigateDown,
|
|
699
760
|
select,
|
|
700
761
|
goBack,
|
|
701
|
-
close
|
|
762
|
+
close,
|
|
763
|
+
searchNested
|
|
702
764
|
};
|
|
703
765
|
return { uiState, actions, callbacksRef };
|
|
704
766
|
}
|
|
705
767
|
|
|
706
768
|
// src/components/SuggestionList.tsx
|
|
707
|
-
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
769
|
+
import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
708
770
|
|
|
709
771
|
// src/utils/ariaHelpers.ts
|
|
710
772
|
function comboboxAttrs(expanded, listboxId) {
|
|
@@ -744,10 +806,29 @@ function SuggestionList({
|
|
|
744
806
|
onSelect,
|
|
745
807
|
onHover,
|
|
746
808
|
onGoBack,
|
|
809
|
+
onSearchNested,
|
|
810
|
+
onNavigateUp,
|
|
811
|
+
onNavigateDown,
|
|
812
|
+
onClose,
|
|
747
813
|
renderItem
|
|
748
814
|
}) {
|
|
749
815
|
const listRef = useRef3(null);
|
|
816
|
+
const searchInputRef = useRef3(null);
|
|
750
817
|
const depth = breadcrumbs.length;
|
|
818
|
+
const [nestedQuery, setNestedQuery] = useState2("");
|
|
819
|
+
const breadcrumbKey = breadcrumbs.map((b) => b.id).join("/");
|
|
820
|
+
const prevBreadcrumbKey = useRef3(breadcrumbKey);
|
|
821
|
+
useEffect2(() => {
|
|
822
|
+
if (prevBreadcrumbKey.current !== breadcrumbKey) {
|
|
823
|
+
setNestedQuery("");
|
|
824
|
+
prevBreadcrumbKey.current = breadcrumbKey;
|
|
825
|
+
}
|
|
826
|
+
}, [breadcrumbKey]);
|
|
827
|
+
useEffect2(() => {
|
|
828
|
+
if (breadcrumbs.length > 0 && searchInputRef.current) {
|
|
829
|
+
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
830
|
+
}
|
|
831
|
+
}, [breadcrumbKey, breadcrumbs.length]);
|
|
751
832
|
useEffect2(() => {
|
|
752
833
|
if (!listRef.current) return;
|
|
753
834
|
const active = listRef.current.querySelector('[aria-selected="true"]');
|
|
@@ -755,6 +836,35 @@ function SuggestionList({
|
|
|
755
836
|
}, [activeIndex]);
|
|
756
837
|
const style = usePopoverPosition(clientRect);
|
|
757
838
|
if (items.length === 0 && !loading) return null;
|
|
839
|
+
const handleSearchKeyDown = (e) => {
|
|
840
|
+
switch (e.key) {
|
|
841
|
+
case "ArrowDown":
|
|
842
|
+
e.preventDefault();
|
|
843
|
+
onNavigateDown?.();
|
|
844
|
+
break;
|
|
845
|
+
case "ArrowUp":
|
|
846
|
+
e.preventDefault();
|
|
847
|
+
onNavigateUp?.();
|
|
848
|
+
break;
|
|
849
|
+
case "Enter": {
|
|
850
|
+
e.preventDefault();
|
|
851
|
+
e.stopPropagation();
|
|
852
|
+
const selectedItem = items[activeIndex];
|
|
853
|
+
if (selectedItem) onSelect(selectedItem);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
case "Escape":
|
|
857
|
+
e.preventDefault();
|
|
858
|
+
onClose?.();
|
|
859
|
+
break;
|
|
860
|
+
case "Backspace":
|
|
861
|
+
if (nestedQuery === "") {
|
|
862
|
+
e.preventDefault();
|
|
863
|
+
onGoBack();
|
|
864
|
+
}
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
};
|
|
758
868
|
return /* @__PURE__ */ jsxs(
|
|
759
869
|
"div",
|
|
760
870
|
{
|
|
@@ -779,6 +889,24 @@ function SuggestionList({
|
|
|
779
889
|
crumb.label
|
|
780
890
|
] }, crumb.id))
|
|
781
891
|
] }),
|
|
892
|
+
breadcrumbs.length > 0 && /* @__PURE__ */ jsx("div", { "data-suggestion-search": "", children: /* @__PURE__ */ jsx(
|
|
893
|
+
"input",
|
|
894
|
+
{
|
|
895
|
+
ref: searchInputRef,
|
|
896
|
+
type: "text",
|
|
897
|
+
"data-suggestion-search-input": "",
|
|
898
|
+
placeholder: "Search...",
|
|
899
|
+
value: nestedQuery,
|
|
900
|
+
onChange: (e) => {
|
|
901
|
+
const q = e.target.value;
|
|
902
|
+
setNestedQuery(q);
|
|
903
|
+
onSearchNested?.(q);
|
|
904
|
+
},
|
|
905
|
+
onKeyDown: handleSearchKeyDown,
|
|
906
|
+
autoComplete: "off",
|
|
907
|
+
spellCheck: false
|
|
908
|
+
}
|
|
909
|
+
) }),
|
|
782
910
|
loading && /* @__PURE__ */ jsx("div", { "data-suggestion-loading": "", children: "Loading..." }),
|
|
783
911
|
!loading && /* @__PURE__ */ jsx("div", { ...listboxAttrs(LISTBOX_ID, `${trigger ?? ""} suggestions`), children: items.map((item, index) => {
|
|
784
912
|
const isActive = index === activeIndex;
|
|
@@ -861,7 +989,8 @@ var MentionsInput = forwardRef(
|
|
|
861
989
|
[clear, setContent, focus]
|
|
862
990
|
);
|
|
863
991
|
const isExpanded = uiState.state !== "idle";
|
|
864
|
-
const handleHover = useCallback3((
|
|
992
|
+
const handleHover = useCallback3((index) => {
|
|
993
|
+
void index;
|
|
865
994
|
}, []);
|
|
866
995
|
return /* @__PURE__ */ jsxs2(
|
|
867
996
|
"div",
|
|
@@ -885,6 +1014,10 @@ var MentionsInput = forwardRef(
|
|
|
885
1014
|
onSelect: (item) => actions.select(item),
|
|
886
1015
|
onHover: handleHover,
|
|
887
1016
|
onGoBack: actions.goBack,
|
|
1017
|
+
onSearchNested: actions.searchNested,
|
|
1018
|
+
onNavigateUp: actions.navigateUp,
|
|
1019
|
+
onNavigateDown: actions.navigateDown,
|
|
1020
|
+
onClose: actions.close,
|
|
888
1021
|
renderItem
|
|
889
1022
|
}
|
|
890
1023
|
)
|
|
@@ -895,6 +1028,7 @@ var MentionsInput = forwardRef(
|
|
|
895
1028
|
);
|
|
896
1029
|
export {
|
|
897
1030
|
MentionsInput,
|
|
1031
|
+
extractFromMarkdown,
|
|
898
1032
|
parseFromMarkdown,
|
|
899
1033
|
serializeToMarkdown
|
|
900
1034
|
};
|