@relevaince/mentions 0.5.0 → 0.6.1
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 +95 -0
- package/dist/index.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +134 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +132 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ This is **not** a simple `@mention` dropdown. It is a resource-addressing langua
|
|
|
13
13
|
- **Async providers** — fetch suggestions from any API
|
|
14
14
|
- **Debounced fetching** — per-provider `debounceMs` prevents API floods
|
|
15
15
|
- **Structured output** — returns `{ markdown, tokens, plainText }` on every change
|
|
16
|
+
- **`useMentionsContent`** — hook that pairs `onChange` with reactive `output` and `hasContent` (e.g. disable submit until there is text)
|
|
16
17
|
- **Markdown serialization** — `@[label](id)` token syntax for storage and LLM context
|
|
17
18
|
- **Markdown parsing** — `extractFromMarkdown()` returns tokens + plain text from a markdown string
|
|
18
19
|
- **Headless styling** — zero bundled CSS, style via `data-*` attributes with Tailwind or plain CSS
|
|
@@ -31,6 +32,7 @@ This is **not** a simple `@mention` dropdown. It is a resource-addressing langua
|
|
|
31
32
|
- **Portal support** — render the dropdown into a custom container
|
|
32
33
|
- **Tab to complete** — Tab selects the active suggestion
|
|
33
34
|
- **Trigger gating** — `allowTrigger` to conditionally suppress the dropdown
|
|
35
|
+
- **Streaming support** — stream AI-generated text into the editor with automatic mention parsing
|
|
34
36
|
- **Multi-instance** — unique ARIA IDs per component instance
|
|
35
37
|
|
|
36
38
|
## Install
|
|
@@ -192,6 +194,8 @@ type MentionsOutput = {
|
|
|
192
194
|
| `allowTrigger` | `(trigger, { textBefore }) => boolean` | — | Conditionally suppress the dropdown |
|
|
193
195
|
| `validateMention` | `(token) => boolean \| Promise<boolean>` | — | Validate mentions; invalid ones get `data-mention-invalid` |
|
|
194
196
|
| `portalContainer` | `HTMLElement` | — | Render dropdown into a custom DOM node |
|
|
197
|
+
| `streaming` | `boolean` | `false` | Signals the editor is receiving streamed content (suppresses triggers, blocks user input, throttles onChange) |
|
|
198
|
+
| `onStreamingComplete` | `(output: MentionsOutput) => void` | — | Fires once when `streaming` transitions from `true` to `false` with the final output |
|
|
195
199
|
|
|
196
200
|
## Imperative ref API
|
|
197
201
|
|
|
@@ -228,9 +232,100 @@ function Chat() {
|
|
|
228
232
|
|--------|-----------|-------------|
|
|
229
233
|
| `clear` | `() => void` | Clears all editor content |
|
|
230
234
|
| `setContent` | `(markdown: string) => void` | Replaces content with a markdown string (mention tokens are parsed) |
|
|
235
|
+
| `appendText` | `(text: string) => void` | Appends plain text at the end (no mention parsing — use for plain-text streaming) |
|
|
231
236
|
| `focus` | `() => void` | Focuses the editor and places the cursor at the end |
|
|
232
237
|
| `getOutput` | `() => MentionsOutput \| null` | Reads the current structured output without waiting for onChange |
|
|
233
238
|
|
|
239
|
+
## Hooks
|
|
240
|
+
|
|
241
|
+
### `useMentionsContent`
|
|
242
|
+
|
|
243
|
+
Keeps the latest `MentionsOutput` in React state so UI can react to editor content. `ref.current.getOutput()` does not trigger re-renders; this hook wires `onChange` for you and exposes `hasContent` from trimmed `plainText`.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
function useMentionsContent(): {
|
|
247
|
+
output: MentionsOutput | null;
|
|
248
|
+
onChange: (output: MentionsOutput) => void;
|
|
249
|
+
hasContent: boolean;
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Submit button disabled when empty:**
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { useRef } from "react";
|
|
257
|
+
import {
|
|
258
|
+
MentionsInput,
|
|
259
|
+
useMentionsContent,
|
|
260
|
+
type MentionsInputHandle,
|
|
261
|
+
} from "@relevaince/mentions";
|
|
262
|
+
|
|
263
|
+
function Composer() {
|
|
264
|
+
const textareaRef = useRef<MentionsInputHandle>(null);
|
|
265
|
+
const { output, onChange, hasContent } = useMentionsContent();
|
|
266
|
+
|
|
267
|
+
function onSubmit() {
|
|
268
|
+
if (!output) return;
|
|
269
|
+
sendMessage(output);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<>
|
|
274
|
+
<MentionsInput
|
|
275
|
+
ref={textareaRef}
|
|
276
|
+
providers={providers}
|
|
277
|
+
onChange={onChange}
|
|
278
|
+
onSubmit={onSubmit}
|
|
279
|
+
/>
|
|
280
|
+
<button type="button" disabled={!hasContent} onClick={onSubmit}>
|
|
281
|
+
Send
|
|
282
|
+
</button>
|
|
283
|
+
</>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Streaming
|
|
289
|
+
|
|
290
|
+
Stream AI-generated text into the editor while maintaining mention state. Set `streaming={true}` to enter streaming mode: the suggestion dropdown is suppressed, user keyboard/paste input is blocked, and `onChange` is throttled (~150 ms). Call `ref.current.setContent(accumulated)` on each chunk — completed mention tokens are parsed into chips automatically.
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
const ref = useRef<MentionsInputHandle>(null);
|
|
294
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
295
|
+
|
|
296
|
+
async function enhancePrompt() {
|
|
297
|
+
setIsStreaming(true);
|
|
298
|
+
let accumulated = "";
|
|
299
|
+
|
|
300
|
+
const stream = await fetchEnhancedPrompt(currentPrompt);
|
|
301
|
+
for await (const chunk of stream) {
|
|
302
|
+
accumulated += chunk;
|
|
303
|
+
ref.current?.setContent(accumulated);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
setIsStreaming(false);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
<MentionsInput
|
|
310
|
+
ref={ref}
|
|
311
|
+
streaming={isStreaming}
|
|
312
|
+
providers={providers}
|
|
313
|
+
onChange={handleChange}
|
|
314
|
+
onStreamingComplete={(output) => {
|
|
315
|
+
console.log("Final tokens:", output.tokens);
|
|
316
|
+
}}
|
|
317
|
+
/>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**How it works:**
|
|
321
|
+
|
|
322
|
+
1. Set `streaming={true}` — the editor enters streaming mode
|
|
323
|
+
2. On each chunk, accumulate the full text and call `ref.current.setContent(accumulated)`
|
|
324
|
+
3. Incomplete mention tokens (e.g. `@[NDA`) render as plain text until the full `@[label](id)` syntax is received, then snap into mention chips
|
|
325
|
+
4. Set `streaming={false}` — the editor exits streaming mode, fires a final `onChange` and `onStreamingComplete`
|
|
326
|
+
|
|
327
|
+
For plain-text-only streaming (no mention syntax in chunks), use `ref.current.appendText(chunk)` instead for better performance.
|
|
328
|
+
|
|
234
329
|
## Keyboard shortcuts
|
|
235
330
|
|
|
236
331
|
| Key | Context | Action |
|
package/dist/index.d.mts
CHANGED
|
@@ -150,6 +150,17 @@ type MentionsInputProps = {
|
|
|
150
150
|
validateMention?: (token: MentionToken) => boolean | Promise<boolean>;
|
|
151
151
|
/** DOM element to portal the suggestion dropdown into. */
|
|
152
152
|
portalContainer?: HTMLElement;
|
|
153
|
+
/**
|
|
154
|
+
* Signals the editor is receiving streamed content (e.g. from an AI).
|
|
155
|
+
* When `true`: suggestion triggers are suppressed, user keyboard/paste
|
|
156
|
+
* input is blocked, and `onChange` is throttled (~150 ms).
|
|
157
|
+
*/
|
|
158
|
+
streaming?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Fires once when `streaming` transitions from `true` to `false`
|
|
161
|
+
* with the final structured output.
|
|
162
|
+
*/
|
|
163
|
+
onStreamingComplete?: (output: MentionsOutput) => void;
|
|
153
164
|
};
|
|
154
165
|
/**
|
|
155
166
|
* Imperative handle exposed via `ref` on `<MentionsInput>`.
|
|
@@ -167,8 +178,10 @@ type MentionsInputProps = {
|
|
|
167
178
|
type MentionsInputHandle = {
|
|
168
179
|
/** Clear all editor content. */
|
|
169
180
|
clear: () => void;
|
|
170
|
-
/** Replace editor content with a markdown string. */
|
|
181
|
+
/** Replace editor content with a markdown string. Mention tokens are parsed. */
|
|
171
182
|
setContent: (markdown: string) => void;
|
|
183
|
+
/** Append plain text at the end of the document (no mention parsing). */
|
|
184
|
+
appendText: (text: string) => void;
|
|
172
185
|
/** Focus the editor. */
|
|
173
186
|
focus: () => void;
|
|
174
187
|
/** Read the current structured output without waiting for onChange. */
|
|
@@ -184,6 +197,12 @@ type MentionsInputHandle = {
|
|
|
184
197
|
*/
|
|
185
198
|
declare const MentionsInput: React.ForwardRefExoticComponent<MentionsInputProps & React.RefAttributes<MentionsInputHandle>>;
|
|
186
199
|
|
|
200
|
+
declare function useMentionsContent(): {
|
|
201
|
+
output: MentionsOutput | null;
|
|
202
|
+
onChange: (o: MentionsOutput) => void;
|
|
203
|
+
hasContent: boolean;
|
|
204
|
+
};
|
|
205
|
+
|
|
187
206
|
/**
|
|
188
207
|
* Serialize a Tiptap JSON document to a markdown string.
|
|
189
208
|
*
|
|
@@ -217,4 +236,4 @@ declare function extractFromMarkdown(markdown: string): {
|
|
|
217
236
|
plainText: string;
|
|
218
237
|
};
|
|
219
238
|
|
|
220
|
-
export { type MentionItem, type MentionProvider, type MentionToken, MentionsInput, type MentionsInputHandle, type MentionsInputProps, type MentionsOutput, extractFromMarkdown, parseFromMarkdown, serializeToMarkdown };
|
|
239
|
+
export { type MentionItem, type MentionProvider, type MentionToken, MentionsInput, type MentionsInputHandle, type MentionsInputProps, type MentionsOutput, extractFromMarkdown, parseFromMarkdown, serializeToMarkdown, useMentionsContent };
|
package/dist/index.d.ts
CHANGED
|
@@ -150,6 +150,17 @@ type MentionsInputProps = {
|
|
|
150
150
|
validateMention?: (token: MentionToken) => boolean | Promise<boolean>;
|
|
151
151
|
/** DOM element to portal the suggestion dropdown into. */
|
|
152
152
|
portalContainer?: HTMLElement;
|
|
153
|
+
/**
|
|
154
|
+
* Signals the editor is receiving streamed content (e.g. from an AI).
|
|
155
|
+
* When `true`: suggestion triggers are suppressed, user keyboard/paste
|
|
156
|
+
* input is blocked, and `onChange` is throttled (~150 ms).
|
|
157
|
+
*/
|
|
158
|
+
streaming?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Fires once when `streaming` transitions from `true` to `false`
|
|
161
|
+
* with the final structured output.
|
|
162
|
+
*/
|
|
163
|
+
onStreamingComplete?: (output: MentionsOutput) => void;
|
|
153
164
|
};
|
|
154
165
|
/**
|
|
155
166
|
* Imperative handle exposed via `ref` on `<MentionsInput>`.
|
|
@@ -167,8 +178,10 @@ type MentionsInputProps = {
|
|
|
167
178
|
type MentionsInputHandle = {
|
|
168
179
|
/** Clear all editor content. */
|
|
169
180
|
clear: () => void;
|
|
170
|
-
/** Replace editor content with a markdown string. */
|
|
181
|
+
/** Replace editor content with a markdown string. Mention tokens are parsed. */
|
|
171
182
|
setContent: (markdown: string) => void;
|
|
183
|
+
/** Append plain text at the end of the document (no mention parsing). */
|
|
184
|
+
appendText: (text: string) => void;
|
|
172
185
|
/** Focus the editor. */
|
|
173
186
|
focus: () => void;
|
|
174
187
|
/** Read the current structured output without waiting for onChange. */
|
|
@@ -184,6 +197,12 @@ type MentionsInputHandle = {
|
|
|
184
197
|
*/
|
|
185
198
|
declare const MentionsInput: React.ForwardRefExoticComponent<MentionsInputProps & React.RefAttributes<MentionsInputHandle>>;
|
|
186
199
|
|
|
200
|
+
declare function useMentionsContent(): {
|
|
201
|
+
output: MentionsOutput | null;
|
|
202
|
+
onChange: (o: MentionsOutput) => void;
|
|
203
|
+
hasContent: boolean;
|
|
204
|
+
};
|
|
205
|
+
|
|
187
206
|
/**
|
|
188
207
|
* Serialize a Tiptap JSON document to a markdown string.
|
|
189
208
|
*
|
|
@@ -217,4 +236,4 @@ declare function extractFromMarkdown(markdown: string): {
|
|
|
217
236
|
plainText: string;
|
|
218
237
|
};
|
|
219
238
|
|
|
220
|
-
export { type MentionItem, type MentionProvider, type MentionToken, MentionsInput, type MentionsInputHandle, type MentionsInputProps, type MentionsOutput, extractFromMarkdown, parseFromMarkdown, serializeToMarkdown };
|
|
239
|
+
export { type MentionItem, type MentionProvider, type MentionToken, MentionsInput, type MentionsInputHandle, type MentionsInputProps, type MentionsOutput, extractFromMarkdown, parseFromMarkdown, serializeToMarkdown, useMentionsContent };
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
MentionsInput: () => MentionsInput,
|
|
34
34
|
extractFromMarkdown: () => extractFromMarkdown,
|
|
35
35
|
parseFromMarkdown: () => parseFromMarkdown,
|
|
36
|
-
serializeToMarkdown: () => serializeToMarkdown
|
|
36
|
+
serializeToMarkdown: () => serializeToMarkdown,
|
|
37
|
+
useMentionsContent: () => useMentionsContent
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(index_exports);
|
|
39
40
|
|
|
@@ -222,7 +223,7 @@ function detectTrigger(text, cursorPos, docStartPos, triggers) {
|
|
|
222
223
|
return null;
|
|
223
224
|
}
|
|
224
225
|
var suggestionPluginKey = new import_state.PluginKey("mentionSuggestion");
|
|
225
|
-
function createSuggestionExtension(triggers, callbacksRef, allowTriggerRef) {
|
|
226
|
+
function createSuggestionExtension(triggers, callbacksRef, allowTriggerRef, streamingRef) {
|
|
226
227
|
return import_core2.Extension.create({
|
|
227
228
|
name: "mentionSuggestion",
|
|
228
229
|
priority: 200,
|
|
@@ -273,6 +274,15 @@ function createSuggestionExtension(triggers, callbacksRef, allowTriggerRef) {
|
|
|
273
274
|
view() {
|
|
274
275
|
return {
|
|
275
276
|
update(view, _prevState) {
|
|
277
|
+
if (streamingRef?.current) {
|
|
278
|
+
if (active) {
|
|
279
|
+
active = false;
|
|
280
|
+
lastQuery = null;
|
|
281
|
+
lastTrigger = null;
|
|
282
|
+
callbacksRef.current.onExit();
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
276
286
|
const { state } = view;
|
|
277
287
|
const { selection } = state;
|
|
278
288
|
if (!selection.empty) {
|
|
@@ -587,6 +597,34 @@ function createMentionRemoveExtension(onMentionRemoveRef) {
|
|
|
587
597
|
}
|
|
588
598
|
});
|
|
589
599
|
}
|
|
600
|
+
var streamingBlockPluginKey = new import_state2.PluginKey("streamingBlock");
|
|
601
|
+
function createStreamingBlockExtension(streamingRef) {
|
|
602
|
+
return import_core3.Extension.create({
|
|
603
|
+
name: "streamingBlock",
|
|
604
|
+
priority: 200,
|
|
605
|
+
addProseMirrorPlugins() {
|
|
606
|
+
return [
|
|
607
|
+
new import_state2.Plugin({
|
|
608
|
+
key: streamingBlockPluginKey,
|
|
609
|
+
props: {
|
|
610
|
+
handleKeyDown() {
|
|
611
|
+
return streamingRef.current;
|
|
612
|
+
},
|
|
613
|
+
handleKeyPress() {
|
|
614
|
+
return streamingRef.current;
|
|
615
|
+
},
|
|
616
|
+
handlePaste() {
|
|
617
|
+
return streamingRef.current;
|
|
618
|
+
},
|
|
619
|
+
handleDrop() {
|
|
620
|
+
return streamingRef.current;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
})
|
|
624
|
+
];
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
}
|
|
590
628
|
function useMentionsEditor({
|
|
591
629
|
providers,
|
|
592
630
|
value,
|
|
@@ -604,7 +642,9 @@ function useMentionsEditor({
|
|
|
604
642
|
onMentionClick,
|
|
605
643
|
onMentionHover,
|
|
606
644
|
allowTrigger,
|
|
607
|
-
validateMention
|
|
645
|
+
validateMention,
|
|
646
|
+
streaming = false,
|
|
647
|
+
onStreamingComplete
|
|
608
648
|
}) {
|
|
609
649
|
const onChangeRef = (0, import_react.useRef)(onChange);
|
|
610
650
|
onChangeRef.current = onChange;
|
|
@@ -628,6 +668,13 @@ function useMentionsEditor({
|
|
|
628
668
|
allowTriggerRef.current = allowTrigger;
|
|
629
669
|
const validateMentionRef = (0, import_react.useRef)(validateMention);
|
|
630
670
|
validateMentionRef.current = validateMention;
|
|
671
|
+
const onStreamingCompleteRef = (0, import_react.useRef)(onStreamingComplete);
|
|
672
|
+
onStreamingCompleteRef.current = onStreamingComplete;
|
|
673
|
+
const streamingRef = (0, import_react.useRef)(streaming);
|
|
674
|
+
streamingRef.current = streaming;
|
|
675
|
+
const prevStreamingRef = (0, import_react.useRef)(streaming);
|
|
676
|
+
const throttleTimerRef = (0, import_react.useRef)(null);
|
|
677
|
+
const pendingOutputRef = (0, import_react.useRef)(null);
|
|
631
678
|
const internalMarkdownRef = (0, import_react.useRef)(null);
|
|
632
679
|
const initialContent = (0, import_react.useMemo)(() => {
|
|
633
680
|
if (!value) return void 0;
|
|
@@ -640,7 +687,12 @@ function useMentionsEditor({
|
|
|
640
687
|
[triggersKey]
|
|
641
688
|
);
|
|
642
689
|
const suggestionExtension = (0, import_react.useMemo)(
|
|
643
|
-
() => createSuggestionExtension(
|
|
690
|
+
() => createSuggestionExtension(
|
|
691
|
+
triggers,
|
|
692
|
+
callbacksRef,
|
|
693
|
+
allowTriggerRef,
|
|
694
|
+
streamingRef
|
|
695
|
+
),
|
|
644
696
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
645
697
|
[triggersKey]
|
|
646
698
|
);
|
|
@@ -656,6 +708,10 @@ function useMentionsEditor({
|
|
|
656
708
|
() => createMentionRemoveExtension(onMentionRemoveRef),
|
|
657
709
|
[]
|
|
658
710
|
);
|
|
711
|
+
const streamingBlockExt = (0, import_react.useMemo)(
|
|
712
|
+
() => createStreamingBlockExtension(streamingRef),
|
|
713
|
+
[]
|
|
714
|
+
);
|
|
659
715
|
const mentionNodeExt = (0, import_react.useMemo)(
|
|
660
716
|
() => MentionNode.configure({
|
|
661
717
|
onClickRef: onMentionClickRef,
|
|
@@ -683,7 +739,8 @@ function useMentionsEditor({
|
|
|
683
739
|
suggestionExtension,
|
|
684
740
|
submitExt,
|
|
685
741
|
enterExt,
|
|
686
|
-
mentionRemoveExt
|
|
742
|
+
mentionRemoveExt,
|
|
743
|
+
streamingBlockExt
|
|
687
744
|
],
|
|
688
745
|
content: initialContent,
|
|
689
746
|
autofocus: autoFocus ? "end" : false,
|
|
@@ -696,7 +753,20 @@ function useMentionsEditor({
|
|
|
696
753
|
onUpdate: ({ editor: editor2 }) => {
|
|
697
754
|
const output = buildOutput(editor2);
|
|
698
755
|
internalMarkdownRef.current = output.markdown;
|
|
699
|
-
|
|
756
|
+
if (streamingRef.current) {
|
|
757
|
+
pendingOutputRef.current = output;
|
|
758
|
+
if (!throttleTimerRef.current) {
|
|
759
|
+
throttleTimerRef.current = setTimeout(() => {
|
|
760
|
+
throttleTimerRef.current = null;
|
|
761
|
+
if (pendingOutputRef.current) {
|
|
762
|
+
onChangeRef.current?.(pendingOutputRef.current);
|
|
763
|
+
pendingOutputRef.current = null;
|
|
764
|
+
}
|
|
765
|
+
}, 150);
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
onChangeRef.current?.(output);
|
|
769
|
+
}
|
|
700
770
|
},
|
|
701
771
|
onFocus: () => {
|
|
702
772
|
onFocusRef.current?.();
|
|
@@ -705,6 +775,26 @@ function useMentionsEditor({
|
|
|
705
775
|
onBlurRef.current?.();
|
|
706
776
|
}
|
|
707
777
|
});
|
|
778
|
+
(0, import_react.useEffect)(() => {
|
|
779
|
+
if (prevStreamingRef.current && !streaming && editor) {
|
|
780
|
+
if (throttleTimerRef.current) {
|
|
781
|
+
clearTimeout(throttleTimerRef.current);
|
|
782
|
+
throttleTimerRef.current = null;
|
|
783
|
+
}
|
|
784
|
+
const output = buildOutput(editor);
|
|
785
|
+
onChangeRef.current?.(output);
|
|
786
|
+
onStreamingCompleteRef.current?.(output);
|
|
787
|
+
pendingOutputRef.current = null;
|
|
788
|
+
}
|
|
789
|
+
prevStreamingRef.current = streaming;
|
|
790
|
+
}, [streaming, editor]);
|
|
791
|
+
(0, import_react.useEffect)(() => {
|
|
792
|
+
return () => {
|
|
793
|
+
if (throttleTimerRef.current) {
|
|
794
|
+
clearTimeout(throttleTimerRef.current);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}, []);
|
|
708
798
|
(0, import_react.useEffect)(() => {
|
|
709
799
|
if (editor && editor.isEditable !== editable) {
|
|
710
800
|
editor.setEditable(editable);
|
|
@@ -749,6 +839,20 @@ function useMentionsEditor({
|
|
|
749
839
|
const doc = parseFromMarkdown(markdown);
|
|
750
840
|
editor.commands.setContent(doc);
|
|
751
841
|
internalMarkdownRef.current = markdown;
|
|
842
|
+
if (streamingRef.current) {
|
|
843
|
+
editor.commands.focus("end");
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
[editor]
|
|
847
|
+
);
|
|
848
|
+
const appendText = (0, import_react.useCallback)(
|
|
849
|
+
(text) => {
|
|
850
|
+
if (!editor) return;
|
|
851
|
+
const endPos = editor.state.doc.content.size - 1;
|
|
852
|
+
editor.commands.insertContentAt(endPos, text);
|
|
853
|
+
if (streamingRef.current) {
|
|
854
|
+
editor.commands.focus("end");
|
|
855
|
+
}
|
|
752
856
|
},
|
|
753
857
|
[editor]
|
|
754
858
|
);
|
|
@@ -759,7 +863,7 @@ function useMentionsEditor({
|
|
|
759
863
|
if (!editor) return null;
|
|
760
864
|
return buildOutput(editor);
|
|
761
865
|
}, [editor]);
|
|
762
|
-
return { editor, getOutput, clear, setContent, focus };
|
|
866
|
+
return { editor, getOutput, clear, setContent, appendText, focus };
|
|
763
867
|
}
|
|
764
868
|
|
|
765
869
|
// src/hooks/useSuggestion.ts
|
|
@@ -1429,14 +1533,16 @@ var MentionsInput = (0, import_react5.forwardRef)(
|
|
|
1429
1533
|
submitKey = "enter",
|
|
1430
1534
|
allowTrigger,
|
|
1431
1535
|
validateMention,
|
|
1432
|
-
portalContainer
|
|
1536
|
+
portalContainer,
|
|
1537
|
+
streaming,
|
|
1538
|
+
onStreamingComplete
|
|
1433
1539
|
}, ref) {
|
|
1434
1540
|
const instanceId = (0, import_react5.useId)();
|
|
1435
1541
|
const listboxId = `mentions-listbox-${instanceId}`;
|
|
1436
1542
|
const { uiState, actions, callbacksRef } = useSuggestion(providers, {
|
|
1437
1543
|
onMentionAdd
|
|
1438
1544
|
});
|
|
1439
|
-
const { editor, getOutput, clear, setContent, focus } = useMentionsEditor({
|
|
1545
|
+
const { editor, getOutput, clear, setContent, appendText, focus } = useMentionsEditor({
|
|
1440
1546
|
providers,
|
|
1441
1547
|
value,
|
|
1442
1548
|
onChange,
|
|
@@ -1453,12 +1559,14 @@ var MentionsInput = (0, import_react5.forwardRef)(
|
|
|
1453
1559
|
onMentionClick,
|
|
1454
1560
|
onMentionHover,
|
|
1455
1561
|
allowTrigger,
|
|
1456
|
-
validateMention
|
|
1562
|
+
validateMention,
|
|
1563
|
+
streaming,
|
|
1564
|
+
onStreamingComplete
|
|
1457
1565
|
});
|
|
1458
1566
|
(0, import_react5.useImperativeHandle)(
|
|
1459
1567
|
ref,
|
|
1460
|
-
() => ({ clear, setContent, focus, getOutput }),
|
|
1461
|
-
[clear, setContent, focus, getOutput]
|
|
1568
|
+
() => ({ clear, setContent, appendText, focus, getOutput }),
|
|
1569
|
+
[clear, setContent, appendText, focus, getOutput]
|
|
1462
1570
|
);
|
|
1463
1571
|
const isExpanded = uiState.state !== "idle";
|
|
1464
1572
|
const handleHover = (0, import_react5.useCallback)((index) => {
|
|
@@ -1514,11 +1622,24 @@ var MentionsInput = (0, import_react5.forwardRef)(
|
|
|
1514
1622
|
);
|
|
1515
1623
|
}
|
|
1516
1624
|
);
|
|
1625
|
+
|
|
1626
|
+
// src/hooks/useMentionsContent.ts
|
|
1627
|
+
var import_react7 = require("react");
|
|
1628
|
+
function useMentionsContent() {
|
|
1629
|
+
const [output, setOutput] = (0, import_react7.useState)(null);
|
|
1630
|
+
const onChange = (0, import_react7.useCallback)((o) => setOutput(o), []);
|
|
1631
|
+
return {
|
|
1632
|
+
output,
|
|
1633
|
+
onChange,
|
|
1634
|
+
hasContent: (output?.plainText ?? "").trim().length > 0
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1517
1637
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1518
1638
|
0 && (module.exports = {
|
|
1519
1639
|
MentionsInput,
|
|
1520
1640
|
extractFromMarkdown,
|
|
1521
1641
|
parseFromMarkdown,
|
|
1522
|
-
serializeToMarkdown
|
|
1642
|
+
serializeToMarkdown,
|
|
1643
|
+
useMentionsContent
|
|
1523
1644
|
});
|
|
1524
1645
|
//# sourceMappingURL=index.js.map
|