@relevaince/mentions 0.3.2 → 0.5.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 +125 -83
- package/dist/index.d.mts +41 -8
- package/dist/index.d.ts +41 -8
- package/dist/index.js +542 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +548 -96
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -3
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/components/MentionsInput.tsx
|
|
2
|
-
import { forwardRef, useCallback as useCallback3, useImperativeHandle } from "react";
|
|
2
|
+
import { forwardRef, useCallback as useCallback3, useId, useImperativeHandle } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
3
4
|
import { EditorContent } from "@tiptap/react";
|
|
4
5
|
|
|
5
6
|
// src/hooks/useMentionsEditor.ts
|
|
@@ -25,6 +26,12 @@ var MentionNode = Node.create({
|
|
|
25
26
|
atom: true,
|
|
26
27
|
selectable: true,
|
|
27
28
|
draggable: false,
|
|
29
|
+
addOptions() {
|
|
30
|
+
return {
|
|
31
|
+
onClickRef: void 0,
|
|
32
|
+
onHoverRef: void 0
|
|
33
|
+
};
|
|
34
|
+
},
|
|
28
35
|
addAttributes() {
|
|
29
36
|
return {
|
|
30
37
|
id: {
|
|
@@ -57,11 +64,17 @@ var MentionNode = Node.create({
|
|
|
57
64
|
const label = node.attrs.label;
|
|
58
65
|
const prefix = DEFAULT_PREFIXES[entityType] ?? "@";
|
|
59
66
|
const display = `${prefix}${label}`;
|
|
67
|
+
const hasClick = !!this.options.onClickRef?.current;
|
|
68
|
+
const extraAttrs = {};
|
|
69
|
+
if (hasClick) {
|
|
70
|
+
extraAttrs["data-mention-clickable"] = "";
|
|
71
|
+
}
|
|
60
72
|
return [
|
|
61
73
|
"span",
|
|
62
74
|
mergeAttributes(HTMLAttributes, {
|
|
63
75
|
"data-mention": "",
|
|
64
|
-
class: "mention-chip"
|
|
76
|
+
class: "mention-chip",
|
|
77
|
+
...extraAttrs
|
|
65
78
|
}),
|
|
66
79
|
display
|
|
67
80
|
];
|
|
@@ -72,6 +85,58 @@ var MentionNode = Node.create({
|
|
|
72
85
|
const prefix = DEFAULT_PREFIXES[entityType] ?? "@";
|
|
73
86
|
return `${prefix}${label}`;
|
|
74
87
|
},
|
|
88
|
+
addNodeView() {
|
|
89
|
+
const options = this.options;
|
|
90
|
+
return ({ node, HTMLAttributes }) => {
|
|
91
|
+
const entityType = node.attrs.entityType;
|
|
92
|
+
const label = node.attrs.label;
|
|
93
|
+
const id = node.attrs.id;
|
|
94
|
+
const prefix = DEFAULT_PREFIXES[entityType] ?? "@";
|
|
95
|
+
const dom = document.createElement("span");
|
|
96
|
+
Object.entries(
|
|
97
|
+
mergeAttributes(HTMLAttributes, {
|
|
98
|
+
"data-mention": "",
|
|
99
|
+
"data-type": entityType,
|
|
100
|
+
"data-id": id,
|
|
101
|
+
class: "mention-chip"
|
|
102
|
+
})
|
|
103
|
+
).forEach(([key, val]) => {
|
|
104
|
+
if (val != null && val !== false) dom.setAttribute(key, String(val));
|
|
105
|
+
});
|
|
106
|
+
dom.textContent = `${prefix}${label}`;
|
|
107
|
+
if (options.onClickRef?.current) {
|
|
108
|
+
dom.setAttribute("data-mention-clickable", "");
|
|
109
|
+
dom.style.cursor = "pointer";
|
|
110
|
+
}
|
|
111
|
+
dom.addEventListener("click", (event) => {
|
|
112
|
+
const handler = options.onClickRef?.current;
|
|
113
|
+
if (handler) {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
handler({ id, type: entityType, label }, event);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
let tooltip = null;
|
|
120
|
+
dom.addEventListener("mouseenter", () => {
|
|
121
|
+
const hoverFn = options.onHoverRef?.current;
|
|
122
|
+
if (!hoverFn) return;
|
|
123
|
+
const content = hoverFn({ id, type: entityType, label });
|
|
124
|
+
if (!content) return;
|
|
125
|
+
tooltip = document.createElement("div");
|
|
126
|
+
tooltip.setAttribute("data-mention-tooltip", "");
|
|
127
|
+
tooltip.textContent = typeof content === "string" ? content : "";
|
|
128
|
+
dom.style.position = "relative";
|
|
129
|
+
dom.appendChild(tooltip);
|
|
130
|
+
});
|
|
131
|
+
dom.addEventListener("mouseleave", () => {
|
|
132
|
+
if (tooltip && tooltip.parentNode) {
|
|
133
|
+
tooltip.parentNode.removeChild(tooltip);
|
|
134
|
+
tooltip = null;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return { dom };
|
|
138
|
+
};
|
|
139
|
+
},
|
|
75
140
|
addKeyboardShortcuts() {
|
|
76
141
|
return {
|
|
77
142
|
Backspace: () => this.editor.commands.command(({ tr, state }) => {
|
|
@@ -104,7 +169,13 @@ function detectTrigger(text, cursorPos, docStartPos, triggers) {
|
|
|
104
169
|
if (before.substring(i, i + trigger.length) === trigger) {
|
|
105
170
|
if (i === 0 || /\s/.test(before[i - 1])) {
|
|
106
171
|
const query = before.slice(i + trigger.length);
|
|
107
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
trigger,
|
|
174
|
+
query,
|
|
175
|
+
from: docStartPos + i,
|
|
176
|
+
to: cursorPos,
|
|
177
|
+
textBefore: before.slice(0, i)
|
|
178
|
+
};
|
|
108
179
|
}
|
|
109
180
|
}
|
|
110
181
|
}
|
|
@@ -112,7 +183,7 @@ function detectTrigger(text, cursorPos, docStartPos, triggers) {
|
|
|
112
183
|
return null;
|
|
113
184
|
}
|
|
114
185
|
var suggestionPluginKey = new PluginKey("mentionSuggestion");
|
|
115
|
-
function createSuggestionExtension(triggers, callbacksRef) {
|
|
186
|
+
function createSuggestionExtension(triggers, callbacksRef, allowTriggerRef) {
|
|
116
187
|
return Extension.create({
|
|
117
188
|
name: "mentionSuggestion",
|
|
118
189
|
priority: 200,
|
|
@@ -190,6 +261,20 @@ function createSuggestionExtension(triggers, callbacksRef) {
|
|
|
190
261
|
const cursorPos = $pos.pos;
|
|
191
262
|
const match = detectTrigger(blockText, cursorPos, blockStart, triggers);
|
|
192
263
|
if (match) {
|
|
264
|
+
if (allowTriggerRef?.current) {
|
|
265
|
+
const allowed = allowTriggerRef.current(match.trigger, {
|
|
266
|
+
textBefore: match.textBefore
|
|
267
|
+
});
|
|
268
|
+
if (!allowed) {
|
|
269
|
+
if (active) {
|
|
270
|
+
active = false;
|
|
271
|
+
lastQuery = null;
|
|
272
|
+
lastTrigger = null;
|
|
273
|
+
callbacksRef.current.onExit();
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
193
278
|
const range = { from: match.from, to: match.to };
|
|
194
279
|
const props = {
|
|
195
280
|
query: match.query,
|
|
@@ -247,11 +332,12 @@ function serializeParagraph(node) {
|
|
|
247
332
|
if (!node.content) return "";
|
|
248
333
|
return node.content.map((child) => {
|
|
249
334
|
if (child.type === "mention") {
|
|
250
|
-
const { id, label, rootLabel } = child.attrs ?? {};
|
|
335
|
+
const { id, label, entityType, rootLabel } = child.attrs ?? {};
|
|
336
|
+
const idPart = entityType && entityType !== "unknown" ? `${entityType}:${id}` : id;
|
|
251
337
|
if (rootLabel != null && rootLabel !== "") {
|
|
252
|
-
return `@${rootLabel}[${label}](${
|
|
338
|
+
return `@${rootLabel}[${label}](${idPart})`;
|
|
253
339
|
}
|
|
254
|
-
return `@[${label}](${
|
|
340
|
+
return `@[${label}](${idPart})`;
|
|
255
341
|
}
|
|
256
342
|
return child.text ?? "";
|
|
257
343
|
}).join("");
|
|
@@ -296,7 +382,7 @@ function extractParagraphText(node) {
|
|
|
296
382
|
}
|
|
297
383
|
|
|
298
384
|
// src/core/markdownParser.ts
|
|
299
|
-
var MENTION_RE = /@(
|
|
385
|
+
var MENTION_RE = /@([^\[@]*)\[([^\]]+)\]\((?:([^:)]+):)?([^)]+)\)/g;
|
|
300
386
|
function parseFromMarkdown(markdown) {
|
|
301
387
|
const lines = markdown.split("\n");
|
|
302
388
|
const content = lines.map((line) => {
|
|
@@ -312,7 +398,7 @@ function parseLine(line) {
|
|
|
312
398
|
let match;
|
|
313
399
|
while ((match = MENTION_RE.exec(line)) !== null) {
|
|
314
400
|
const fullMatch = match[0];
|
|
315
|
-
const rootLabel = match[1]
|
|
401
|
+
const rootLabel = match[1] || null;
|
|
316
402
|
const label = match[2];
|
|
317
403
|
const entityType = match[3] ?? "unknown";
|
|
318
404
|
const id = match[4];
|
|
@@ -358,27 +444,48 @@ function buildOutput(editor) {
|
|
|
358
444
|
plainText: extractPlainText(json)
|
|
359
445
|
};
|
|
360
446
|
}
|
|
361
|
-
function
|
|
447
|
+
function collectMentionTokens(doc) {
|
|
448
|
+
const tokens = [];
|
|
449
|
+
function walk(node) {
|
|
450
|
+
if (node.type === "mention" && node.attrs) {
|
|
451
|
+
tokens.push({
|
|
452
|
+
id: node.attrs.id,
|
|
453
|
+
type: node.attrs.entityType ?? node.attrs.type,
|
|
454
|
+
label: node.attrs.label
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
if (node.content) {
|
|
458
|
+
for (const child of node.content) walk(child);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
walk(doc);
|
|
462
|
+
return tokens;
|
|
463
|
+
}
|
|
464
|
+
function createSubmitExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef) {
|
|
362
465
|
return Extension2.create({
|
|
363
466
|
name: "submitShortcut",
|
|
364
467
|
priority: 150,
|
|
365
468
|
addKeyboardShortcuts() {
|
|
366
469
|
return {
|
|
367
470
|
"Mod-Enter": () => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (
|
|
371
|
-
this.editor
|
|
471
|
+
const key = submitKeyRef.current;
|
|
472
|
+
if (key === "mod+enter" || key === "enter") {
|
|
473
|
+
if (onSubmitRef.current) {
|
|
474
|
+
onSubmitRef.current(buildOutput(this.editor));
|
|
475
|
+
if (clearOnSubmitRef.current) {
|
|
476
|
+
this.editor.commands.clearContent(true);
|
|
477
|
+
}
|
|
372
478
|
}
|
|
479
|
+
return true;
|
|
373
480
|
}
|
|
374
|
-
return
|
|
481
|
+
return false;
|
|
375
482
|
}
|
|
376
483
|
};
|
|
377
484
|
}
|
|
378
485
|
});
|
|
379
486
|
}
|
|
380
487
|
var enterSubmitPluginKey = new PluginKey2("enterSubmit");
|
|
381
|
-
function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
488
|
+
function createEnterExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef) {
|
|
382
489
|
return Extension2.create({
|
|
383
490
|
name: "enterSubmit",
|
|
384
491
|
priority: 150,
|
|
@@ -390,6 +497,11 @@ function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
390
497
|
props: {
|
|
391
498
|
handleKeyDown(_view, event) {
|
|
392
499
|
if (event.key !== "Enter") return false;
|
|
500
|
+
const key = submitKeyRef.current;
|
|
501
|
+
if (key === "none") return false;
|
|
502
|
+
if (key === "mod+enter") {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
393
505
|
if (event.shiftKey) {
|
|
394
506
|
editor.commands.splitBlock();
|
|
395
507
|
return true;
|
|
@@ -409,6 +521,33 @@ function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
409
521
|
}
|
|
410
522
|
});
|
|
411
523
|
}
|
|
524
|
+
var mentionRemovePluginKey = new PluginKey2("mentionRemove");
|
|
525
|
+
function createMentionRemoveExtension(onMentionRemoveRef) {
|
|
526
|
+
return Extension2.create({
|
|
527
|
+
name: "mentionRemoveDetector",
|
|
528
|
+
priority: 100,
|
|
529
|
+
addProseMirrorPlugins() {
|
|
530
|
+
return [
|
|
531
|
+
new Plugin2({
|
|
532
|
+
key: mentionRemovePluginKey,
|
|
533
|
+
appendTransaction(transactions, oldState, newState) {
|
|
534
|
+
if (!onMentionRemoveRef.current) return null;
|
|
535
|
+
const oldMentions = collectMentionTokens(oldState.doc.toJSON());
|
|
536
|
+
const newMentions = collectMentionTokens(newState.doc.toJSON());
|
|
537
|
+
if (oldMentions.length <= newMentions.length) return null;
|
|
538
|
+
const newIds = new Set(newMentions.map((m) => m.id));
|
|
539
|
+
for (const m of oldMentions) {
|
|
540
|
+
if (!newIds.has(m.id)) {
|
|
541
|
+
onMentionRemoveRef.current(m);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
];
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
412
551
|
function useMentionsEditor({
|
|
413
552
|
providers,
|
|
414
553
|
value,
|
|
@@ -418,7 +557,15 @@ function useMentionsEditor({
|
|
|
418
557
|
placeholder,
|
|
419
558
|
autoFocus = false,
|
|
420
559
|
editable = true,
|
|
421
|
-
callbacksRef
|
|
560
|
+
callbacksRef,
|
|
561
|
+
onFocus,
|
|
562
|
+
onBlur,
|
|
563
|
+
submitKey = "enter",
|
|
564
|
+
onMentionRemove,
|
|
565
|
+
onMentionClick,
|
|
566
|
+
onMentionHover,
|
|
567
|
+
allowTrigger,
|
|
568
|
+
validateMention
|
|
422
569
|
}) {
|
|
423
570
|
const onChangeRef = useRef(onChange);
|
|
424
571
|
onChangeRef.current = onChange;
|
|
@@ -426,6 +573,23 @@ function useMentionsEditor({
|
|
|
426
573
|
onSubmitRef.current = onSubmit;
|
|
427
574
|
const clearOnSubmitRef = useRef(clearOnSubmit);
|
|
428
575
|
clearOnSubmitRef.current = clearOnSubmit;
|
|
576
|
+
const onFocusRef = useRef(onFocus);
|
|
577
|
+
onFocusRef.current = onFocus;
|
|
578
|
+
const onBlurRef = useRef(onBlur);
|
|
579
|
+
onBlurRef.current = onBlur;
|
|
580
|
+
const submitKeyRef = useRef(submitKey);
|
|
581
|
+
submitKeyRef.current = submitKey;
|
|
582
|
+
const onMentionRemoveRef = useRef(onMentionRemove);
|
|
583
|
+
onMentionRemoveRef.current = onMentionRemove;
|
|
584
|
+
const onMentionClickRef = useRef(onMentionClick);
|
|
585
|
+
onMentionClickRef.current = onMentionClick;
|
|
586
|
+
const onMentionHoverRef = useRef(onMentionHover);
|
|
587
|
+
onMentionHoverRef.current = onMentionHover;
|
|
588
|
+
const allowTriggerRef = useRef(allowTrigger);
|
|
589
|
+
allowTriggerRef.current = allowTrigger;
|
|
590
|
+
const validateMentionRef = useRef(validateMention);
|
|
591
|
+
validateMentionRef.current = validateMention;
|
|
592
|
+
const internalMarkdownRef = useRef(null);
|
|
429
593
|
const initialContent = useMemo(() => {
|
|
430
594
|
if (!value) return void 0;
|
|
431
595
|
return parseFromMarkdown(value);
|
|
@@ -437,16 +601,27 @@ function useMentionsEditor({
|
|
|
437
601
|
[triggersKey]
|
|
438
602
|
);
|
|
439
603
|
const suggestionExtension = useMemo(
|
|
440
|
-
() => createSuggestionExtension(triggers, callbacksRef),
|
|
604
|
+
() => createSuggestionExtension(triggers, callbacksRef, allowTriggerRef),
|
|
441
605
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
442
606
|
[triggersKey]
|
|
443
607
|
);
|
|
444
608
|
const submitExt = useMemo(
|
|
445
|
-
() => createSubmitExtension(onSubmitRef, clearOnSubmitRef),
|
|
609
|
+
() => createSubmitExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef),
|
|
446
610
|
[]
|
|
447
611
|
);
|
|
448
612
|
const enterExt = useMemo(
|
|
449
|
-
() => createEnterExtension(onSubmitRef, clearOnSubmitRef),
|
|
613
|
+
() => createEnterExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef),
|
|
614
|
+
[]
|
|
615
|
+
);
|
|
616
|
+
const mentionRemoveExt = useMemo(
|
|
617
|
+
() => createMentionRemoveExtension(onMentionRemoveRef),
|
|
618
|
+
[]
|
|
619
|
+
);
|
|
620
|
+
const mentionNodeExt = useMemo(
|
|
621
|
+
() => MentionNode.configure({
|
|
622
|
+
onClickRef: onMentionClickRef,
|
|
623
|
+
onHoverRef: onMentionHoverRef
|
|
624
|
+
}),
|
|
450
625
|
[]
|
|
451
626
|
);
|
|
452
627
|
const editor = useEditor({
|
|
@@ -465,10 +640,11 @@ function useMentionsEditor({
|
|
|
465
640
|
placeholder: ({ editor: editor2 }) => editor2.isEmpty ? placeholder ?? "Type a message..." : "",
|
|
466
641
|
showOnlyCurrent: true
|
|
467
642
|
}),
|
|
468
|
-
|
|
643
|
+
mentionNodeExt,
|
|
469
644
|
suggestionExtension,
|
|
470
645
|
submitExt,
|
|
471
|
-
enterExt
|
|
646
|
+
enterExt,
|
|
647
|
+
mentionRemoveExt
|
|
472
648
|
],
|
|
473
649
|
content: initialContent,
|
|
474
650
|
autofocus: autoFocus ? "end" : false,
|
|
@@ -479,7 +655,15 @@ function useMentionsEditor({
|
|
|
479
655
|
}
|
|
480
656
|
},
|
|
481
657
|
onUpdate: ({ editor: editor2 }) => {
|
|
482
|
-
|
|
658
|
+
const output = buildOutput(editor2);
|
|
659
|
+
internalMarkdownRef.current = output.markdown;
|
|
660
|
+
onChangeRef.current?.(output);
|
|
661
|
+
},
|
|
662
|
+
onFocus: () => {
|
|
663
|
+
onFocusRef.current?.();
|
|
664
|
+
},
|
|
665
|
+
onBlur: () => {
|
|
666
|
+
onBlurRef.current?.();
|
|
483
667
|
}
|
|
484
668
|
});
|
|
485
669
|
useEffect(() => {
|
|
@@ -487,6 +671,36 @@ function useMentionsEditor({
|
|
|
487
671
|
editor.setEditable(editable);
|
|
488
672
|
}
|
|
489
673
|
}, [editor, editable]);
|
|
674
|
+
useEffect(() => {
|
|
675
|
+
if (!editor || value === void 0) return;
|
|
676
|
+
if (value === internalMarkdownRef.current) return;
|
|
677
|
+
const doc = parseFromMarkdown(value);
|
|
678
|
+
editor.commands.setContent(doc);
|
|
679
|
+
internalMarkdownRef.current = value;
|
|
680
|
+
}, [editor, value]);
|
|
681
|
+
useEffect(() => {
|
|
682
|
+
if (!editor || !validateMention) return;
|
|
683
|
+
const runValidation = async () => {
|
|
684
|
+
const doc = editor.getJSON();
|
|
685
|
+
const tokens = collectMentionTokens(doc);
|
|
686
|
+
const invalidIds = /* @__PURE__ */ new Set();
|
|
687
|
+
await Promise.all(
|
|
688
|
+
tokens.map(async (token) => {
|
|
689
|
+
const valid = await validateMention(token);
|
|
690
|
+
if (!valid) invalidIds.add(token.id);
|
|
691
|
+
})
|
|
692
|
+
);
|
|
693
|
+
editor.view.dom.querySelectorAll("[data-mention]").forEach((el) => {
|
|
694
|
+
const id = el.getAttribute("data-id");
|
|
695
|
+
if (id && invalidIds.has(id)) {
|
|
696
|
+
el.setAttribute("data-mention-invalid", "");
|
|
697
|
+
} else {
|
|
698
|
+
el.removeAttribute("data-mention-invalid");
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
};
|
|
702
|
+
runValidation();
|
|
703
|
+
}, [editor, validateMention]);
|
|
490
704
|
const clear = useCallback(() => {
|
|
491
705
|
editor?.commands.clearContent(true);
|
|
492
706
|
}, [editor]);
|
|
@@ -495,6 +709,7 @@ function useMentionsEditor({
|
|
|
495
709
|
if (!editor) return;
|
|
496
710
|
const doc = parseFromMarkdown(markdown);
|
|
497
711
|
editor.commands.setContent(doc);
|
|
712
|
+
internalMarkdownRef.current = markdown;
|
|
498
713
|
},
|
|
499
714
|
[editor]
|
|
500
715
|
);
|
|
@@ -509,7 +724,28 @@ function useMentionsEditor({
|
|
|
509
724
|
}
|
|
510
725
|
|
|
511
726
|
// src/hooks/useSuggestion.ts
|
|
512
|
-
import { useCallback as useCallback2, useRef as useRef2, useState } from "react";
|
|
727
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
728
|
+
|
|
729
|
+
// src/utils/debounce.ts
|
|
730
|
+
function debounce(fn, ms) {
|
|
731
|
+
let timer = null;
|
|
732
|
+
const debounced = ((...args) => {
|
|
733
|
+
if (timer != null) clearTimeout(timer);
|
|
734
|
+
timer = setTimeout(() => {
|
|
735
|
+
timer = null;
|
|
736
|
+
fn(...args);
|
|
737
|
+
}, ms);
|
|
738
|
+
});
|
|
739
|
+
debounced.cancel = () => {
|
|
740
|
+
if (timer != null) {
|
|
741
|
+
clearTimeout(timer);
|
|
742
|
+
timer = null;
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
return debounced;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/hooks/useSuggestion.ts
|
|
513
749
|
var IDLE_STATE = {
|
|
514
750
|
state: "idle",
|
|
515
751
|
items: [],
|
|
@@ -520,16 +756,24 @@ var IDLE_STATE = {
|
|
|
520
756
|
trigger: null,
|
|
521
757
|
query: ""
|
|
522
758
|
};
|
|
523
|
-
function useSuggestion(providers) {
|
|
759
|
+
function useSuggestion(providers, options = {}) {
|
|
524
760
|
const [uiState, setUIState] = useState(IDLE_STATE);
|
|
525
761
|
const stateRef = useRef2(uiState);
|
|
526
762
|
stateRef.current = uiState;
|
|
527
763
|
const providersRef = useRef2(providers);
|
|
528
764
|
providersRef.current = providers;
|
|
765
|
+
const onMentionAddRef = useRef2(options.onMentionAdd);
|
|
766
|
+
onMentionAddRef.current = options.onMentionAdd;
|
|
529
767
|
const commandRef = useRef2(
|
|
530
768
|
null
|
|
531
769
|
);
|
|
532
770
|
const providerRef = useRef2(null);
|
|
771
|
+
const debouncedFetchRef = useRef2(null);
|
|
772
|
+
useEffect2(() => {
|
|
773
|
+
return () => {
|
|
774
|
+
debouncedFetchRef.current?.cancel();
|
|
775
|
+
};
|
|
776
|
+
}, []);
|
|
533
777
|
const fetchItems = useCallback2(
|
|
534
778
|
async (provider, query, parent, useSearchAll) => {
|
|
535
779
|
setUIState((prev) => ({ ...prev, loading: true, state: "loading" }));
|
|
@@ -560,6 +804,23 @@ function useSuggestion(providers) {
|
|
|
560
804
|
},
|
|
561
805
|
[]
|
|
562
806
|
);
|
|
807
|
+
const scheduleFetch = useCallback2(
|
|
808
|
+
(provider, query, parent, useSearchAll) => {
|
|
809
|
+
debouncedFetchRef.current?.cancel();
|
|
810
|
+
const ms = provider.debounceMs;
|
|
811
|
+
if (ms && ms > 0) {
|
|
812
|
+
setUIState((prev) => ({ ...prev, loading: true }));
|
|
813
|
+
const debouncedFn = debounce(() => {
|
|
814
|
+
fetchItems(provider, query, parent, useSearchAll);
|
|
815
|
+
}, ms);
|
|
816
|
+
debouncedFetchRef.current = debouncedFn;
|
|
817
|
+
debouncedFn();
|
|
818
|
+
} else {
|
|
819
|
+
fetchItems(provider, query, parent, useSearchAll);
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
[fetchItems]
|
|
823
|
+
);
|
|
563
824
|
const onStart = useCallback2(
|
|
564
825
|
(props) => {
|
|
565
826
|
const provider = providersRef.current.find(
|
|
@@ -578,13 +839,31 @@ function useSuggestion(providers) {
|
|
|
578
839
|
trigger: props.trigger,
|
|
579
840
|
query: props.query
|
|
580
841
|
});
|
|
842
|
+
if (!props.query.trim() && provider.getRecentItems) {
|
|
843
|
+
provider.getRecentItems().then((recentItems) => {
|
|
844
|
+
const tagged = recentItems.map((item) => ({
|
|
845
|
+
...item,
|
|
846
|
+
group: item.group ?? "Recent"
|
|
847
|
+
}));
|
|
848
|
+
setUIState((prev) => ({
|
|
849
|
+
...prev,
|
|
850
|
+
items: tagged,
|
|
851
|
+
loading: false,
|
|
852
|
+
state: "showing",
|
|
853
|
+
activeIndex: 0
|
|
854
|
+
}));
|
|
855
|
+
}).catch(() => {
|
|
856
|
+
scheduleFetch(provider, props.query);
|
|
857
|
+
});
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
581
860
|
if (props.query.trim() && provider.searchAll) {
|
|
582
|
-
|
|
861
|
+
scheduleFetch(provider, props.query, void 0, true);
|
|
583
862
|
} else {
|
|
584
|
-
|
|
863
|
+
scheduleFetch(provider, props.query);
|
|
585
864
|
}
|
|
586
865
|
},
|
|
587
|
-
[
|
|
866
|
+
[scheduleFetch]
|
|
588
867
|
);
|
|
589
868
|
const onUpdate = useCallback2(
|
|
590
869
|
(props) => {
|
|
@@ -608,14 +887,31 @@ function useSuggestion(providers) {
|
|
|
608
887
|
}));
|
|
609
888
|
}
|
|
610
889
|
if (props.query.trim() && provider.searchAll) {
|
|
611
|
-
|
|
890
|
+
scheduleFetch(provider, props.query, void 0, true);
|
|
891
|
+
} else if (!props.query.trim() && provider.getRecentItems) {
|
|
892
|
+
provider.getRecentItems().then((recentItems) => {
|
|
893
|
+
const tagged = recentItems.map((item) => ({
|
|
894
|
+
...item,
|
|
895
|
+
group: item.group ?? "Recent"
|
|
896
|
+
}));
|
|
897
|
+
setUIState((prev) => ({
|
|
898
|
+
...prev,
|
|
899
|
+
items: tagged,
|
|
900
|
+
loading: false,
|
|
901
|
+
state: "showing",
|
|
902
|
+
activeIndex: 0
|
|
903
|
+
}));
|
|
904
|
+
}).catch(() => {
|
|
905
|
+
scheduleFetch(provider, props.query);
|
|
906
|
+
});
|
|
612
907
|
} else {
|
|
613
|
-
|
|
908
|
+
scheduleFetch(provider, props.query);
|
|
614
909
|
}
|
|
615
910
|
},
|
|
616
|
-
[
|
|
911
|
+
[scheduleFetch]
|
|
617
912
|
);
|
|
618
913
|
const onExit = useCallback2(() => {
|
|
914
|
+
debouncedFetchRef.current?.cancel();
|
|
619
915
|
providerRef.current = null;
|
|
620
916
|
commandRef.current = null;
|
|
621
917
|
setUIState(IDLE_STATE);
|
|
@@ -650,8 +946,8 @@ function useSuggestion(providers) {
|
|
|
650
946
|
fetchItems(provider, "", selected);
|
|
651
947
|
return;
|
|
652
948
|
}
|
|
949
|
+
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
653
950
|
if (commandRef.current) {
|
|
654
|
-
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
655
951
|
commandRef.current({
|
|
656
952
|
id: selected.id,
|
|
657
953
|
label: selected.label,
|
|
@@ -659,6 +955,11 @@ function useSuggestion(providers) {
|
|
|
659
955
|
rootLabel
|
|
660
956
|
});
|
|
661
957
|
}
|
|
958
|
+
onMentionAddRef.current?.({
|
|
959
|
+
id: selected.id,
|
|
960
|
+
type: selected.type,
|
|
961
|
+
label: selected.label
|
|
962
|
+
});
|
|
662
963
|
},
|
|
663
964
|
[fetchItems]
|
|
664
965
|
);
|
|
@@ -683,6 +984,7 @@ function useSuggestion(providers) {
|
|
|
683
984
|
});
|
|
684
985
|
}, [fetchItems]);
|
|
685
986
|
const close = useCallback2(() => {
|
|
987
|
+
debouncedFetchRef.current?.cancel();
|
|
686
988
|
setUIState(IDLE_STATE);
|
|
687
989
|
}, []);
|
|
688
990
|
const searchNested = useCallback2(
|
|
@@ -692,10 +994,10 @@ function useSuggestion(providers) {
|
|
|
692
994
|
const current = stateRef.current;
|
|
693
995
|
const parent = current.breadcrumbs[current.breadcrumbs.length - 1];
|
|
694
996
|
if (parent) {
|
|
695
|
-
|
|
997
|
+
scheduleFetch(provider, query, parent);
|
|
696
998
|
}
|
|
697
999
|
},
|
|
698
|
-
[
|
|
1000
|
+
[scheduleFetch]
|
|
699
1001
|
);
|
|
700
1002
|
const onKeyDown = useCallback2(
|
|
701
1003
|
({ event }) => {
|
|
@@ -718,6 +1020,14 @@ function useSuggestion(providers) {
|
|
|
718
1020
|
}
|
|
719
1021
|
return true;
|
|
720
1022
|
}
|
|
1023
|
+
case "Tab": {
|
|
1024
|
+
event.preventDefault();
|
|
1025
|
+
const selectedItem = current.items[current.activeIndex];
|
|
1026
|
+
if (selectedItem) {
|
|
1027
|
+
select(selectedItem);
|
|
1028
|
+
}
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
721
1031
|
case "ArrowRight": {
|
|
722
1032
|
const activeItem = current.items[current.activeIndex];
|
|
723
1033
|
if (activeItem?.hasChildren) {
|
|
@@ -763,7 +1073,7 @@ function useSuggestion(providers) {
|
|
|
763
1073
|
}
|
|
764
1074
|
|
|
765
1075
|
// src/components/SuggestionList.tsx
|
|
766
|
-
import { useEffect as
|
|
1076
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
767
1077
|
|
|
768
1078
|
// src/utils/ariaHelpers.ts
|
|
769
1079
|
function comboboxAttrs(expanded, listboxId) {
|
|
@@ -792,13 +1102,13 @@ function optionAttrs(id, selected, index) {
|
|
|
792
1102
|
|
|
793
1103
|
// src/components/SuggestionList.tsx
|
|
794
1104
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
795
|
-
var LISTBOX_ID = "mentions-suggestion-listbox";
|
|
796
1105
|
function SuggestionList({
|
|
797
1106
|
items,
|
|
798
1107
|
activeIndex,
|
|
799
1108
|
breadcrumbs,
|
|
800
1109
|
loading,
|
|
801
1110
|
trigger,
|
|
1111
|
+
query,
|
|
802
1112
|
clientRect,
|
|
803
1113
|
onSelect,
|
|
804
1114
|
onHover,
|
|
@@ -807,7 +1117,12 @@ function SuggestionList({
|
|
|
807
1117
|
onNavigateUp,
|
|
808
1118
|
onNavigateDown,
|
|
809
1119
|
onClose,
|
|
810
|
-
|
|
1120
|
+
onFocusEditor,
|
|
1121
|
+
renderItem,
|
|
1122
|
+
renderEmpty,
|
|
1123
|
+
renderLoading,
|
|
1124
|
+
renderGroupHeader,
|
|
1125
|
+
listboxId
|
|
811
1126
|
}) {
|
|
812
1127
|
const listRef = useRef3(null);
|
|
813
1128
|
const searchInputRef = useRef3(null);
|
|
@@ -815,24 +1130,30 @@ function SuggestionList({
|
|
|
815
1130
|
const [nestedQuery, setNestedQuery] = useState2("");
|
|
816
1131
|
const breadcrumbKey = breadcrumbs.map((b) => b.id).join("/");
|
|
817
1132
|
const prevBreadcrumbKey = useRef3(breadcrumbKey);
|
|
818
|
-
|
|
1133
|
+
useEffect3(() => {
|
|
819
1134
|
if (prevBreadcrumbKey.current !== breadcrumbKey) {
|
|
820
1135
|
setNestedQuery("");
|
|
821
1136
|
prevBreadcrumbKey.current = breadcrumbKey;
|
|
822
1137
|
}
|
|
823
1138
|
}, [breadcrumbKey]);
|
|
824
|
-
|
|
825
|
-
|
|
1139
|
+
const prevBreadcrumbsLen = useRef3(breadcrumbs.length);
|
|
1140
|
+
useEffect3(() => {
|
|
1141
|
+
if (prevBreadcrumbsLen.current > 0 && breadcrumbs.length === 0) {
|
|
1142
|
+
onFocusEditor?.();
|
|
1143
|
+
} else if (breadcrumbs.length > 0 && searchInputRef.current) {
|
|
826
1144
|
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
827
1145
|
}
|
|
828
|
-
|
|
829
|
-
|
|
1146
|
+
prevBreadcrumbsLen.current = breadcrumbs.length;
|
|
1147
|
+
}, [breadcrumbKey, breadcrumbs.length, onFocusEditor]);
|
|
1148
|
+
useEffect3(() => {
|
|
830
1149
|
if (!listRef.current) return;
|
|
831
1150
|
const active = listRef.current.querySelector('[aria-selected="true"]');
|
|
832
1151
|
active?.scrollIntoView({ block: "nearest" });
|
|
833
1152
|
}, [activeIndex]);
|
|
834
|
-
const style = usePopoverPosition(clientRect);
|
|
835
|
-
|
|
1153
|
+
const { style, position } = usePopoverPosition(clientRect);
|
|
1154
|
+
const activeQuery = breadcrumbs.length > 0 ? nestedQuery : query;
|
|
1155
|
+
const showEmpty = !loading && items.length === 0 && activeQuery.trim().length > 0;
|
|
1156
|
+
if (items.length === 0 && !loading && !showEmpty) return null;
|
|
836
1157
|
const handleSearchKeyDown = (e) => {
|
|
837
1158
|
switch (e.key) {
|
|
838
1159
|
case "ArrowDown":
|
|
@@ -852,8 +1173,23 @@ function SuggestionList({
|
|
|
852
1173
|
}
|
|
853
1174
|
case "Escape":
|
|
854
1175
|
e.preventDefault();
|
|
1176
|
+
onFocusEditor?.();
|
|
855
1177
|
onClose?.();
|
|
856
1178
|
break;
|
|
1179
|
+
case "ArrowLeft":
|
|
1180
|
+
if (nestedQuery === "" || e.currentTarget.selectionStart === 0) {
|
|
1181
|
+
e.preventDefault();
|
|
1182
|
+
onGoBack();
|
|
1183
|
+
}
|
|
1184
|
+
break;
|
|
1185
|
+
case "ArrowRight": {
|
|
1186
|
+
const item = items[activeIndex];
|
|
1187
|
+
if (item?.hasChildren) {
|
|
1188
|
+
e.preventDefault();
|
|
1189
|
+
onSelect(item);
|
|
1190
|
+
}
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
857
1193
|
case "Backspace":
|
|
858
1194
|
if (nestedQuery === "") {
|
|
859
1195
|
e.preventDefault();
|
|
@@ -862,11 +1198,13 @@ function SuggestionList({
|
|
|
862
1198
|
break;
|
|
863
1199
|
}
|
|
864
1200
|
};
|
|
1201
|
+
const hasGroups = items.some((item) => item.group);
|
|
865
1202
|
return /* @__PURE__ */ jsxs(
|
|
866
1203
|
"div",
|
|
867
1204
|
{
|
|
868
1205
|
"data-suggestions": "",
|
|
869
1206
|
"data-trigger": trigger,
|
|
1207
|
+
"data-suggestions-position": position,
|
|
870
1208
|
style,
|
|
871
1209
|
ref: listRef,
|
|
872
1210
|
children: [
|
|
@@ -904,28 +1242,76 @@ function SuggestionList({
|
|
|
904
1242
|
spellCheck: false
|
|
905
1243
|
}
|
|
906
1244
|
) }),
|
|
907
|
-
loading && /* @__PURE__ */ jsx("div", { "data-suggestion-loading": "", children: "Loading..." }),
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
item.id
|
|
923
|
-
);
|
|
924
|
-
}) })
|
|
1245
|
+
loading && /* @__PURE__ */ jsx("div", { "data-suggestion-loading": "", children: renderLoading ? renderLoading() : "Loading..." }),
|
|
1246
|
+
showEmpty && /* @__PURE__ */ jsx("div", { "data-suggestion-empty": "", children: renderEmpty ? renderEmpty(activeQuery) : "No results" }),
|
|
1247
|
+
!loading && items.length > 0 && /* @__PURE__ */ jsx("div", { ...listboxAttrs(listboxId, `${trigger ?? ""} suggestions`), children: hasGroups ? renderGroupedItems(items, activeIndex, depth, onSelect, onHover, renderItem, renderGroupHeader) : items.map((item, index) => /* @__PURE__ */ jsx(
|
|
1248
|
+
SuggestionItem,
|
|
1249
|
+
{
|
|
1250
|
+
item,
|
|
1251
|
+
index,
|
|
1252
|
+
isActive: index === activeIndex,
|
|
1253
|
+
depth,
|
|
1254
|
+
onSelect,
|
|
1255
|
+
onHover,
|
|
1256
|
+
renderItem
|
|
1257
|
+
},
|
|
1258
|
+
item.id
|
|
1259
|
+
)) })
|
|
925
1260
|
]
|
|
926
1261
|
}
|
|
927
1262
|
);
|
|
928
1263
|
}
|
|
1264
|
+
function renderGroupedItems(items, activeIndex, depth, onSelect, onHover, renderItem, renderGroupHeader) {
|
|
1265
|
+
const elements = [];
|
|
1266
|
+
let lastGroup;
|
|
1267
|
+
items.forEach((item, index) => {
|
|
1268
|
+
if (item.group && item.group !== lastGroup) {
|
|
1269
|
+
lastGroup = item.group;
|
|
1270
|
+
elements.push(
|
|
1271
|
+
/* @__PURE__ */ jsx("div", { "data-suggestion-group-header": "", children: renderGroupHeader ? renderGroupHeader(item.group) : item.group }, `group-${item.group}`)
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
elements.push(
|
|
1275
|
+
/* @__PURE__ */ jsx(
|
|
1276
|
+
SuggestionItem,
|
|
1277
|
+
{
|
|
1278
|
+
item,
|
|
1279
|
+
index,
|
|
1280
|
+
isActive: index === activeIndex,
|
|
1281
|
+
depth,
|
|
1282
|
+
onSelect,
|
|
1283
|
+
onHover,
|
|
1284
|
+
renderItem
|
|
1285
|
+
},
|
|
1286
|
+
item.id
|
|
1287
|
+
)
|
|
1288
|
+
);
|
|
1289
|
+
});
|
|
1290
|
+
return elements;
|
|
1291
|
+
}
|
|
1292
|
+
function SuggestionItem({
|
|
1293
|
+
item,
|
|
1294
|
+
index,
|
|
1295
|
+
isActive,
|
|
1296
|
+
depth,
|
|
1297
|
+
onSelect,
|
|
1298
|
+
onHover,
|
|
1299
|
+
renderItem
|
|
1300
|
+
}) {
|
|
1301
|
+
const itemId = `mention-option-${item.id}`;
|
|
1302
|
+
return /* @__PURE__ */ jsx(
|
|
1303
|
+
"div",
|
|
1304
|
+
{
|
|
1305
|
+
...optionAttrs(itemId, isActive, index),
|
|
1306
|
+
"data-suggestion-item": "",
|
|
1307
|
+
"data-suggestion-item-active": isActive ? "" : void 0,
|
|
1308
|
+
"data-has-children": item.hasChildren ? "" : void 0,
|
|
1309
|
+
onMouseEnter: () => onHover(index),
|
|
1310
|
+
onClick: () => onSelect(item),
|
|
1311
|
+
children: renderItem ? renderItem(item, depth) : /* @__PURE__ */ jsx(DefaultSuggestionItem, { item })
|
|
1312
|
+
}
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
929
1315
|
function DefaultSuggestionItem({ item }) {
|
|
930
1316
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
931
1317
|
item.icon && /* @__PURE__ */ jsx("span", { "data-suggestion-item-icon": "", children: item.icon }),
|
|
@@ -934,25 +1320,48 @@ function DefaultSuggestionItem({ item }) {
|
|
|
934
1320
|
item.hasChildren && /* @__PURE__ */ jsx("span", { "data-suggestion-item-chevron": "", "aria-hidden": "true", children: "\u203A" })
|
|
935
1321
|
] });
|
|
936
1322
|
}
|
|
1323
|
+
var POPOVER_HEIGHT_ESTIMATE = 280;
|
|
1324
|
+
var POPOVER_WIDTH_ESTIMATE = 360;
|
|
937
1325
|
function usePopoverPosition(clientRect) {
|
|
938
1326
|
if (!clientRect) {
|
|
939
|
-
return { display: "none" };
|
|
1327
|
+
return { style: { display: "none" }, position: "below" };
|
|
940
1328
|
}
|
|
941
1329
|
const rect = clientRect();
|
|
942
1330
|
if (!rect) {
|
|
943
|
-
return { display: "none" };
|
|
1331
|
+
return { style: { display: "none" }, position: "below" };
|
|
1332
|
+
}
|
|
1333
|
+
const viewportH = typeof window !== "undefined" ? window.innerHeight : 800;
|
|
1334
|
+
const viewportW = typeof window !== "undefined" ? window.innerWidth : 1200;
|
|
1335
|
+
const spaceBelow = viewportH - rect.bottom;
|
|
1336
|
+
const shouldFlip = spaceBelow < POPOVER_HEIGHT_ESTIMATE && rect.top > spaceBelow;
|
|
1337
|
+
let left = rect.left;
|
|
1338
|
+
if (left + POPOVER_WIDTH_ESTIMATE > viewportW) {
|
|
1339
|
+
left = Math.max(0, viewportW - POPOVER_WIDTH_ESTIMATE);
|
|
1340
|
+
}
|
|
1341
|
+
if (shouldFlip) {
|
|
1342
|
+
return {
|
|
1343
|
+
style: {
|
|
1344
|
+
position: "fixed",
|
|
1345
|
+
left: `${left}px`,
|
|
1346
|
+
bottom: `${viewportH - rect.top + 4}px`,
|
|
1347
|
+
zIndex: 50
|
|
1348
|
+
},
|
|
1349
|
+
position: "above"
|
|
1350
|
+
};
|
|
944
1351
|
}
|
|
945
1352
|
return {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1353
|
+
style: {
|
|
1354
|
+
position: "fixed",
|
|
1355
|
+
left: `${left}px`,
|
|
1356
|
+
top: `${rect.bottom + 4}px`,
|
|
1357
|
+
zIndex: 50
|
|
1358
|
+
},
|
|
1359
|
+
position: "below"
|
|
950
1360
|
};
|
|
951
1361
|
}
|
|
952
1362
|
|
|
953
1363
|
// src/components/MentionsInput.tsx
|
|
954
1364
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
955
|
-
var LISTBOX_ID2 = "mentions-suggestion-listbox";
|
|
956
1365
|
var MentionsInput = forwardRef(
|
|
957
1366
|
function MentionsInput2({
|
|
958
1367
|
value,
|
|
@@ -966,10 +1375,29 @@ var MentionsInput = forwardRef(
|
|
|
966
1375
|
clearOnSubmit = true,
|
|
967
1376
|
maxLength,
|
|
968
1377
|
renderItem,
|
|
969
|
-
renderChip
|
|
1378
|
+
renderChip,
|
|
1379
|
+
renderEmpty,
|
|
1380
|
+
renderLoading,
|
|
1381
|
+
renderGroupHeader,
|
|
1382
|
+
onFocus,
|
|
1383
|
+
onBlur,
|
|
1384
|
+
onMentionAdd,
|
|
1385
|
+
onMentionRemove,
|
|
1386
|
+
onMentionClick,
|
|
1387
|
+
onMentionHover,
|
|
1388
|
+
minHeight,
|
|
1389
|
+
maxHeight,
|
|
1390
|
+
submitKey = "enter",
|
|
1391
|
+
allowTrigger,
|
|
1392
|
+
validateMention,
|
|
1393
|
+
portalContainer
|
|
970
1394
|
}, ref) {
|
|
971
|
-
const
|
|
972
|
-
const
|
|
1395
|
+
const instanceId = useId();
|
|
1396
|
+
const listboxId = `mentions-listbox-${instanceId}`;
|
|
1397
|
+
const { uiState, actions, callbacksRef } = useSuggestion(providers, {
|
|
1398
|
+
onMentionAdd
|
|
1399
|
+
});
|
|
1400
|
+
const { editor, getOutput, clear, setContent, focus } = useMentionsEditor({
|
|
973
1401
|
providers,
|
|
974
1402
|
value,
|
|
975
1403
|
onChange,
|
|
@@ -978,46 +1406,70 @@ var MentionsInput = forwardRef(
|
|
|
978
1406
|
placeholder,
|
|
979
1407
|
autoFocus,
|
|
980
1408
|
editable: !disabled,
|
|
981
|
-
callbacksRef
|
|
1409
|
+
callbacksRef,
|
|
1410
|
+
onFocus,
|
|
1411
|
+
onBlur,
|
|
1412
|
+
submitKey,
|
|
1413
|
+
onMentionRemove,
|
|
1414
|
+
onMentionClick,
|
|
1415
|
+
onMentionHover,
|
|
1416
|
+
allowTrigger,
|
|
1417
|
+
validateMention
|
|
982
1418
|
});
|
|
983
1419
|
useImperativeHandle(
|
|
984
1420
|
ref,
|
|
985
|
-
() => ({ clear, setContent, focus }),
|
|
986
|
-
[clear, setContent, focus]
|
|
1421
|
+
() => ({ clear, setContent, focus, getOutput }),
|
|
1422
|
+
[clear, setContent, focus, getOutput]
|
|
987
1423
|
);
|
|
988
1424
|
const isExpanded = uiState.state !== "idle";
|
|
989
1425
|
const handleHover = useCallback3((index) => {
|
|
990
1426
|
void index;
|
|
991
1427
|
}, []);
|
|
1428
|
+
const handleFocusEditor = useCallback3(() => {
|
|
1429
|
+
editor?.commands.focus();
|
|
1430
|
+
}, [editor]);
|
|
1431
|
+
const editorStyle = {};
|
|
1432
|
+
if (minHeight != null) editorStyle.minHeight = `${minHeight}px`;
|
|
1433
|
+
if (maxHeight != null) {
|
|
1434
|
+
editorStyle.maxHeight = `${maxHeight}px`;
|
|
1435
|
+
editorStyle.overflowY = "auto";
|
|
1436
|
+
}
|
|
1437
|
+
const suggestionList = isExpanded ? /* @__PURE__ */ jsx2(
|
|
1438
|
+
SuggestionList,
|
|
1439
|
+
{
|
|
1440
|
+
items: uiState.items,
|
|
1441
|
+
activeIndex: uiState.activeIndex,
|
|
1442
|
+
breadcrumbs: uiState.breadcrumbs,
|
|
1443
|
+
loading: uiState.loading,
|
|
1444
|
+
trigger: uiState.trigger,
|
|
1445
|
+
query: uiState.query,
|
|
1446
|
+
clientRect: uiState.clientRect,
|
|
1447
|
+
onSelect: (item) => actions.select(item),
|
|
1448
|
+
onHover: handleHover,
|
|
1449
|
+
onGoBack: actions.goBack,
|
|
1450
|
+
onSearchNested: actions.searchNested,
|
|
1451
|
+
onNavigateUp: actions.navigateUp,
|
|
1452
|
+
onNavigateDown: actions.navigateDown,
|
|
1453
|
+
onClose: actions.close,
|
|
1454
|
+
onFocusEditor: handleFocusEditor,
|
|
1455
|
+
renderItem,
|
|
1456
|
+
renderEmpty,
|
|
1457
|
+
renderLoading,
|
|
1458
|
+
renderGroupHeader,
|
|
1459
|
+
listboxId
|
|
1460
|
+
}
|
|
1461
|
+
) : null;
|
|
992
1462
|
return /* @__PURE__ */ jsxs2(
|
|
993
1463
|
"div",
|
|
994
1464
|
{
|
|
995
1465
|
className,
|
|
996
1466
|
"data-mentions-input": "",
|
|
997
1467
|
"data-disabled": disabled ? "" : void 0,
|
|
998
|
-
...comboboxAttrs(isExpanded,
|
|
1468
|
+
...comboboxAttrs(isExpanded, listboxId),
|
|
999
1469
|
"aria-activedescendant": isExpanded && uiState.items[uiState.activeIndex] ? `mention-option-${uiState.items[uiState.activeIndex].id}` : void 0,
|
|
1000
1470
|
children: [
|
|
1001
|
-
/* @__PURE__ */ jsx2(EditorContent, { editor }),
|
|
1002
|
-
|
|
1003
|
-
SuggestionList,
|
|
1004
|
-
{
|
|
1005
|
-
items: uiState.items,
|
|
1006
|
-
activeIndex: uiState.activeIndex,
|
|
1007
|
-
breadcrumbs: uiState.breadcrumbs,
|
|
1008
|
-
loading: uiState.loading,
|
|
1009
|
-
trigger: uiState.trigger,
|
|
1010
|
-
clientRect: uiState.clientRect,
|
|
1011
|
-
onSelect: (item) => actions.select(item),
|
|
1012
|
-
onHover: handleHover,
|
|
1013
|
-
onGoBack: actions.goBack,
|
|
1014
|
-
onSearchNested: actions.searchNested,
|
|
1015
|
-
onNavigateUp: actions.navigateUp,
|
|
1016
|
-
onNavigateDown: actions.navigateDown,
|
|
1017
|
-
onClose: actions.close,
|
|
1018
|
-
renderItem
|
|
1019
|
-
}
|
|
1020
|
-
)
|
|
1471
|
+
/* @__PURE__ */ jsx2("div", { style: editorStyle, children: /* @__PURE__ */ jsx2(EditorContent, { editor }) }),
|
|
1472
|
+
portalContainer ? suggestionList && createPortal(suggestionList, portalContainer) : suggestionList
|
|
1021
1473
|
]
|
|
1022
1474
|
}
|
|
1023
1475
|
);
|