@relevaince/mentions 0.3.3 → 0.6.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 +160 -73
- package/dist/index.d.mts +55 -9
- package/dist/index.d.ts +55 -9
- package/dist/index.js +644 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +650 -92
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
39
39
|
|
|
40
40
|
// src/components/MentionsInput.tsx
|
|
41
41
|
var import_react5 = require("react");
|
|
42
|
+
var import_react_dom = require("react-dom");
|
|
42
43
|
var import_react6 = require("@tiptap/react");
|
|
43
44
|
|
|
44
45
|
// src/hooks/useMentionsEditor.ts
|
|
@@ -64,6 +65,12 @@ var MentionNode = import_core.Node.create({
|
|
|
64
65
|
atom: true,
|
|
65
66
|
selectable: true,
|
|
66
67
|
draggable: false,
|
|
68
|
+
addOptions() {
|
|
69
|
+
return {
|
|
70
|
+
onClickRef: void 0,
|
|
71
|
+
onHoverRef: void 0
|
|
72
|
+
};
|
|
73
|
+
},
|
|
67
74
|
addAttributes() {
|
|
68
75
|
return {
|
|
69
76
|
id: {
|
|
@@ -96,11 +103,17 @@ var MentionNode = import_core.Node.create({
|
|
|
96
103
|
const label = node.attrs.label;
|
|
97
104
|
const prefix = DEFAULT_PREFIXES[entityType] ?? "@";
|
|
98
105
|
const display = `${prefix}${label}`;
|
|
106
|
+
const hasClick = !!this.options.onClickRef?.current;
|
|
107
|
+
const extraAttrs = {};
|
|
108
|
+
if (hasClick) {
|
|
109
|
+
extraAttrs["data-mention-clickable"] = "";
|
|
110
|
+
}
|
|
99
111
|
return [
|
|
100
112
|
"span",
|
|
101
113
|
(0, import_core.mergeAttributes)(HTMLAttributes, {
|
|
102
114
|
"data-mention": "",
|
|
103
|
-
class: "mention-chip"
|
|
115
|
+
class: "mention-chip",
|
|
116
|
+
...extraAttrs
|
|
104
117
|
}),
|
|
105
118
|
display
|
|
106
119
|
];
|
|
@@ -111,6 +124,58 @@ var MentionNode = import_core.Node.create({
|
|
|
111
124
|
const prefix = DEFAULT_PREFIXES[entityType] ?? "@";
|
|
112
125
|
return `${prefix}${label}`;
|
|
113
126
|
},
|
|
127
|
+
addNodeView() {
|
|
128
|
+
const options = this.options;
|
|
129
|
+
return ({ node, HTMLAttributes }) => {
|
|
130
|
+
const entityType = node.attrs.entityType;
|
|
131
|
+
const label = node.attrs.label;
|
|
132
|
+
const id = node.attrs.id;
|
|
133
|
+
const prefix = DEFAULT_PREFIXES[entityType] ?? "@";
|
|
134
|
+
const dom = document.createElement("span");
|
|
135
|
+
Object.entries(
|
|
136
|
+
(0, import_core.mergeAttributes)(HTMLAttributes, {
|
|
137
|
+
"data-mention": "",
|
|
138
|
+
"data-type": entityType,
|
|
139
|
+
"data-id": id,
|
|
140
|
+
class: "mention-chip"
|
|
141
|
+
})
|
|
142
|
+
).forEach(([key, val]) => {
|
|
143
|
+
if (val != null && val !== false) dom.setAttribute(key, String(val));
|
|
144
|
+
});
|
|
145
|
+
dom.textContent = `${prefix}${label}`;
|
|
146
|
+
if (options.onClickRef?.current) {
|
|
147
|
+
dom.setAttribute("data-mention-clickable", "");
|
|
148
|
+
dom.style.cursor = "pointer";
|
|
149
|
+
}
|
|
150
|
+
dom.addEventListener("click", (event) => {
|
|
151
|
+
const handler = options.onClickRef?.current;
|
|
152
|
+
if (handler) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
event.stopPropagation();
|
|
155
|
+
handler({ id, type: entityType, label }, event);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
let tooltip = null;
|
|
159
|
+
dom.addEventListener("mouseenter", () => {
|
|
160
|
+
const hoverFn = options.onHoverRef?.current;
|
|
161
|
+
if (!hoverFn) return;
|
|
162
|
+
const content = hoverFn({ id, type: entityType, label });
|
|
163
|
+
if (!content) return;
|
|
164
|
+
tooltip = document.createElement("div");
|
|
165
|
+
tooltip.setAttribute("data-mention-tooltip", "");
|
|
166
|
+
tooltip.textContent = typeof content === "string" ? content : "";
|
|
167
|
+
dom.style.position = "relative";
|
|
168
|
+
dom.appendChild(tooltip);
|
|
169
|
+
});
|
|
170
|
+
dom.addEventListener("mouseleave", () => {
|
|
171
|
+
if (tooltip && tooltip.parentNode) {
|
|
172
|
+
tooltip.parentNode.removeChild(tooltip);
|
|
173
|
+
tooltip = null;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return { dom };
|
|
177
|
+
};
|
|
178
|
+
},
|
|
114
179
|
addKeyboardShortcuts() {
|
|
115
180
|
return {
|
|
116
181
|
Backspace: () => this.editor.commands.command(({ tr, state }) => {
|
|
@@ -143,7 +208,13 @@ function detectTrigger(text, cursorPos, docStartPos, triggers) {
|
|
|
143
208
|
if (before.substring(i, i + trigger.length) === trigger) {
|
|
144
209
|
if (i === 0 || /\s/.test(before[i - 1])) {
|
|
145
210
|
const query = before.slice(i + trigger.length);
|
|
146
|
-
return {
|
|
211
|
+
return {
|
|
212
|
+
trigger,
|
|
213
|
+
query,
|
|
214
|
+
from: docStartPos + i,
|
|
215
|
+
to: cursorPos,
|
|
216
|
+
textBefore: before.slice(0, i)
|
|
217
|
+
};
|
|
147
218
|
}
|
|
148
219
|
}
|
|
149
220
|
}
|
|
@@ -151,7 +222,7 @@ function detectTrigger(text, cursorPos, docStartPos, triggers) {
|
|
|
151
222
|
return null;
|
|
152
223
|
}
|
|
153
224
|
var suggestionPluginKey = new import_state.PluginKey("mentionSuggestion");
|
|
154
|
-
function createSuggestionExtension(triggers, callbacksRef) {
|
|
225
|
+
function createSuggestionExtension(triggers, callbacksRef, allowTriggerRef, streamingRef) {
|
|
155
226
|
return import_core2.Extension.create({
|
|
156
227
|
name: "mentionSuggestion",
|
|
157
228
|
priority: 200,
|
|
@@ -202,6 +273,15 @@ function createSuggestionExtension(triggers, callbacksRef) {
|
|
|
202
273
|
view() {
|
|
203
274
|
return {
|
|
204
275
|
update(view, _prevState) {
|
|
276
|
+
if (streamingRef?.current) {
|
|
277
|
+
if (active) {
|
|
278
|
+
active = false;
|
|
279
|
+
lastQuery = null;
|
|
280
|
+
lastTrigger = null;
|
|
281
|
+
callbacksRef.current.onExit();
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
205
285
|
const { state } = view;
|
|
206
286
|
const { selection } = state;
|
|
207
287
|
if (!selection.empty) {
|
|
@@ -229,6 +309,20 @@ function createSuggestionExtension(triggers, callbacksRef) {
|
|
|
229
309
|
const cursorPos = $pos.pos;
|
|
230
310
|
const match = detectTrigger(blockText, cursorPos, blockStart, triggers);
|
|
231
311
|
if (match) {
|
|
312
|
+
if (allowTriggerRef?.current) {
|
|
313
|
+
const allowed = allowTriggerRef.current(match.trigger, {
|
|
314
|
+
textBefore: match.textBefore
|
|
315
|
+
});
|
|
316
|
+
if (!allowed) {
|
|
317
|
+
if (active) {
|
|
318
|
+
active = false;
|
|
319
|
+
lastQuery = null;
|
|
320
|
+
lastTrigger = null;
|
|
321
|
+
callbacksRef.current.onExit();
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
232
326
|
const range = { from: match.from, to: match.to };
|
|
233
327
|
const props = {
|
|
234
328
|
query: match.query,
|
|
@@ -398,27 +492,48 @@ function buildOutput(editor) {
|
|
|
398
492
|
plainText: extractPlainText(json)
|
|
399
493
|
};
|
|
400
494
|
}
|
|
401
|
-
function
|
|
495
|
+
function collectMentionTokens(doc) {
|
|
496
|
+
const tokens = [];
|
|
497
|
+
function walk(node) {
|
|
498
|
+
if (node.type === "mention" && node.attrs) {
|
|
499
|
+
tokens.push({
|
|
500
|
+
id: node.attrs.id,
|
|
501
|
+
type: node.attrs.entityType ?? node.attrs.type,
|
|
502
|
+
label: node.attrs.label
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
if (node.content) {
|
|
506
|
+
for (const child of node.content) walk(child);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
walk(doc);
|
|
510
|
+
return tokens;
|
|
511
|
+
}
|
|
512
|
+
function createSubmitExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef) {
|
|
402
513
|
return import_core3.Extension.create({
|
|
403
514
|
name: "submitShortcut",
|
|
404
515
|
priority: 150,
|
|
405
516
|
addKeyboardShortcuts() {
|
|
406
517
|
return {
|
|
407
518
|
"Mod-Enter": () => {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
this.editor
|
|
519
|
+
const key = submitKeyRef.current;
|
|
520
|
+
if (key === "mod+enter" || key === "enter") {
|
|
521
|
+
if (onSubmitRef.current) {
|
|
522
|
+
onSubmitRef.current(buildOutput(this.editor));
|
|
523
|
+
if (clearOnSubmitRef.current) {
|
|
524
|
+
this.editor.commands.clearContent(true);
|
|
525
|
+
}
|
|
412
526
|
}
|
|
527
|
+
return true;
|
|
413
528
|
}
|
|
414
|
-
return
|
|
529
|
+
return false;
|
|
415
530
|
}
|
|
416
531
|
};
|
|
417
532
|
}
|
|
418
533
|
});
|
|
419
534
|
}
|
|
420
535
|
var enterSubmitPluginKey = new import_state2.PluginKey("enterSubmit");
|
|
421
|
-
function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
536
|
+
function createEnterExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef) {
|
|
422
537
|
return import_core3.Extension.create({
|
|
423
538
|
name: "enterSubmit",
|
|
424
539
|
priority: 150,
|
|
@@ -430,6 +545,11 @@ function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
430
545
|
props: {
|
|
431
546
|
handleKeyDown(_view, event) {
|
|
432
547
|
if (event.key !== "Enter") return false;
|
|
548
|
+
const key = submitKeyRef.current;
|
|
549
|
+
if (key === "none") return false;
|
|
550
|
+
if (key === "mod+enter") {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
433
553
|
if (event.shiftKey) {
|
|
434
554
|
editor.commands.splitBlock();
|
|
435
555
|
return true;
|
|
@@ -449,6 +569,61 @@ function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
449
569
|
}
|
|
450
570
|
});
|
|
451
571
|
}
|
|
572
|
+
var mentionRemovePluginKey = new import_state2.PluginKey("mentionRemove");
|
|
573
|
+
function createMentionRemoveExtension(onMentionRemoveRef) {
|
|
574
|
+
return import_core3.Extension.create({
|
|
575
|
+
name: "mentionRemoveDetector",
|
|
576
|
+
priority: 100,
|
|
577
|
+
addProseMirrorPlugins() {
|
|
578
|
+
return [
|
|
579
|
+
new import_state2.Plugin({
|
|
580
|
+
key: mentionRemovePluginKey,
|
|
581
|
+
appendTransaction(transactions, oldState, newState) {
|
|
582
|
+
if (!onMentionRemoveRef.current) return null;
|
|
583
|
+
const oldMentions = collectMentionTokens(oldState.doc.toJSON());
|
|
584
|
+
const newMentions = collectMentionTokens(newState.doc.toJSON());
|
|
585
|
+
if (oldMentions.length <= newMentions.length) return null;
|
|
586
|
+
const newIds = new Set(newMentions.map((m) => m.id));
|
|
587
|
+
for (const m of oldMentions) {
|
|
588
|
+
if (!newIds.has(m.id)) {
|
|
589
|
+
onMentionRemoveRef.current(m);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
})
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
var streamingBlockPluginKey = new import_state2.PluginKey("streamingBlock");
|
|
600
|
+
function createStreamingBlockExtension(streamingRef) {
|
|
601
|
+
return import_core3.Extension.create({
|
|
602
|
+
name: "streamingBlock",
|
|
603
|
+
priority: 200,
|
|
604
|
+
addProseMirrorPlugins() {
|
|
605
|
+
return [
|
|
606
|
+
new import_state2.Plugin({
|
|
607
|
+
key: streamingBlockPluginKey,
|
|
608
|
+
props: {
|
|
609
|
+
handleKeyDown() {
|
|
610
|
+
return streamingRef.current;
|
|
611
|
+
},
|
|
612
|
+
handleKeyPress() {
|
|
613
|
+
return streamingRef.current;
|
|
614
|
+
},
|
|
615
|
+
handlePaste() {
|
|
616
|
+
return streamingRef.current;
|
|
617
|
+
},
|
|
618
|
+
handleDrop() {
|
|
619
|
+
return streamingRef.current;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
})
|
|
623
|
+
];
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
452
627
|
function useMentionsEditor({
|
|
453
628
|
providers,
|
|
454
629
|
value,
|
|
@@ -458,7 +633,17 @@ function useMentionsEditor({
|
|
|
458
633
|
placeholder,
|
|
459
634
|
autoFocus = false,
|
|
460
635
|
editable = true,
|
|
461
|
-
callbacksRef
|
|
636
|
+
callbacksRef,
|
|
637
|
+
onFocus,
|
|
638
|
+
onBlur,
|
|
639
|
+
submitKey = "enter",
|
|
640
|
+
onMentionRemove,
|
|
641
|
+
onMentionClick,
|
|
642
|
+
onMentionHover,
|
|
643
|
+
allowTrigger,
|
|
644
|
+
validateMention,
|
|
645
|
+
streaming = false,
|
|
646
|
+
onStreamingComplete
|
|
462
647
|
}) {
|
|
463
648
|
const onChangeRef = (0, import_react.useRef)(onChange);
|
|
464
649
|
onChangeRef.current = onChange;
|
|
@@ -466,6 +651,30 @@ function useMentionsEditor({
|
|
|
466
651
|
onSubmitRef.current = onSubmit;
|
|
467
652
|
const clearOnSubmitRef = (0, import_react.useRef)(clearOnSubmit);
|
|
468
653
|
clearOnSubmitRef.current = clearOnSubmit;
|
|
654
|
+
const onFocusRef = (0, import_react.useRef)(onFocus);
|
|
655
|
+
onFocusRef.current = onFocus;
|
|
656
|
+
const onBlurRef = (0, import_react.useRef)(onBlur);
|
|
657
|
+
onBlurRef.current = onBlur;
|
|
658
|
+
const submitKeyRef = (0, import_react.useRef)(submitKey);
|
|
659
|
+
submitKeyRef.current = submitKey;
|
|
660
|
+
const onMentionRemoveRef = (0, import_react.useRef)(onMentionRemove);
|
|
661
|
+
onMentionRemoveRef.current = onMentionRemove;
|
|
662
|
+
const onMentionClickRef = (0, import_react.useRef)(onMentionClick);
|
|
663
|
+
onMentionClickRef.current = onMentionClick;
|
|
664
|
+
const onMentionHoverRef = (0, import_react.useRef)(onMentionHover);
|
|
665
|
+
onMentionHoverRef.current = onMentionHover;
|
|
666
|
+
const allowTriggerRef = (0, import_react.useRef)(allowTrigger);
|
|
667
|
+
allowTriggerRef.current = allowTrigger;
|
|
668
|
+
const validateMentionRef = (0, import_react.useRef)(validateMention);
|
|
669
|
+
validateMentionRef.current = validateMention;
|
|
670
|
+
const onStreamingCompleteRef = (0, import_react.useRef)(onStreamingComplete);
|
|
671
|
+
onStreamingCompleteRef.current = onStreamingComplete;
|
|
672
|
+
const streamingRef = (0, import_react.useRef)(streaming);
|
|
673
|
+
streamingRef.current = streaming;
|
|
674
|
+
const prevStreamingRef = (0, import_react.useRef)(streaming);
|
|
675
|
+
const throttleTimerRef = (0, import_react.useRef)(null);
|
|
676
|
+
const pendingOutputRef = (0, import_react.useRef)(null);
|
|
677
|
+
const internalMarkdownRef = (0, import_react.useRef)(null);
|
|
469
678
|
const initialContent = (0, import_react.useMemo)(() => {
|
|
470
679
|
if (!value) return void 0;
|
|
471
680
|
return parseFromMarkdown(value);
|
|
@@ -477,16 +686,36 @@ function useMentionsEditor({
|
|
|
477
686
|
[triggersKey]
|
|
478
687
|
);
|
|
479
688
|
const suggestionExtension = (0, import_react.useMemo)(
|
|
480
|
-
() => createSuggestionExtension(
|
|
689
|
+
() => createSuggestionExtension(
|
|
690
|
+
triggers,
|
|
691
|
+
callbacksRef,
|
|
692
|
+
allowTriggerRef,
|
|
693
|
+
streamingRef
|
|
694
|
+
),
|
|
481
695
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
482
696
|
[triggersKey]
|
|
483
697
|
);
|
|
484
698
|
const submitExt = (0, import_react.useMemo)(
|
|
485
|
-
() => createSubmitExtension(onSubmitRef, clearOnSubmitRef),
|
|
699
|
+
() => createSubmitExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef),
|
|
486
700
|
[]
|
|
487
701
|
);
|
|
488
702
|
const enterExt = (0, import_react.useMemo)(
|
|
489
|
-
() => createEnterExtension(onSubmitRef, clearOnSubmitRef),
|
|
703
|
+
() => createEnterExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef),
|
|
704
|
+
[]
|
|
705
|
+
);
|
|
706
|
+
const mentionRemoveExt = (0, import_react.useMemo)(
|
|
707
|
+
() => createMentionRemoveExtension(onMentionRemoveRef),
|
|
708
|
+
[]
|
|
709
|
+
);
|
|
710
|
+
const streamingBlockExt = (0, import_react.useMemo)(
|
|
711
|
+
() => createStreamingBlockExtension(streamingRef),
|
|
712
|
+
[]
|
|
713
|
+
);
|
|
714
|
+
const mentionNodeExt = (0, import_react.useMemo)(
|
|
715
|
+
() => MentionNode.configure({
|
|
716
|
+
onClickRef: onMentionClickRef,
|
|
717
|
+
onHoverRef: onMentionHoverRef
|
|
718
|
+
}),
|
|
490
719
|
[]
|
|
491
720
|
);
|
|
492
721
|
const editor = (0, import_react2.useEditor)({
|
|
@@ -505,10 +734,12 @@ function useMentionsEditor({
|
|
|
505
734
|
placeholder: ({ editor: editor2 }) => editor2.isEmpty ? placeholder ?? "Type a message..." : "",
|
|
506
735
|
showOnlyCurrent: true
|
|
507
736
|
}),
|
|
508
|
-
|
|
737
|
+
mentionNodeExt,
|
|
509
738
|
suggestionExtension,
|
|
510
739
|
submitExt,
|
|
511
|
-
enterExt
|
|
740
|
+
enterExt,
|
|
741
|
+
mentionRemoveExt,
|
|
742
|
+
streamingBlockExt
|
|
512
743
|
],
|
|
513
744
|
content: initialContent,
|
|
514
745
|
autofocus: autoFocus ? "end" : false,
|
|
@@ -519,14 +750,85 @@ function useMentionsEditor({
|
|
|
519
750
|
}
|
|
520
751
|
},
|
|
521
752
|
onUpdate: ({ editor: editor2 }) => {
|
|
522
|
-
|
|
753
|
+
const output = buildOutput(editor2);
|
|
754
|
+
internalMarkdownRef.current = output.markdown;
|
|
755
|
+
if (streamingRef.current) {
|
|
756
|
+
pendingOutputRef.current = output;
|
|
757
|
+
if (!throttleTimerRef.current) {
|
|
758
|
+
throttleTimerRef.current = setTimeout(() => {
|
|
759
|
+
throttleTimerRef.current = null;
|
|
760
|
+
if (pendingOutputRef.current) {
|
|
761
|
+
onChangeRef.current?.(pendingOutputRef.current);
|
|
762
|
+
pendingOutputRef.current = null;
|
|
763
|
+
}
|
|
764
|
+
}, 150);
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
onChangeRef.current?.(output);
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
onFocus: () => {
|
|
771
|
+
onFocusRef.current?.();
|
|
772
|
+
},
|
|
773
|
+
onBlur: () => {
|
|
774
|
+
onBlurRef.current?.();
|
|
523
775
|
}
|
|
524
776
|
});
|
|
777
|
+
(0, import_react.useEffect)(() => {
|
|
778
|
+
if (prevStreamingRef.current && !streaming && editor) {
|
|
779
|
+
if (throttleTimerRef.current) {
|
|
780
|
+
clearTimeout(throttleTimerRef.current);
|
|
781
|
+
throttleTimerRef.current = null;
|
|
782
|
+
}
|
|
783
|
+
const output = buildOutput(editor);
|
|
784
|
+
onChangeRef.current?.(output);
|
|
785
|
+
onStreamingCompleteRef.current?.(output);
|
|
786
|
+
pendingOutputRef.current = null;
|
|
787
|
+
}
|
|
788
|
+
prevStreamingRef.current = streaming;
|
|
789
|
+
}, [streaming, editor]);
|
|
790
|
+
(0, import_react.useEffect)(() => {
|
|
791
|
+
return () => {
|
|
792
|
+
if (throttleTimerRef.current) {
|
|
793
|
+
clearTimeout(throttleTimerRef.current);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}, []);
|
|
525
797
|
(0, import_react.useEffect)(() => {
|
|
526
798
|
if (editor && editor.isEditable !== editable) {
|
|
527
799
|
editor.setEditable(editable);
|
|
528
800
|
}
|
|
529
801
|
}, [editor, editable]);
|
|
802
|
+
(0, import_react.useEffect)(() => {
|
|
803
|
+
if (!editor || value === void 0) return;
|
|
804
|
+
if (value === internalMarkdownRef.current) return;
|
|
805
|
+
const doc = parseFromMarkdown(value);
|
|
806
|
+
editor.commands.setContent(doc);
|
|
807
|
+
internalMarkdownRef.current = value;
|
|
808
|
+
}, [editor, value]);
|
|
809
|
+
(0, import_react.useEffect)(() => {
|
|
810
|
+
if (!editor || !validateMention) return;
|
|
811
|
+
const runValidation = async () => {
|
|
812
|
+
const doc = editor.getJSON();
|
|
813
|
+
const tokens = collectMentionTokens(doc);
|
|
814
|
+
const invalidIds = /* @__PURE__ */ new Set();
|
|
815
|
+
await Promise.all(
|
|
816
|
+
tokens.map(async (token) => {
|
|
817
|
+
const valid = await validateMention(token);
|
|
818
|
+
if (!valid) invalidIds.add(token.id);
|
|
819
|
+
})
|
|
820
|
+
);
|
|
821
|
+
editor.view.dom.querySelectorAll("[data-mention]").forEach((el) => {
|
|
822
|
+
const id = el.getAttribute("data-id");
|
|
823
|
+
if (id && invalidIds.has(id)) {
|
|
824
|
+
el.setAttribute("data-mention-invalid", "");
|
|
825
|
+
} else {
|
|
826
|
+
el.removeAttribute("data-mention-invalid");
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
};
|
|
830
|
+
runValidation();
|
|
831
|
+
}, [editor, validateMention]);
|
|
530
832
|
const clear = (0, import_react.useCallback)(() => {
|
|
531
833
|
editor?.commands.clearContent(true);
|
|
532
834
|
}, [editor]);
|
|
@@ -535,6 +837,21 @@ function useMentionsEditor({
|
|
|
535
837
|
if (!editor) return;
|
|
536
838
|
const doc = parseFromMarkdown(markdown);
|
|
537
839
|
editor.commands.setContent(doc);
|
|
840
|
+
internalMarkdownRef.current = markdown;
|
|
841
|
+
if (streamingRef.current) {
|
|
842
|
+
editor.commands.focus("end");
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
[editor]
|
|
846
|
+
);
|
|
847
|
+
const appendText = (0, import_react.useCallback)(
|
|
848
|
+
(text) => {
|
|
849
|
+
if (!editor) return;
|
|
850
|
+
const endPos = editor.state.doc.content.size - 1;
|
|
851
|
+
editor.commands.insertContentAt(endPos, text);
|
|
852
|
+
if (streamingRef.current) {
|
|
853
|
+
editor.commands.focus("end");
|
|
854
|
+
}
|
|
538
855
|
},
|
|
539
856
|
[editor]
|
|
540
857
|
);
|
|
@@ -545,11 +862,32 @@ function useMentionsEditor({
|
|
|
545
862
|
if (!editor) return null;
|
|
546
863
|
return buildOutput(editor);
|
|
547
864
|
}, [editor]);
|
|
548
|
-
return { editor, getOutput, clear, setContent, focus };
|
|
865
|
+
return { editor, getOutput, clear, setContent, appendText, focus };
|
|
549
866
|
}
|
|
550
867
|
|
|
551
868
|
// src/hooks/useSuggestion.ts
|
|
552
869
|
var import_react3 = require("react");
|
|
870
|
+
|
|
871
|
+
// src/utils/debounce.ts
|
|
872
|
+
function debounce(fn, ms) {
|
|
873
|
+
let timer = null;
|
|
874
|
+
const debounced = ((...args) => {
|
|
875
|
+
if (timer != null) clearTimeout(timer);
|
|
876
|
+
timer = setTimeout(() => {
|
|
877
|
+
timer = null;
|
|
878
|
+
fn(...args);
|
|
879
|
+
}, ms);
|
|
880
|
+
});
|
|
881
|
+
debounced.cancel = () => {
|
|
882
|
+
if (timer != null) {
|
|
883
|
+
clearTimeout(timer);
|
|
884
|
+
timer = null;
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
return debounced;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/hooks/useSuggestion.ts
|
|
553
891
|
var IDLE_STATE = {
|
|
554
892
|
state: "idle",
|
|
555
893
|
items: [],
|
|
@@ -560,16 +898,24 @@ var IDLE_STATE = {
|
|
|
560
898
|
trigger: null,
|
|
561
899
|
query: ""
|
|
562
900
|
};
|
|
563
|
-
function useSuggestion(providers) {
|
|
901
|
+
function useSuggestion(providers, options = {}) {
|
|
564
902
|
const [uiState, setUIState] = (0, import_react3.useState)(IDLE_STATE);
|
|
565
903
|
const stateRef = (0, import_react3.useRef)(uiState);
|
|
566
904
|
stateRef.current = uiState;
|
|
567
905
|
const providersRef = (0, import_react3.useRef)(providers);
|
|
568
906
|
providersRef.current = providers;
|
|
907
|
+
const onMentionAddRef = (0, import_react3.useRef)(options.onMentionAdd);
|
|
908
|
+
onMentionAddRef.current = options.onMentionAdd;
|
|
569
909
|
const commandRef = (0, import_react3.useRef)(
|
|
570
910
|
null
|
|
571
911
|
);
|
|
572
912
|
const providerRef = (0, import_react3.useRef)(null);
|
|
913
|
+
const debouncedFetchRef = (0, import_react3.useRef)(null);
|
|
914
|
+
(0, import_react3.useEffect)(() => {
|
|
915
|
+
return () => {
|
|
916
|
+
debouncedFetchRef.current?.cancel();
|
|
917
|
+
};
|
|
918
|
+
}, []);
|
|
573
919
|
const fetchItems = (0, import_react3.useCallback)(
|
|
574
920
|
async (provider, query, parent, useSearchAll) => {
|
|
575
921
|
setUIState((prev) => ({ ...prev, loading: true, state: "loading" }));
|
|
@@ -600,6 +946,23 @@ function useSuggestion(providers) {
|
|
|
600
946
|
},
|
|
601
947
|
[]
|
|
602
948
|
);
|
|
949
|
+
const scheduleFetch = (0, import_react3.useCallback)(
|
|
950
|
+
(provider, query, parent, useSearchAll) => {
|
|
951
|
+
debouncedFetchRef.current?.cancel();
|
|
952
|
+
const ms = provider.debounceMs;
|
|
953
|
+
if (ms && ms > 0) {
|
|
954
|
+
setUIState((prev) => ({ ...prev, loading: true }));
|
|
955
|
+
const debouncedFn = debounce(() => {
|
|
956
|
+
fetchItems(provider, query, parent, useSearchAll);
|
|
957
|
+
}, ms);
|
|
958
|
+
debouncedFetchRef.current = debouncedFn;
|
|
959
|
+
debouncedFn();
|
|
960
|
+
} else {
|
|
961
|
+
fetchItems(provider, query, parent, useSearchAll);
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
[fetchItems]
|
|
965
|
+
);
|
|
603
966
|
const onStart = (0, import_react3.useCallback)(
|
|
604
967
|
(props) => {
|
|
605
968
|
const provider = providersRef.current.find(
|
|
@@ -618,13 +981,31 @@ function useSuggestion(providers) {
|
|
|
618
981
|
trigger: props.trigger,
|
|
619
982
|
query: props.query
|
|
620
983
|
});
|
|
984
|
+
if (!props.query.trim() && provider.getRecentItems) {
|
|
985
|
+
provider.getRecentItems().then((recentItems) => {
|
|
986
|
+
const tagged = recentItems.map((item) => ({
|
|
987
|
+
...item,
|
|
988
|
+
group: item.group ?? "Recent"
|
|
989
|
+
}));
|
|
990
|
+
setUIState((prev) => ({
|
|
991
|
+
...prev,
|
|
992
|
+
items: tagged,
|
|
993
|
+
loading: false,
|
|
994
|
+
state: "showing",
|
|
995
|
+
activeIndex: 0
|
|
996
|
+
}));
|
|
997
|
+
}).catch(() => {
|
|
998
|
+
scheduleFetch(provider, props.query);
|
|
999
|
+
});
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
621
1002
|
if (props.query.trim() && provider.searchAll) {
|
|
622
|
-
|
|
1003
|
+
scheduleFetch(provider, props.query, void 0, true);
|
|
623
1004
|
} else {
|
|
624
|
-
|
|
1005
|
+
scheduleFetch(provider, props.query);
|
|
625
1006
|
}
|
|
626
1007
|
},
|
|
627
|
-
[
|
|
1008
|
+
[scheduleFetch]
|
|
628
1009
|
);
|
|
629
1010
|
const onUpdate = (0, import_react3.useCallback)(
|
|
630
1011
|
(props) => {
|
|
@@ -648,14 +1029,31 @@ function useSuggestion(providers) {
|
|
|
648
1029
|
}));
|
|
649
1030
|
}
|
|
650
1031
|
if (props.query.trim() && provider.searchAll) {
|
|
651
|
-
|
|
1032
|
+
scheduleFetch(provider, props.query, void 0, true);
|
|
1033
|
+
} else if (!props.query.trim() && provider.getRecentItems) {
|
|
1034
|
+
provider.getRecentItems().then((recentItems) => {
|
|
1035
|
+
const tagged = recentItems.map((item) => ({
|
|
1036
|
+
...item,
|
|
1037
|
+
group: item.group ?? "Recent"
|
|
1038
|
+
}));
|
|
1039
|
+
setUIState((prev) => ({
|
|
1040
|
+
...prev,
|
|
1041
|
+
items: tagged,
|
|
1042
|
+
loading: false,
|
|
1043
|
+
state: "showing",
|
|
1044
|
+
activeIndex: 0
|
|
1045
|
+
}));
|
|
1046
|
+
}).catch(() => {
|
|
1047
|
+
scheduleFetch(provider, props.query);
|
|
1048
|
+
});
|
|
652
1049
|
} else {
|
|
653
|
-
|
|
1050
|
+
scheduleFetch(provider, props.query);
|
|
654
1051
|
}
|
|
655
1052
|
},
|
|
656
|
-
[
|
|
1053
|
+
[scheduleFetch]
|
|
657
1054
|
);
|
|
658
1055
|
const onExit = (0, import_react3.useCallback)(() => {
|
|
1056
|
+
debouncedFetchRef.current?.cancel();
|
|
659
1057
|
providerRef.current = null;
|
|
660
1058
|
commandRef.current = null;
|
|
661
1059
|
setUIState(IDLE_STATE);
|
|
@@ -690,8 +1088,8 @@ function useSuggestion(providers) {
|
|
|
690
1088
|
fetchItems(provider, "", selected);
|
|
691
1089
|
return;
|
|
692
1090
|
}
|
|
1091
|
+
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
693
1092
|
if (commandRef.current) {
|
|
694
|
-
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
695
1093
|
commandRef.current({
|
|
696
1094
|
id: selected.id,
|
|
697
1095
|
label: selected.label,
|
|
@@ -699,6 +1097,11 @@ function useSuggestion(providers) {
|
|
|
699
1097
|
rootLabel
|
|
700
1098
|
});
|
|
701
1099
|
}
|
|
1100
|
+
onMentionAddRef.current?.({
|
|
1101
|
+
id: selected.id,
|
|
1102
|
+
type: selected.type,
|
|
1103
|
+
label: selected.label
|
|
1104
|
+
});
|
|
702
1105
|
},
|
|
703
1106
|
[fetchItems]
|
|
704
1107
|
);
|
|
@@ -723,6 +1126,7 @@ function useSuggestion(providers) {
|
|
|
723
1126
|
});
|
|
724
1127
|
}, [fetchItems]);
|
|
725
1128
|
const close = (0, import_react3.useCallback)(() => {
|
|
1129
|
+
debouncedFetchRef.current?.cancel();
|
|
726
1130
|
setUIState(IDLE_STATE);
|
|
727
1131
|
}, []);
|
|
728
1132
|
const searchNested = (0, import_react3.useCallback)(
|
|
@@ -732,10 +1136,10 @@ function useSuggestion(providers) {
|
|
|
732
1136
|
const current = stateRef.current;
|
|
733
1137
|
const parent = current.breadcrumbs[current.breadcrumbs.length - 1];
|
|
734
1138
|
if (parent) {
|
|
735
|
-
|
|
1139
|
+
scheduleFetch(provider, query, parent);
|
|
736
1140
|
}
|
|
737
1141
|
},
|
|
738
|
-
[
|
|
1142
|
+
[scheduleFetch]
|
|
739
1143
|
);
|
|
740
1144
|
const onKeyDown = (0, import_react3.useCallback)(
|
|
741
1145
|
({ event }) => {
|
|
@@ -758,6 +1162,14 @@ function useSuggestion(providers) {
|
|
|
758
1162
|
}
|
|
759
1163
|
return true;
|
|
760
1164
|
}
|
|
1165
|
+
case "Tab": {
|
|
1166
|
+
event.preventDefault();
|
|
1167
|
+
const selectedItem = current.items[current.activeIndex];
|
|
1168
|
+
if (selectedItem) {
|
|
1169
|
+
select(selectedItem);
|
|
1170
|
+
}
|
|
1171
|
+
return true;
|
|
1172
|
+
}
|
|
761
1173
|
case "ArrowRight": {
|
|
762
1174
|
const activeItem = current.items[current.activeIndex];
|
|
763
1175
|
if (activeItem?.hasChildren) {
|
|
@@ -832,13 +1244,13 @@ function optionAttrs(id, selected, index) {
|
|
|
832
1244
|
|
|
833
1245
|
// src/components/SuggestionList.tsx
|
|
834
1246
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
835
|
-
var LISTBOX_ID = "mentions-suggestion-listbox";
|
|
836
1247
|
function SuggestionList({
|
|
837
1248
|
items,
|
|
838
1249
|
activeIndex,
|
|
839
1250
|
breadcrumbs,
|
|
840
1251
|
loading,
|
|
841
1252
|
trigger,
|
|
1253
|
+
query,
|
|
842
1254
|
clientRect,
|
|
843
1255
|
onSelect,
|
|
844
1256
|
onHover,
|
|
@@ -847,7 +1259,12 @@ function SuggestionList({
|
|
|
847
1259
|
onNavigateUp,
|
|
848
1260
|
onNavigateDown,
|
|
849
1261
|
onClose,
|
|
850
|
-
|
|
1262
|
+
onFocusEditor,
|
|
1263
|
+
renderItem,
|
|
1264
|
+
renderEmpty,
|
|
1265
|
+
renderLoading,
|
|
1266
|
+
renderGroupHeader,
|
|
1267
|
+
listboxId
|
|
851
1268
|
}) {
|
|
852
1269
|
const listRef = (0, import_react4.useRef)(null);
|
|
853
1270
|
const searchInputRef = (0, import_react4.useRef)(null);
|
|
@@ -861,18 +1278,24 @@ function SuggestionList({
|
|
|
861
1278
|
prevBreadcrumbKey.current = breadcrumbKey;
|
|
862
1279
|
}
|
|
863
1280
|
}, [breadcrumbKey]);
|
|
1281
|
+
const prevBreadcrumbsLen = (0, import_react4.useRef)(breadcrumbs.length);
|
|
864
1282
|
(0, import_react4.useEffect)(() => {
|
|
865
|
-
if (
|
|
1283
|
+
if (prevBreadcrumbsLen.current > 0 && breadcrumbs.length === 0) {
|
|
1284
|
+
onFocusEditor?.();
|
|
1285
|
+
} else if (breadcrumbs.length > 0 && searchInputRef.current) {
|
|
866
1286
|
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
867
1287
|
}
|
|
868
|
-
|
|
1288
|
+
prevBreadcrumbsLen.current = breadcrumbs.length;
|
|
1289
|
+
}, [breadcrumbKey, breadcrumbs.length, onFocusEditor]);
|
|
869
1290
|
(0, import_react4.useEffect)(() => {
|
|
870
1291
|
if (!listRef.current) return;
|
|
871
1292
|
const active = listRef.current.querySelector('[aria-selected="true"]');
|
|
872
1293
|
active?.scrollIntoView({ block: "nearest" });
|
|
873
1294
|
}, [activeIndex]);
|
|
874
|
-
const style = usePopoverPosition(clientRect);
|
|
875
|
-
|
|
1295
|
+
const { style, position } = usePopoverPosition(clientRect);
|
|
1296
|
+
const activeQuery = breadcrumbs.length > 0 ? nestedQuery : query;
|
|
1297
|
+
const showEmpty = !loading && items.length === 0 && activeQuery.trim().length > 0;
|
|
1298
|
+
if (items.length === 0 && !loading && !showEmpty) return null;
|
|
876
1299
|
const handleSearchKeyDown = (e) => {
|
|
877
1300
|
switch (e.key) {
|
|
878
1301
|
case "ArrowDown":
|
|
@@ -892,8 +1315,23 @@ function SuggestionList({
|
|
|
892
1315
|
}
|
|
893
1316
|
case "Escape":
|
|
894
1317
|
e.preventDefault();
|
|
1318
|
+
onFocusEditor?.();
|
|
895
1319
|
onClose?.();
|
|
896
1320
|
break;
|
|
1321
|
+
case "ArrowLeft":
|
|
1322
|
+
if (nestedQuery === "" || e.currentTarget.selectionStart === 0) {
|
|
1323
|
+
e.preventDefault();
|
|
1324
|
+
onGoBack();
|
|
1325
|
+
}
|
|
1326
|
+
break;
|
|
1327
|
+
case "ArrowRight": {
|
|
1328
|
+
const item = items[activeIndex];
|
|
1329
|
+
if (item?.hasChildren) {
|
|
1330
|
+
e.preventDefault();
|
|
1331
|
+
onSelect(item);
|
|
1332
|
+
}
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
897
1335
|
case "Backspace":
|
|
898
1336
|
if (nestedQuery === "") {
|
|
899
1337
|
e.preventDefault();
|
|
@@ -902,11 +1340,13 @@ function SuggestionList({
|
|
|
902
1340
|
break;
|
|
903
1341
|
}
|
|
904
1342
|
};
|
|
1343
|
+
const hasGroups = items.some((item) => item.group);
|
|
905
1344
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
906
1345
|
"div",
|
|
907
1346
|
{
|
|
908
1347
|
"data-suggestions": "",
|
|
909
1348
|
"data-trigger": trigger,
|
|
1349
|
+
"data-suggestions-position": position,
|
|
910
1350
|
style,
|
|
911
1351
|
ref: listRef,
|
|
912
1352
|
children: [
|
|
@@ -944,28 +1384,76 @@ function SuggestionList({
|
|
|
944
1384
|
spellCheck: false
|
|
945
1385
|
}
|
|
946
1386
|
) }),
|
|
947
|
-
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-loading": "", children: "Loading..." }),
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
item.id
|
|
963
|
-
);
|
|
964
|
-
}) })
|
|
1387
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-loading": "", children: renderLoading ? renderLoading() : "Loading..." }),
|
|
1388
|
+
showEmpty && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-empty": "", children: renderEmpty ? renderEmpty(activeQuery) : "No results" }),
|
|
1389
|
+
!loading && items.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...listboxAttrs(listboxId, `${trigger ?? ""} suggestions`), children: hasGroups ? renderGroupedItems(items, activeIndex, depth, onSelect, onHover, renderItem, renderGroupHeader) : items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1390
|
+
SuggestionItem,
|
|
1391
|
+
{
|
|
1392
|
+
item,
|
|
1393
|
+
index,
|
|
1394
|
+
isActive: index === activeIndex,
|
|
1395
|
+
depth,
|
|
1396
|
+
onSelect,
|
|
1397
|
+
onHover,
|
|
1398
|
+
renderItem
|
|
1399
|
+
},
|
|
1400
|
+
item.id
|
|
1401
|
+
)) })
|
|
965
1402
|
]
|
|
966
1403
|
}
|
|
967
1404
|
);
|
|
968
1405
|
}
|
|
1406
|
+
function renderGroupedItems(items, activeIndex, depth, onSelect, onHover, renderItem, renderGroupHeader) {
|
|
1407
|
+
const elements = [];
|
|
1408
|
+
let lastGroup;
|
|
1409
|
+
items.forEach((item, index) => {
|
|
1410
|
+
if (item.group && item.group !== lastGroup) {
|
|
1411
|
+
lastGroup = item.group;
|
|
1412
|
+
elements.push(
|
|
1413
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-group-header": "", children: renderGroupHeader ? renderGroupHeader(item.group) : item.group }, `group-${item.group}`)
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
elements.push(
|
|
1417
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1418
|
+
SuggestionItem,
|
|
1419
|
+
{
|
|
1420
|
+
item,
|
|
1421
|
+
index,
|
|
1422
|
+
isActive: index === activeIndex,
|
|
1423
|
+
depth,
|
|
1424
|
+
onSelect,
|
|
1425
|
+
onHover,
|
|
1426
|
+
renderItem
|
|
1427
|
+
},
|
|
1428
|
+
item.id
|
|
1429
|
+
)
|
|
1430
|
+
);
|
|
1431
|
+
});
|
|
1432
|
+
return elements;
|
|
1433
|
+
}
|
|
1434
|
+
function SuggestionItem({
|
|
1435
|
+
item,
|
|
1436
|
+
index,
|
|
1437
|
+
isActive,
|
|
1438
|
+
depth,
|
|
1439
|
+
onSelect,
|
|
1440
|
+
onHover,
|
|
1441
|
+
renderItem
|
|
1442
|
+
}) {
|
|
1443
|
+
const itemId = `mention-option-${item.id}`;
|
|
1444
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1445
|
+
"div",
|
|
1446
|
+
{
|
|
1447
|
+
...optionAttrs(itemId, isActive, index),
|
|
1448
|
+
"data-suggestion-item": "",
|
|
1449
|
+
"data-suggestion-item-active": isActive ? "" : void 0,
|
|
1450
|
+
"data-has-children": item.hasChildren ? "" : void 0,
|
|
1451
|
+
onMouseEnter: () => onHover(index),
|
|
1452
|
+
onClick: () => onSelect(item),
|
|
1453
|
+
children: renderItem ? renderItem(item, depth) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultSuggestionItem, { item })
|
|
1454
|
+
}
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
969
1457
|
function DefaultSuggestionItem({ item }) {
|
|
970
1458
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
971
1459
|
item.icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-suggestion-item-icon": "", children: item.icon }),
|
|
@@ -974,25 +1462,48 @@ function DefaultSuggestionItem({ item }) {
|
|
|
974
1462
|
item.hasChildren && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-suggestion-item-chevron": "", "aria-hidden": "true", children: "\u203A" })
|
|
975
1463
|
] });
|
|
976
1464
|
}
|
|
1465
|
+
var POPOVER_HEIGHT_ESTIMATE = 280;
|
|
1466
|
+
var POPOVER_WIDTH_ESTIMATE = 360;
|
|
977
1467
|
function usePopoverPosition(clientRect) {
|
|
978
1468
|
if (!clientRect) {
|
|
979
|
-
return { display: "none" };
|
|
1469
|
+
return { style: { display: "none" }, position: "below" };
|
|
980
1470
|
}
|
|
981
1471
|
const rect = clientRect();
|
|
982
1472
|
if (!rect) {
|
|
983
|
-
return { display: "none" };
|
|
1473
|
+
return { style: { display: "none" }, position: "below" };
|
|
1474
|
+
}
|
|
1475
|
+
const viewportH = typeof window !== "undefined" ? window.innerHeight : 800;
|
|
1476
|
+
const viewportW = typeof window !== "undefined" ? window.innerWidth : 1200;
|
|
1477
|
+
const spaceBelow = viewportH - rect.bottom;
|
|
1478
|
+
const shouldFlip = spaceBelow < POPOVER_HEIGHT_ESTIMATE && rect.top > spaceBelow;
|
|
1479
|
+
let left = rect.left;
|
|
1480
|
+
if (left + POPOVER_WIDTH_ESTIMATE > viewportW) {
|
|
1481
|
+
left = Math.max(0, viewportW - POPOVER_WIDTH_ESTIMATE);
|
|
1482
|
+
}
|
|
1483
|
+
if (shouldFlip) {
|
|
1484
|
+
return {
|
|
1485
|
+
style: {
|
|
1486
|
+
position: "fixed",
|
|
1487
|
+
left: `${left}px`,
|
|
1488
|
+
bottom: `${viewportH - rect.top + 4}px`,
|
|
1489
|
+
zIndex: 50
|
|
1490
|
+
},
|
|
1491
|
+
position: "above"
|
|
1492
|
+
};
|
|
984
1493
|
}
|
|
985
1494
|
return {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1495
|
+
style: {
|
|
1496
|
+
position: "fixed",
|
|
1497
|
+
left: `${left}px`,
|
|
1498
|
+
top: `${rect.bottom + 4}px`,
|
|
1499
|
+
zIndex: 50
|
|
1500
|
+
},
|
|
1501
|
+
position: "below"
|
|
990
1502
|
};
|
|
991
1503
|
}
|
|
992
1504
|
|
|
993
1505
|
// src/components/MentionsInput.tsx
|
|
994
1506
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
995
|
-
var LISTBOX_ID2 = "mentions-suggestion-listbox";
|
|
996
1507
|
var MentionsInput = (0, import_react5.forwardRef)(
|
|
997
1508
|
function MentionsInput2({
|
|
998
1509
|
value,
|
|
@@ -1006,10 +1517,31 @@ var MentionsInput = (0, import_react5.forwardRef)(
|
|
|
1006
1517
|
clearOnSubmit = true,
|
|
1007
1518
|
maxLength,
|
|
1008
1519
|
renderItem,
|
|
1009
|
-
renderChip
|
|
1520
|
+
renderChip,
|
|
1521
|
+
renderEmpty,
|
|
1522
|
+
renderLoading,
|
|
1523
|
+
renderGroupHeader,
|
|
1524
|
+
onFocus,
|
|
1525
|
+
onBlur,
|
|
1526
|
+
onMentionAdd,
|
|
1527
|
+
onMentionRemove,
|
|
1528
|
+
onMentionClick,
|
|
1529
|
+
onMentionHover,
|
|
1530
|
+
minHeight,
|
|
1531
|
+
maxHeight,
|
|
1532
|
+
submitKey = "enter",
|
|
1533
|
+
allowTrigger,
|
|
1534
|
+
validateMention,
|
|
1535
|
+
portalContainer,
|
|
1536
|
+
streaming,
|
|
1537
|
+
onStreamingComplete
|
|
1010
1538
|
}, ref) {
|
|
1011
|
-
const
|
|
1012
|
-
const
|
|
1539
|
+
const instanceId = (0, import_react5.useId)();
|
|
1540
|
+
const listboxId = `mentions-listbox-${instanceId}`;
|
|
1541
|
+
const { uiState, actions, callbacksRef } = useSuggestion(providers, {
|
|
1542
|
+
onMentionAdd
|
|
1543
|
+
});
|
|
1544
|
+
const { editor, getOutput, clear, setContent, appendText, focus } = useMentionsEditor({
|
|
1013
1545
|
providers,
|
|
1014
1546
|
value,
|
|
1015
1547
|
onChange,
|
|
@@ -1018,46 +1550,72 @@ var MentionsInput = (0, import_react5.forwardRef)(
|
|
|
1018
1550
|
placeholder,
|
|
1019
1551
|
autoFocus,
|
|
1020
1552
|
editable: !disabled,
|
|
1021
|
-
callbacksRef
|
|
1553
|
+
callbacksRef,
|
|
1554
|
+
onFocus,
|
|
1555
|
+
onBlur,
|
|
1556
|
+
submitKey,
|
|
1557
|
+
onMentionRemove,
|
|
1558
|
+
onMentionClick,
|
|
1559
|
+
onMentionHover,
|
|
1560
|
+
allowTrigger,
|
|
1561
|
+
validateMention,
|
|
1562
|
+
streaming,
|
|
1563
|
+
onStreamingComplete
|
|
1022
1564
|
});
|
|
1023
1565
|
(0, import_react5.useImperativeHandle)(
|
|
1024
1566
|
ref,
|
|
1025
|
-
() => ({ clear, setContent, focus }),
|
|
1026
|
-
[clear, setContent, focus]
|
|
1567
|
+
() => ({ clear, setContent, appendText, focus, getOutput }),
|
|
1568
|
+
[clear, setContent, appendText, focus, getOutput]
|
|
1027
1569
|
);
|
|
1028
1570
|
const isExpanded = uiState.state !== "idle";
|
|
1029
1571
|
const handleHover = (0, import_react5.useCallback)((index) => {
|
|
1030
1572
|
void index;
|
|
1031
1573
|
}, []);
|
|
1574
|
+
const handleFocusEditor = (0, import_react5.useCallback)(() => {
|
|
1575
|
+
editor?.commands.focus();
|
|
1576
|
+
}, [editor]);
|
|
1577
|
+
const editorStyle = {};
|
|
1578
|
+
if (minHeight != null) editorStyle.minHeight = `${minHeight}px`;
|
|
1579
|
+
if (maxHeight != null) {
|
|
1580
|
+
editorStyle.maxHeight = `${maxHeight}px`;
|
|
1581
|
+
editorStyle.overflowY = "auto";
|
|
1582
|
+
}
|
|
1583
|
+
const suggestionList = isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1584
|
+
SuggestionList,
|
|
1585
|
+
{
|
|
1586
|
+
items: uiState.items,
|
|
1587
|
+
activeIndex: uiState.activeIndex,
|
|
1588
|
+
breadcrumbs: uiState.breadcrumbs,
|
|
1589
|
+
loading: uiState.loading,
|
|
1590
|
+
trigger: uiState.trigger,
|
|
1591
|
+
query: uiState.query,
|
|
1592
|
+
clientRect: uiState.clientRect,
|
|
1593
|
+
onSelect: (item) => actions.select(item),
|
|
1594
|
+
onHover: handleHover,
|
|
1595
|
+
onGoBack: actions.goBack,
|
|
1596
|
+
onSearchNested: actions.searchNested,
|
|
1597
|
+
onNavigateUp: actions.navigateUp,
|
|
1598
|
+
onNavigateDown: actions.navigateDown,
|
|
1599
|
+
onClose: actions.close,
|
|
1600
|
+
onFocusEditor: handleFocusEditor,
|
|
1601
|
+
renderItem,
|
|
1602
|
+
renderEmpty,
|
|
1603
|
+
renderLoading,
|
|
1604
|
+
renderGroupHeader,
|
|
1605
|
+
listboxId
|
|
1606
|
+
}
|
|
1607
|
+
) : null;
|
|
1032
1608
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1033
1609
|
"div",
|
|
1034
1610
|
{
|
|
1035
1611
|
className,
|
|
1036
1612
|
"data-mentions-input": "",
|
|
1037
1613
|
"data-disabled": disabled ? "" : void 0,
|
|
1038
|
-
...comboboxAttrs(isExpanded,
|
|
1614
|
+
...comboboxAttrs(isExpanded, listboxId),
|
|
1039
1615
|
"aria-activedescendant": isExpanded && uiState.items[uiState.activeIndex] ? `mention-option-${uiState.items[uiState.activeIndex].id}` : void 0,
|
|
1040
1616
|
children: [
|
|
1041
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react6.EditorContent, { editor }),
|
|
1042
|
-
|
|
1043
|
-
SuggestionList,
|
|
1044
|
-
{
|
|
1045
|
-
items: uiState.items,
|
|
1046
|
-
activeIndex: uiState.activeIndex,
|
|
1047
|
-
breadcrumbs: uiState.breadcrumbs,
|
|
1048
|
-
loading: uiState.loading,
|
|
1049
|
-
trigger: uiState.trigger,
|
|
1050
|
-
clientRect: uiState.clientRect,
|
|
1051
|
-
onSelect: (item) => actions.select(item),
|
|
1052
|
-
onHover: handleHover,
|
|
1053
|
-
onGoBack: actions.goBack,
|
|
1054
|
-
onSearchNested: actions.searchNested,
|
|
1055
|
-
onNavigateUp: actions.navigateUp,
|
|
1056
|
-
onNavigateDown: actions.navigateDown,
|
|
1057
|
-
onClose: actions.close,
|
|
1058
|
-
renderItem
|
|
1059
|
-
}
|
|
1060
|
-
)
|
|
1617
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: editorStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react6.EditorContent, { editor }) }),
|
|
1618
|
+
portalContainer ? suggestionList && (0, import_react_dom.createPortal)(suggestionList, portalContainer) : suggestionList
|
|
1061
1619
|
]
|
|
1062
1620
|
}
|
|
1063
1621
|
);
|