@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.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) {
|
|
155
226
|
return import_core2.Extension.create({
|
|
156
227
|
name: "mentionSuggestion",
|
|
157
228
|
priority: 200,
|
|
@@ -229,6 +300,20 @@ function createSuggestionExtension(triggers, callbacksRef) {
|
|
|
229
300
|
const cursorPos = $pos.pos;
|
|
230
301
|
const match = detectTrigger(blockText, cursorPos, blockStart, triggers);
|
|
231
302
|
if (match) {
|
|
303
|
+
if (allowTriggerRef?.current) {
|
|
304
|
+
const allowed = allowTriggerRef.current(match.trigger, {
|
|
305
|
+
textBefore: match.textBefore
|
|
306
|
+
});
|
|
307
|
+
if (!allowed) {
|
|
308
|
+
if (active) {
|
|
309
|
+
active = false;
|
|
310
|
+
lastQuery = null;
|
|
311
|
+
lastTrigger = null;
|
|
312
|
+
callbacksRef.current.onExit();
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
232
317
|
const range = { from: match.from, to: match.to };
|
|
233
318
|
const props = {
|
|
234
319
|
query: match.query,
|
|
@@ -286,11 +371,12 @@ function serializeParagraph(node) {
|
|
|
286
371
|
if (!node.content) return "";
|
|
287
372
|
return node.content.map((child) => {
|
|
288
373
|
if (child.type === "mention") {
|
|
289
|
-
const { id, label, rootLabel } = child.attrs ?? {};
|
|
374
|
+
const { id, label, entityType, rootLabel } = child.attrs ?? {};
|
|
375
|
+
const idPart = entityType && entityType !== "unknown" ? `${entityType}:${id}` : id;
|
|
290
376
|
if (rootLabel != null && rootLabel !== "") {
|
|
291
|
-
return `@${rootLabel}[${label}](${
|
|
377
|
+
return `@${rootLabel}[${label}](${idPart})`;
|
|
292
378
|
}
|
|
293
|
-
return `@[${label}](${
|
|
379
|
+
return `@[${label}](${idPart})`;
|
|
294
380
|
}
|
|
295
381
|
return child.text ?? "";
|
|
296
382
|
}).join("");
|
|
@@ -335,7 +421,7 @@ function extractParagraphText(node) {
|
|
|
335
421
|
}
|
|
336
422
|
|
|
337
423
|
// src/core/markdownParser.ts
|
|
338
|
-
var MENTION_RE = /@(
|
|
424
|
+
var MENTION_RE = /@([^\[@]*)\[([^\]]+)\]\((?:([^:)]+):)?([^)]+)\)/g;
|
|
339
425
|
function parseFromMarkdown(markdown) {
|
|
340
426
|
const lines = markdown.split("\n");
|
|
341
427
|
const content = lines.map((line) => {
|
|
@@ -351,7 +437,7 @@ function parseLine(line) {
|
|
|
351
437
|
let match;
|
|
352
438
|
while ((match = MENTION_RE.exec(line)) !== null) {
|
|
353
439
|
const fullMatch = match[0];
|
|
354
|
-
const rootLabel = match[1]
|
|
440
|
+
const rootLabel = match[1] || null;
|
|
355
441
|
const label = match[2];
|
|
356
442
|
const entityType = match[3] ?? "unknown";
|
|
357
443
|
const id = match[4];
|
|
@@ -397,27 +483,48 @@ function buildOutput(editor) {
|
|
|
397
483
|
plainText: extractPlainText(json)
|
|
398
484
|
};
|
|
399
485
|
}
|
|
400
|
-
function
|
|
486
|
+
function collectMentionTokens(doc) {
|
|
487
|
+
const tokens = [];
|
|
488
|
+
function walk(node) {
|
|
489
|
+
if (node.type === "mention" && node.attrs) {
|
|
490
|
+
tokens.push({
|
|
491
|
+
id: node.attrs.id,
|
|
492
|
+
type: node.attrs.entityType ?? node.attrs.type,
|
|
493
|
+
label: node.attrs.label
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
if (node.content) {
|
|
497
|
+
for (const child of node.content) walk(child);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
walk(doc);
|
|
501
|
+
return tokens;
|
|
502
|
+
}
|
|
503
|
+
function createSubmitExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef) {
|
|
401
504
|
return import_core3.Extension.create({
|
|
402
505
|
name: "submitShortcut",
|
|
403
506
|
priority: 150,
|
|
404
507
|
addKeyboardShortcuts() {
|
|
405
508
|
return {
|
|
406
509
|
"Mod-Enter": () => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (
|
|
410
|
-
this.editor
|
|
510
|
+
const key = submitKeyRef.current;
|
|
511
|
+
if (key === "mod+enter" || key === "enter") {
|
|
512
|
+
if (onSubmitRef.current) {
|
|
513
|
+
onSubmitRef.current(buildOutput(this.editor));
|
|
514
|
+
if (clearOnSubmitRef.current) {
|
|
515
|
+
this.editor.commands.clearContent(true);
|
|
516
|
+
}
|
|
411
517
|
}
|
|
518
|
+
return true;
|
|
412
519
|
}
|
|
413
|
-
return
|
|
520
|
+
return false;
|
|
414
521
|
}
|
|
415
522
|
};
|
|
416
523
|
}
|
|
417
524
|
});
|
|
418
525
|
}
|
|
419
526
|
var enterSubmitPluginKey = new import_state2.PluginKey("enterSubmit");
|
|
420
|
-
function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
527
|
+
function createEnterExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef) {
|
|
421
528
|
return import_core3.Extension.create({
|
|
422
529
|
name: "enterSubmit",
|
|
423
530
|
priority: 150,
|
|
@@ -429,6 +536,11 @@ function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
429
536
|
props: {
|
|
430
537
|
handleKeyDown(_view, event) {
|
|
431
538
|
if (event.key !== "Enter") return false;
|
|
539
|
+
const key = submitKeyRef.current;
|
|
540
|
+
if (key === "none") return false;
|
|
541
|
+
if (key === "mod+enter") {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
432
544
|
if (event.shiftKey) {
|
|
433
545
|
editor.commands.splitBlock();
|
|
434
546
|
return true;
|
|
@@ -448,6 +560,33 @@ function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
448
560
|
}
|
|
449
561
|
});
|
|
450
562
|
}
|
|
563
|
+
var mentionRemovePluginKey = new import_state2.PluginKey("mentionRemove");
|
|
564
|
+
function createMentionRemoveExtension(onMentionRemoveRef) {
|
|
565
|
+
return import_core3.Extension.create({
|
|
566
|
+
name: "mentionRemoveDetector",
|
|
567
|
+
priority: 100,
|
|
568
|
+
addProseMirrorPlugins() {
|
|
569
|
+
return [
|
|
570
|
+
new import_state2.Plugin({
|
|
571
|
+
key: mentionRemovePluginKey,
|
|
572
|
+
appendTransaction(transactions, oldState, newState) {
|
|
573
|
+
if (!onMentionRemoveRef.current) return null;
|
|
574
|
+
const oldMentions = collectMentionTokens(oldState.doc.toJSON());
|
|
575
|
+
const newMentions = collectMentionTokens(newState.doc.toJSON());
|
|
576
|
+
if (oldMentions.length <= newMentions.length) return null;
|
|
577
|
+
const newIds = new Set(newMentions.map((m) => m.id));
|
|
578
|
+
for (const m of oldMentions) {
|
|
579
|
+
if (!newIds.has(m.id)) {
|
|
580
|
+
onMentionRemoveRef.current(m);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
];
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
|
451
590
|
function useMentionsEditor({
|
|
452
591
|
providers,
|
|
453
592
|
value,
|
|
@@ -457,7 +596,15 @@ function useMentionsEditor({
|
|
|
457
596
|
placeholder,
|
|
458
597
|
autoFocus = false,
|
|
459
598
|
editable = true,
|
|
460
|
-
callbacksRef
|
|
599
|
+
callbacksRef,
|
|
600
|
+
onFocus,
|
|
601
|
+
onBlur,
|
|
602
|
+
submitKey = "enter",
|
|
603
|
+
onMentionRemove,
|
|
604
|
+
onMentionClick,
|
|
605
|
+
onMentionHover,
|
|
606
|
+
allowTrigger,
|
|
607
|
+
validateMention
|
|
461
608
|
}) {
|
|
462
609
|
const onChangeRef = (0, import_react.useRef)(onChange);
|
|
463
610
|
onChangeRef.current = onChange;
|
|
@@ -465,6 +612,23 @@ function useMentionsEditor({
|
|
|
465
612
|
onSubmitRef.current = onSubmit;
|
|
466
613
|
const clearOnSubmitRef = (0, import_react.useRef)(clearOnSubmit);
|
|
467
614
|
clearOnSubmitRef.current = clearOnSubmit;
|
|
615
|
+
const onFocusRef = (0, import_react.useRef)(onFocus);
|
|
616
|
+
onFocusRef.current = onFocus;
|
|
617
|
+
const onBlurRef = (0, import_react.useRef)(onBlur);
|
|
618
|
+
onBlurRef.current = onBlur;
|
|
619
|
+
const submitKeyRef = (0, import_react.useRef)(submitKey);
|
|
620
|
+
submitKeyRef.current = submitKey;
|
|
621
|
+
const onMentionRemoveRef = (0, import_react.useRef)(onMentionRemove);
|
|
622
|
+
onMentionRemoveRef.current = onMentionRemove;
|
|
623
|
+
const onMentionClickRef = (0, import_react.useRef)(onMentionClick);
|
|
624
|
+
onMentionClickRef.current = onMentionClick;
|
|
625
|
+
const onMentionHoverRef = (0, import_react.useRef)(onMentionHover);
|
|
626
|
+
onMentionHoverRef.current = onMentionHover;
|
|
627
|
+
const allowTriggerRef = (0, import_react.useRef)(allowTrigger);
|
|
628
|
+
allowTriggerRef.current = allowTrigger;
|
|
629
|
+
const validateMentionRef = (0, import_react.useRef)(validateMention);
|
|
630
|
+
validateMentionRef.current = validateMention;
|
|
631
|
+
const internalMarkdownRef = (0, import_react.useRef)(null);
|
|
468
632
|
const initialContent = (0, import_react.useMemo)(() => {
|
|
469
633
|
if (!value) return void 0;
|
|
470
634
|
return parseFromMarkdown(value);
|
|
@@ -476,16 +640,27 @@ function useMentionsEditor({
|
|
|
476
640
|
[triggersKey]
|
|
477
641
|
);
|
|
478
642
|
const suggestionExtension = (0, import_react.useMemo)(
|
|
479
|
-
() => createSuggestionExtension(triggers, callbacksRef),
|
|
643
|
+
() => createSuggestionExtension(triggers, callbacksRef, allowTriggerRef),
|
|
480
644
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
481
645
|
[triggersKey]
|
|
482
646
|
);
|
|
483
647
|
const submitExt = (0, import_react.useMemo)(
|
|
484
|
-
() => createSubmitExtension(onSubmitRef, clearOnSubmitRef),
|
|
648
|
+
() => createSubmitExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef),
|
|
485
649
|
[]
|
|
486
650
|
);
|
|
487
651
|
const enterExt = (0, import_react.useMemo)(
|
|
488
|
-
() => createEnterExtension(onSubmitRef, clearOnSubmitRef),
|
|
652
|
+
() => createEnterExtension(onSubmitRef, clearOnSubmitRef, submitKeyRef),
|
|
653
|
+
[]
|
|
654
|
+
);
|
|
655
|
+
const mentionRemoveExt = (0, import_react.useMemo)(
|
|
656
|
+
() => createMentionRemoveExtension(onMentionRemoveRef),
|
|
657
|
+
[]
|
|
658
|
+
);
|
|
659
|
+
const mentionNodeExt = (0, import_react.useMemo)(
|
|
660
|
+
() => MentionNode.configure({
|
|
661
|
+
onClickRef: onMentionClickRef,
|
|
662
|
+
onHoverRef: onMentionHoverRef
|
|
663
|
+
}),
|
|
489
664
|
[]
|
|
490
665
|
);
|
|
491
666
|
const editor = (0, import_react2.useEditor)({
|
|
@@ -504,10 +679,11 @@ function useMentionsEditor({
|
|
|
504
679
|
placeholder: ({ editor: editor2 }) => editor2.isEmpty ? placeholder ?? "Type a message..." : "",
|
|
505
680
|
showOnlyCurrent: true
|
|
506
681
|
}),
|
|
507
|
-
|
|
682
|
+
mentionNodeExt,
|
|
508
683
|
suggestionExtension,
|
|
509
684
|
submitExt,
|
|
510
|
-
enterExt
|
|
685
|
+
enterExt,
|
|
686
|
+
mentionRemoveExt
|
|
511
687
|
],
|
|
512
688
|
content: initialContent,
|
|
513
689
|
autofocus: autoFocus ? "end" : false,
|
|
@@ -518,7 +694,15 @@ function useMentionsEditor({
|
|
|
518
694
|
}
|
|
519
695
|
},
|
|
520
696
|
onUpdate: ({ editor: editor2 }) => {
|
|
521
|
-
|
|
697
|
+
const output = buildOutput(editor2);
|
|
698
|
+
internalMarkdownRef.current = output.markdown;
|
|
699
|
+
onChangeRef.current?.(output);
|
|
700
|
+
},
|
|
701
|
+
onFocus: () => {
|
|
702
|
+
onFocusRef.current?.();
|
|
703
|
+
},
|
|
704
|
+
onBlur: () => {
|
|
705
|
+
onBlurRef.current?.();
|
|
522
706
|
}
|
|
523
707
|
});
|
|
524
708
|
(0, import_react.useEffect)(() => {
|
|
@@ -526,6 +710,36 @@ function useMentionsEditor({
|
|
|
526
710
|
editor.setEditable(editable);
|
|
527
711
|
}
|
|
528
712
|
}, [editor, editable]);
|
|
713
|
+
(0, import_react.useEffect)(() => {
|
|
714
|
+
if (!editor || value === void 0) return;
|
|
715
|
+
if (value === internalMarkdownRef.current) return;
|
|
716
|
+
const doc = parseFromMarkdown(value);
|
|
717
|
+
editor.commands.setContent(doc);
|
|
718
|
+
internalMarkdownRef.current = value;
|
|
719
|
+
}, [editor, value]);
|
|
720
|
+
(0, import_react.useEffect)(() => {
|
|
721
|
+
if (!editor || !validateMention) return;
|
|
722
|
+
const runValidation = async () => {
|
|
723
|
+
const doc = editor.getJSON();
|
|
724
|
+
const tokens = collectMentionTokens(doc);
|
|
725
|
+
const invalidIds = /* @__PURE__ */ new Set();
|
|
726
|
+
await Promise.all(
|
|
727
|
+
tokens.map(async (token) => {
|
|
728
|
+
const valid = await validateMention(token);
|
|
729
|
+
if (!valid) invalidIds.add(token.id);
|
|
730
|
+
})
|
|
731
|
+
);
|
|
732
|
+
editor.view.dom.querySelectorAll("[data-mention]").forEach((el) => {
|
|
733
|
+
const id = el.getAttribute("data-id");
|
|
734
|
+
if (id && invalidIds.has(id)) {
|
|
735
|
+
el.setAttribute("data-mention-invalid", "");
|
|
736
|
+
} else {
|
|
737
|
+
el.removeAttribute("data-mention-invalid");
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
};
|
|
741
|
+
runValidation();
|
|
742
|
+
}, [editor, validateMention]);
|
|
529
743
|
const clear = (0, import_react.useCallback)(() => {
|
|
530
744
|
editor?.commands.clearContent(true);
|
|
531
745
|
}, [editor]);
|
|
@@ -534,6 +748,7 @@ function useMentionsEditor({
|
|
|
534
748
|
if (!editor) return;
|
|
535
749
|
const doc = parseFromMarkdown(markdown);
|
|
536
750
|
editor.commands.setContent(doc);
|
|
751
|
+
internalMarkdownRef.current = markdown;
|
|
537
752
|
},
|
|
538
753
|
[editor]
|
|
539
754
|
);
|
|
@@ -549,6 +764,27 @@ function useMentionsEditor({
|
|
|
549
764
|
|
|
550
765
|
// src/hooks/useSuggestion.ts
|
|
551
766
|
var import_react3 = require("react");
|
|
767
|
+
|
|
768
|
+
// src/utils/debounce.ts
|
|
769
|
+
function debounce(fn, ms) {
|
|
770
|
+
let timer = null;
|
|
771
|
+
const debounced = ((...args) => {
|
|
772
|
+
if (timer != null) clearTimeout(timer);
|
|
773
|
+
timer = setTimeout(() => {
|
|
774
|
+
timer = null;
|
|
775
|
+
fn(...args);
|
|
776
|
+
}, ms);
|
|
777
|
+
});
|
|
778
|
+
debounced.cancel = () => {
|
|
779
|
+
if (timer != null) {
|
|
780
|
+
clearTimeout(timer);
|
|
781
|
+
timer = null;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
return debounced;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/hooks/useSuggestion.ts
|
|
552
788
|
var IDLE_STATE = {
|
|
553
789
|
state: "idle",
|
|
554
790
|
items: [],
|
|
@@ -559,16 +795,24 @@ var IDLE_STATE = {
|
|
|
559
795
|
trigger: null,
|
|
560
796
|
query: ""
|
|
561
797
|
};
|
|
562
|
-
function useSuggestion(providers) {
|
|
798
|
+
function useSuggestion(providers, options = {}) {
|
|
563
799
|
const [uiState, setUIState] = (0, import_react3.useState)(IDLE_STATE);
|
|
564
800
|
const stateRef = (0, import_react3.useRef)(uiState);
|
|
565
801
|
stateRef.current = uiState;
|
|
566
802
|
const providersRef = (0, import_react3.useRef)(providers);
|
|
567
803
|
providersRef.current = providers;
|
|
804
|
+
const onMentionAddRef = (0, import_react3.useRef)(options.onMentionAdd);
|
|
805
|
+
onMentionAddRef.current = options.onMentionAdd;
|
|
568
806
|
const commandRef = (0, import_react3.useRef)(
|
|
569
807
|
null
|
|
570
808
|
);
|
|
571
809
|
const providerRef = (0, import_react3.useRef)(null);
|
|
810
|
+
const debouncedFetchRef = (0, import_react3.useRef)(null);
|
|
811
|
+
(0, import_react3.useEffect)(() => {
|
|
812
|
+
return () => {
|
|
813
|
+
debouncedFetchRef.current?.cancel();
|
|
814
|
+
};
|
|
815
|
+
}, []);
|
|
572
816
|
const fetchItems = (0, import_react3.useCallback)(
|
|
573
817
|
async (provider, query, parent, useSearchAll) => {
|
|
574
818
|
setUIState((prev) => ({ ...prev, loading: true, state: "loading" }));
|
|
@@ -599,6 +843,23 @@ function useSuggestion(providers) {
|
|
|
599
843
|
},
|
|
600
844
|
[]
|
|
601
845
|
);
|
|
846
|
+
const scheduleFetch = (0, import_react3.useCallback)(
|
|
847
|
+
(provider, query, parent, useSearchAll) => {
|
|
848
|
+
debouncedFetchRef.current?.cancel();
|
|
849
|
+
const ms = provider.debounceMs;
|
|
850
|
+
if (ms && ms > 0) {
|
|
851
|
+
setUIState((prev) => ({ ...prev, loading: true }));
|
|
852
|
+
const debouncedFn = debounce(() => {
|
|
853
|
+
fetchItems(provider, query, parent, useSearchAll);
|
|
854
|
+
}, ms);
|
|
855
|
+
debouncedFetchRef.current = debouncedFn;
|
|
856
|
+
debouncedFn();
|
|
857
|
+
} else {
|
|
858
|
+
fetchItems(provider, query, parent, useSearchAll);
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
[fetchItems]
|
|
862
|
+
);
|
|
602
863
|
const onStart = (0, import_react3.useCallback)(
|
|
603
864
|
(props) => {
|
|
604
865
|
const provider = providersRef.current.find(
|
|
@@ -617,13 +878,31 @@ function useSuggestion(providers) {
|
|
|
617
878
|
trigger: props.trigger,
|
|
618
879
|
query: props.query
|
|
619
880
|
});
|
|
881
|
+
if (!props.query.trim() && provider.getRecentItems) {
|
|
882
|
+
provider.getRecentItems().then((recentItems) => {
|
|
883
|
+
const tagged = recentItems.map((item) => ({
|
|
884
|
+
...item,
|
|
885
|
+
group: item.group ?? "Recent"
|
|
886
|
+
}));
|
|
887
|
+
setUIState((prev) => ({
|
|
888
|
+
...prev,
|
|
889
|
+
items: tagged,
|
|
890
|
+
loading: false,
|
|
891
|
+
state: "showing",
|
|
892
|
+
activeIndex: 0
|
|
893
|
+
}));
|
|
894
|
+
}).catch(() => {
|
|
895
|
+
scheduleFetch(provider, props.query);
|
|
896
|
+
});
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
620
899
|
if (props.query.trim() && provider.searchAll) {
|
|
621
|
-
|
|
900
|
+
scheduleFetch(provider, props.query, void 0, true);
|
|
622
901
|
} else {
|
|
623
|
-
|
|
902
|
+
scheduleFetch(provider, props.query);
|
|
624
903
|
}
|
|
625
904
|
},
|
|
626
|
-
[
|
|
905
|
+
[scheduleFetch]
|
|
627
906
|
);
|
|
628
907
|
const onUpdate = (0, import_react3.useCallback)(
|
|
629
908
|
(props) => {
|
|
@@ -647,14 +926,31 @@ function useSuggestion(providers) {
|
|
|
647
926
|
}));
|
|
648
927
|
}
|
|
649
928
|
if (props.query.trim() && provider.searchAll) {
|
|
650
|
-
|
|
929
|
+
scheduleFetch(provider, props.query, void 0, true);
|
|
930
|
+
} else if (!props.query.trim() && provider.getRecentItems) {
|
|
931
|
+
provider.getRecentItems().then((recentItems) => {
|
|
932
|
+
const tagged = recentItems.map((item) => ({
|
|
933
|
+
...item,
|
|
934
|
+
group: item.group ?? "Recent"
|
|
935
|
+
}));
|
|
936
|
+
setUIState((prev) => ({
|
|
937
|
+
...prev,
|
|
938
|
+
items: tagged,
|
|
939
|
+
loading: false,
|
|
940
|
+
state: "showing",
|
|
941
|
+
activeIndex: 0
|
|
942
|
+
}));
|
|
943
|
+
}).catch(() => {
|
|
944
|
+
scheduleFetch(provider, props.query);
|
|
945
|
+
});
|
|
651
946
|
} else {
|
|
652
|
-
|
|
947
|
+
scheduleFetch(provider, props.query);
|
|
653
948
|
}
|
|
654
949
|
},
|
|
655
|
-
[
|
|
950
|
+
[scheduleFetch]
|
|
656
951
|
);
|
|
657
952
|
const onExit = (0, import_react3.useCallback)(() => {
|
|
953
|
+
debouncedFetchRef.current?.cancel();
|
|
658
954
|
providerRef.current = null;
|
|
659
955
|
commandRef.current = null;
|
|
660
956
|
setUIState(IDLE_STATE);
|
|
@@ -689,8 +985,8 @@ function useSuggestion(providers) {
|
|
|
689
985
|
fetchItems(provider, "", selected);
|
|
690
986
|
return;
|
|
691
987
|
}
|
|
988
|
+
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
692
989
|
if (commandRef.current) {
|
|
693
|
-
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
694
990
|
commandRef.current({
|
|
695
991
|
id: selected.id,
|
|
696
992
|
label: selected.label,
|
|
@@ -698,6 +994,11 @@ function useSuggestion(providers) {
|
|
|
698
994
|
rootLabel
|
|
699
995
|
});
|
|
700
996
|
}
|
|
997
|
+
onMentionAddRef.current?.({
|
|
998
|
+
id: selected.id,
|
|
999
|
+
type: selected.type,
|
|
1000
|
+
label: selected.label
|
|
1001
|
+
});
|
|
701
1002
|
},
|
|
702
1003
|
[fetchItems]
|
|
703
1004
|
);
|
|
@@ -722,6 +1023,7 @@ function useSuggestion(providers) {
|
|
|
722
1023
|
});
|
|
723
1024
|
}, [fetchItems]);
|
|
724
1025
|
const close = (0, import_react3.useCallback)(() => {
|
|
1026
|
+
debouncedFetchRef.current?.cancel();
|
|
725
1027
|
setUIState(IDLE_STATE);
|
|
726
1028
|
}, []);
|
|
727
1029
|
const searchNested = (0, import_react3.useCallback)(
|
|
@@ -731,10 +1033,10 @@ function useSuggestion(providers) {
|
|
|
731
1033
|
const current = stateRef.current;
|
|
732
1034
|
const parent = current.breadcrumbs[current.breadcrumbs.length - 1];
|
|
733
1035
|
if (parent) {
|
|
734
|
-
|
|
1036
|
+
scheduleFetch(provider, query, parent);
|
|
735
1037
|
}
|
|
736
1038
|
},
|
|
737
|
-
[
|
|
1039
|
+
[scheduleFetch]
|
|
738
1040
|
);
|
|
739
1041
|
const onKeyDown = (0, import_react3.useCallback)(
|
|
740
1042
|
({ event }) => {
|
|
@@ -757,6 +1059,14 @@ function useSuggestion(providers) {
|
|
|
757
1059
|
}
|
|
758
1060
|
return true;
|
|
759
1061
|
}
|
|
1062
|
+
case "Tab": {
|
|
1063
|
+
event.preventDefault();
|
|
1064
|
+
const selectedItem = current.items[current.activeIndex];
|
|
1065
|
+
if (selectedItem) {
|
|
1066
|
+
select(selectedItem);
|
|
1067
|
+
}
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
760
1070
|
case "ArrowRight": {
|
|
761
1071
|
const activeItem = current.items[current.activeIndex];
|
|
762
1072
|
if (activeItem?.hasChildren) {
|
|
@@ -831,13 +1141,13 @@ function optionAttrs(id, selected, index) {
|
|
|
831
1141
|
|
|
832
1142
|
// src/components/SuggestionList.tsx
|
|
833
1143
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
834
|
-
var LISTBOX_ID = "mentions-suggestion-listbox";
|
|
835
1144
|
function SuggestionList({
|
|
836
1145
|
items,
|
|
837
1146
|
activeIndex,
|
|
838
1147
|
breadcrumbs,
|
|
839
1148
|
loading,
|
|
840
1149
|
trigger,
|
|
1150
|
+
query,
|
|
841
1151
|
clientRect,
|
|
842
1152
|
onSelect,
|
|
843
1153
|
onHover,
|
|
@@ -846,7 +1156,12 @@ function SuggestionList({
|
|
|
846
1156
|
onNavigateUp,
|
|
847
1157
|
onNavigateDown,
|
|
848
1158
|
onClose,
|
|
849
|
-
|
|
1159
|
+
onFocusEditor,
|
|
1160
|
+
renderItem,
|
|
1161
|
+
renderEmpty,
|
|
1162
|
+
renderLoading,
|
|
1163
|
+
renderGroupHeader,
|
|
1164
|
+
listboxId
|
|
850
1165
|
}) {
|
|
851
1166
|
const listRef = (0, import_react4.useRef)(null);
|
|
852
1167
|
const searchInputRef = (0, import_react4.useRef)(null);
|
|
@@ -860,18 +1175,24 @@ function SuggestionList({
|
|
|
860
1175
|
prevBreadcrumbKey.current = breadcrumbKey;
|
|
861
1176
|
}
|
|
862
1177
|
}, [breadcrumbKey]);
|
|
1178
|
+
const prevBreadcrumbsLen = (0, import_react4.useRef)(breadcrumbs.length);
|
|
863
1179
|
(0, import_react4.useEffect)(() => {
|
|
864
|
-
if (
|
|
1180
|
+
if (prevBreadcrumbsLen.current > 0 && breadcrumbs.length === 0) {
|
|
1181
|
+
onFocusEditor?.();
|
|
1182
|
+
} else if (breadcrumbs.length > 0 && searchInputRef.current) {
|
|
865
1183
|
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
866
1184
|
}
|
|
867
|
-
|
|
1185
|
+
prevBreadcrumbsLen.current = breadcrumbs.length;
|
|
1186
|
+
}, [breadcrumbKey, breadcrumbs.length, onFocusEditor]);
|
|
868
1187
|
(0, import_react4.useEffect)(() => {
|
|
869
1188
|
if (!listRef.current) return;
|
|
870
1189
|
const active = listRef.current.querySelector('[aria-selected="true"]');
|
|
871
1190
|
active?.scrollIntoView({ block: "nearest" });
|
|
872
1191
|
}, [activeIndex]);
|
|
873
|
-
const style = usePopoverPosition(clientRect);
|
|
874
|
-
|
|
1192
|
+
const { style, position } = usePopoverPosition(clientRect);
|
|
1193
|
+
const activeQuery = breadcrumbs.length > 0 ? nestedQuery : query;
|
|
1194
|
+
const showEmpty = !loading && items.length === 0 && activeQuery.trim().length > 0;
|
|
1195
|
+
if (items.length === 0 && !loading && !showEmpty) return null;
|
|
875
1196
|
const handleSearchKeyDown = (e) => {
|
|
876
1197
|
switch (e.key) {
|
|
877
1198
|
case "ArrowDown":
|
|
@@ -891,8 +1212,23 @@ function SuggestionList({
|
|
|
891
1212
|
}
|
|
892
1213
|
case "Escape":
|
|
893
1214
|
e.preventDefault();
|
|
1215
|
+
onFocusEditor?.();
|
|
894
1216
|
onClose?.();
|
|
895
1217
|
break;
|
|
1218
|
+
case "ArrowLeft":
|
|
1219
|
+
if (nestedQuery === "" || e.currentTarget.selectionStart === 0) {
|
|
1220
|
+
e.preventDefault();
|
|
1221
|
+
onGoBack();
|
|
1222
|
+
}
|
|
1223
|
+
break;
|
|
1224
|
+
case "ArrowRight": {
|
|
1225
|
+
const item = items[activeIndex];
|
|
1226
|
+
if (item?.hasChildren) {
|
|
1227
|
+
e.preventDefault();
|
|
1228
|
+
onSelect(item);
|
|
1229
|
+
}
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
896
1232
|
case "Backspace":
|
|
897
1233
|
if (nestedQuery === "") {
|
|
898
1234
|
e.preventDefault();
|
|
@@ -901,11 +1237,13 @@ function SuggestionList({
|
|
|
901
1237
|
break;
|
|
902
1238
|
}
|
|
903
1239
|
};
|
|
1240
|
+
const hasGroups = items.some((item) => item.group);
|
|
904
1241
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
905
1242
|
"div",
|
|
906
1243
|
{
|
|
907
1244
|
"data-suggestions": "",
|
|
908
1245
|
"data-trigger": trigger,
|
|
1246
|
+
"data-suggestions-position": position,
|
|
909
1247
|
style,
|
|
910
1248
|
ref: listRef,
|
|
911
1249
|
children: [
|
|
@@ -943,28 +1281,76 @@ function SuggestionList({
|
|
|
943
1281
|
spellCheck: false
|
|
944
1282
|
}
|
|
945
1283
|
) }),
|
|
946
|
-
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-loading": "", children: "Loading..." }),
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
item.id
|
|
962
|
-
);
|
|
963
|
-
}) })
|
|
1284
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-loading": "", children: renderLoading ? renderLoading() : "Loading..." }),
|
|
1285
|
+
showEmpty && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-empty": "", children: renderEmpty ? renderEmpty(activeQuery) : "No results" }),
|
|
1286
|
+
!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)(
|
|
1287
|
+
SuggestionItem,
|
|
1288
|
+
{
|
|
1289
|
+
item,
|
|
1290
|
+
index,
|
|
1291
|
+
isActive: index === activeIndex,
|
|
1292
|
+
depth,
|
|
1293
|
+
onSelect,
|
|
1294
|
+
onHover,
|
|
1295
|
+
renderItem
|
|
1296
|
+
},
|
|
1297
|
+
item.id
|
|
1298
|
+
)) })
|
|
964
1299
|
]
|
|
965
1300
|
}
|
|
966
1301
|
);
|
|
967
1302
|
}
|
|
1303
|
+
function renderGroupedItems(items, activeIndex, depth, onSelect, onHover, renderItem, renderGroupHeader) {
|
|
1304
|
+
const elements = [];
|
|
1305
|
+
let lastGroup;
|
|
1306
|
+
items.forEach((item, index) => {
|
|
1307
|
+
if (item.group && item.group !== lastGroup) {
|
|
1308
|
+
lastGroup = item.group;
|
|
1309
|
+
elements.push(
|
|
1310
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-suggestion-group-header": "", children: renderGroupHeader ? renderGroupHeader(item.group) : item.group }, `group-${item.group}`)
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
elements.push(
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1315
|
+
SuggestionItem,
|
|
1316
|
+
{
|
|
1317
|
+
item,
|
|
1318
|
+
index,
|
|
1319
|
+
isActive: index === activeIndex,
|
|
1320
|
+
depth,
|
|
1321
|
+
onSelect,
|
|
1322
|
+
onHover,
|
|
1323
|
+
renderItem
|
|
1324
|
+
},
|
|
1325
|
+
item.id
|
|
1326
|
+
)
|
|
1327
|
+
);
|
|
1328
|
+
});
|
|
1329
|
+
return elements;
|
|
1330
|
+
}
|
|
1331
|
+
function SuggestionItem({
|
|
1332
|
+
item,
|
|
1333
|
+
index,
|
|
1334
|
+
isActive,
|
|
1335
|
+
depth,
|
|
1336
|
+
onSelect,
|
|
1337
|
+
onHover,
|
|
1338
|
+
renderItem
|
|
1339
|
+
}) {
|
|
1340
|
+
const itemId = `mention-option-${item.id}`;
|
|
1341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1342
|
+
"div",
|
|
1343
|
+
{
|
|
1344
|
+
...optionAttrs(itemId, isActive, index),
|
|
1345
|
+
"data-suggestion-item": "",
|
|
1346
|
+
"data-suggestion-item-active": isActive ? "" : void 0,
|
|
1347
|
+
"data-has-children": item.hasChildren ? "" : void 0,
|
|
1348
|
+
onMouseEnter: () => onHover(index),
|
|
1349
|
+
onClick: () => onSelect(item),
|
|
1350
|
+
children: renderItem ? renderItem(item, depth) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultSuggestionItem, { item })
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
968
1354
|
function DefaultSuggestionItem({ item }) {
|
|
969
1355
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
970
1356
|
item.icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-suggestion-item-icon": "", children: item.icon }),
|
|
@@ -973,25 +1359,48 @@ function DefaultSuggestionItem({ item }) {
|
|
|
973
1359
|
item.hasChildren && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-suggestion-item-chevron": "", "aria-hidden": "true", children: "\u203A" })
|
|
974
1360
|
] });
|
|
975
1361
|
}
|
|
1362
|
+
var POPOVER_HEIGHT_ESTIMATE = 280;
|
|
1363
|
+
var POPOVER_WIDTH_ESTIMATE = 360;
|
|
976
1364
|
function usePopoverPosition(clientRect) {
|
|
977
1365
|
if (!clientRect) {
|
|
978
|
-
return { display: "none" };
|
|
1366
|
+
return { style: { display: "none" }, position: "below" };
|
|
979
1367
|
}
|
|
980
1368
|
const rect = clientRect();
|
|
981
1369
|
if (!rect) {
|
|
982
|
-
return { display: "none" };
|
|
1370
|
+
return { style: { display: "none" }, position: "below" };
|
|
1371
|
+
}
|
|
1372
|
+
const viewportH = typeof window !== "undefined" ? window.innerHeight : 800;
|
|
1373
|
+
const viewportW = typeof window !== "undefined" ? window.innerWidth : 1200;
|
|
1374
|
+
const spaceBelow = viewportH - rect.bottom;
|
|
1375
|
+
const shouldFlip = spaceBelow < POPOVER_HEIGHT_ESTIMATE && rect.top > spaceBelow;
|
|
1376
|
+
let left = rect.left;
|
|
1377
|
+
if (left + POPOVER_WIDTH_ESTIMATE > viewportW) {
|
|
1378
|
+
left = Math.max(0, viewportW - POPOVER_WIDTH_ESTIMATE);
|
|
1379
|
+
}
|
|
1380
|
+
if (shouldFlip) {
|
|
1381
|
+
return {
|
|
1382
|
+
style: {
|
|
1383
|
+
position: "fixed",
|
|
1384
|
+
left: `${left}px`,
|
|
1385
|
+
bottom: `${viewportH - rect.top + 4}px`,
|
|
1386
|
+
zIndex: 50
|
|
1387
|
+
},
|
|
1388
|
+
position: "above"
|
|
1389
|
+
};
|
|
983
1390
|
}
|
|
984
1391
|
return {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1392
|
+
style: {
|
|
1393
|
+
position: "fixed",
|
|
1394
|
+
left: `${left}px`,
|
|
1395
|
+
top: `${rect.bottom + 4}px`,
|
|
1396
|
+
zIndex: 50
|
|
1397
|
+
},
|
|
1398
|
+
position: "below"
|
|
989
1399
|
};
|
|
990
1400
|
}
|
|
991
1401
|
|
|
992
1402
|
// src/components/MentionsInput.tsx
|
|
993
1403
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
994
|
-
var LISTBOX_ID2 = "mentions-suggestion-listbox";
|
|
995
1404
|
var MentionsInput = (0, import_react5.forwardRef)(
|
|
996
1405
|
function MentionsInput2({
|
|
997
1406
|
value,
|
|
@@ -1005,10 +1414,29 @@ var MentionsInput = (0, import_react5.forwardRef)(
|
|
|
1005
1414
|
clearOnSubmit = true,
|
|
1006
1415
|
maxLength,
|
|
1007
1416
|
renderItem,
|
|
1008
|
-
renderChip
|
|
1417
|
+
renderChip,
|
|
1418
|
+
renderEmpty,
|
|
1419
|
+
renderLoading,
|
|
1420
|
+
renderGroupHeader,
|
|
1421
|
+
onFocus,
|
|
1422
|
+
onBlur,
|
|
1423
|
+
onMentionAdd,
|
|
1424
|
+
onMentionRemove,
|
|
1425
|
+
onMentionClick,
|
|
1426
|
+
onMentionHover,
|
|
1427
|
+
minHeight,
|
|
1428
|
+
maxHeight,
|
|
1429
|
+
submitKey = "enter",
|
|
1430
|
+
allowTrigger,
|
|
1431
|
+
validateMention,
|
|
1432
|
+
portalContainer
|
|
1009
1433
|
}, ref) {
|
|
1010
|
-
const
|
|
1011
|
-
const
|
|
1434
|
+
const instanceId = (0, import_react5.useId)();
|
|
1435
|
+
const listboxId = `mentions-listbox-${instanceId}`;
|
|
1436
|
+
const { uiState, actions, callbacksRef } = useSuggestion(providers, {
|
|
1437
|
+
onMentionAdd
|
|
1438
|
+
});
|
|
1439
|
+
const { editor, getOutput, clear, setContent, focus } = useMentionsEditor({
|
|
1012
1440
|
providers,
|
|
1013
1441
|
value,
|
|
1014
1442
|
onChange,
|
|
@@ -1017,46 +1445,70 @@ var MentionsInput = (0, import_react5.forwardRef)(
|
|
|
1017
1445
|
placeholder,
|
|
1018
1446
|
autoFocus,
|
|
1019
1447
|
editable: !disabled,
|
|
1020
|
-
callbacksRef
|
|
1448
|
+
callbacksRef,
|
|
1449
|
+
onFocus,
|
|
1450
|
+
onBlur,
|
|
1451
|
+
submitKey,
|
|
1452
|
+
onMentionRemove,
|
|
1453
|
+
onMentionClick,
|
|
1454
|
+
onMentionHover,
|
|
1455
|
+
allowTrigger,
|
|
1456
|
+
validateMention
|
|
1021
1457
|
});
|
|
1022
1458
|
(0, import_react5.useImperativeHandle)(
|
|
1023
1459
|
ref,
|
|
1024
|
-
() => ({ clear, setContent, focus }),
|
|
1025
|
-
[clear, setContent, focus]
|
|
1460
|
+
() => ({ clear, setContent, focus, getOutput }),
|
|
1461
|
+
[clear, setContent, focus, getOutput]
|
|
1026
1462
|
);
|
|
1027
1463
|
const isExpanded = uiState.state !== "idle";
|
|
1028
1464
|
const handleHover = (0, import_react5.useCallback)((index) => {
|
|
1029
1465
|
void index;
|
|
1030
1466
|
}, []);
|
|
1467
|
+
const handleFocusEditor = (0, import_react5.useCallback)(() => {
|
|
1468
|
+
editor?.commands.focus();
|
|
1469
|
+
}, [editor]);
|
|
1470
|
+
const editorStyle = {};
|
|
1471
|
+
if (minHeight != null) editorStyle.minHeight = `${minHeight}px`;
|
|
1472
|
+
if (maxHeight != null) {
|
|
1473
|
+
editorStyle.maxHeight = `${maxHeight}px`;
|
|
1474
|
+
editorStyle.overflowY = "auto";
|
|
1475
|
+
}
|
|
1476
|
+
const suggestionList = isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1477
|
+
SuggestionList,
|
|
1478
|
+
{
|
|
1479
|
+
items: uiState.items,
|
|
1480
|
+
activeIndex: uiState.activeIndex,
|
|
1481
|
+
breadcrumbs: uiState.breadcrumbs,
|
|
1482
|
+
loading: uiState.loading,
|
|
1483
|
+
trigger: uiState.trigger,
|
|
1484
|
+
query: uiState.query,
|
|
1485
|
+
clientRect: uiState.clientRect,
|
|
1486
|
+
onSelect: (item) => actions.select(item),
|
|
1487
|
+
onHover: handleHover,
|
|
1488
|
+
onGoBack: actions.goBack,
|
|
1489
|
+
onSearchNested: actions.searchNested,
|
|
1490
|
+
onNavigateUp: actions.navigateUp,
|
|
1491
|
+
onNavigateDown: actions.navigateDown,
|
|
1492
|
+
onClose: actions.close,
|
|
1493
|
+
onFocusEditor: handleFocusEditor,
|
|
1494
|
+
renderItem,
|
|
1495
|
+
renderEmpty,
|
|
1496
|
+
renderLoading,
|
|
1497
|
+
renderGroupHeader,
|
|
1498
|
+
listboxId
|
|
1499
|
+
}
|
|
1500
|
+
) : null;
|
|
1031
1501
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1032
1502
|
"div",
|
|
1033
1503
|
{
|
|
1034
1504
|
className,
|
|
1035
1505
|
"data-mentions-input": "",
|
|
1036
1506
|
"data-disabled": disabled ? "" : void 0,
|
|
1037
|
-
...comboboxAttrs(isExpanded,
|
|
1507
|
+
...comboboxAttrs(isExpanded, listboxId),
|
|
1038
1508
|
"aria-activedescendant": isExpanded && uiState.items[uiState.activeIndex] ? `mention-option-${uiState.items[uiState.activeIndex].id}` : void 0,
|
|
1039
1509
|
children: [
|
|
1040
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react6.EditorContent, { editor }),
|
|
1041
|
-
|
|
1042
|
-
SuggestionList,
|
|
1043
|
-
{
|
|
1044
|
-
items: uiState.items,
|
|
1045
|
-
activeIndex: uiState.activeIndex,
|
|
1046
|
-
breadcrumbs: uiState.breadcrumbs,
|
|
1047
|
-
loading: uiState.loading,
|
|
1048
|
-
trigger: uiState.trigger,
|
|
1049
|
-
clientRect: uiState.clientRect,
|
|
1050
|
-
onSelect: (item) => actions.select(item),
|
|
1051
|
-
onHover: handleHover,
|
|
1052
|
-
onGoBack: actions.goBack,
|
|
1053
|
-
onSearchNested: actions.searchNested,
|
|
1054
|
-
onNavigateUp: actions.navigateUp,
|
|
1055
|
-
onNavigateDown: actions.navigateDown,
|
|
1056
|
-
onClose: actions.close,
|
|
1057
|
-
renderItem
|
|
1058
|
-
}
|
|
1059
|
-
)
|
|
1510
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: editorStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react6.EditorContent, { editor }) }),
|
|
1511
|
+
portalContainer ? suggestionList && (0, import_react_dom.createPortal)(suggestionList, portalContainer) : suggestionList
|
|
1060
1512
|
]
|
|
1061
1513
|
}
|
|
1062
1514
|
);
|