mentionize 0.0.6 → 0.0.7

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/dist/cjs/index.js CHANGED
@@ -1,51 +1,962 @@
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
1
3
  var __defProp = Object.defineProperty;
2
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- function __accessProp(key) {
6
- return this[key];
7
- }
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
8
19
  var __toCommonJS = (from) => {
9
- var entry = (__moduleCache ??= new WeakMap).get(from), desc;
20
+ var entry = __moduleCache.get(from), desc;
10
21
  if (entry)
11
22
  return entry;
12
23
  entry = __defProp({}, "__esModule", { value: true });
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (var key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(entry, key))
16
- __defProp(entry, key, {
17
- get: __accessProp.bind(from, key),
18
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
- });
20
- }
24
+ if (from && typeof from === "object" || typeof from === "function")
25
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
26
+ get: () => from[key],
27
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
28
+ }));
21
29
  __moduleCache.set(from, entry);
22
30
  return entry;
23
31
  };
24
- var __moduleCache;
25
- var __returnValue = (v) => v;
26
- function __exportSetter(name, newValue) {
27
- this[name] = __returnValue.bind(null, newValue);
28
- }
29
32
  var __export = (target, all) => {
30
33
  for (var name in all)
31
34
  __defProp(target, name, {
32
35
  get: all[name],
33
36
  enumerable: true,
34
37
  configurable: true,
35
- set: __exportSetter.bind(all, name)
38
+ set: (newValue) => all[name] = () => newValue
36
39
  });
37
40
  };
38
41
 
39
42
  // src/index.ts
40
43
  var exports_src = {};
41
44
  __export(exports_src, {
42
- useMentionEngine: () => import_useMentionEngine.useMentionEngine,
43
- useCaretPosition: () => import_useCaretPosition.useCaretPosition,
44
- MentionInput: () => import_MentionInput.MentionInput,
45
- MentionHighlighter: () => import_MentionHighlighter.MentionHighlighter,
46
- MentionDropdown: () => import_MentionDropdown.MentionDropdown
45
+ useMentionEngine: () => useMentionEngine,
46
+ useCaretPosition: () => useCaretPosition,
47
+ MentionInput: () => MentionInput,
48
+ MentionHighlighter: () => MentionHighlighter,
49
+ MentionDropdown: () => MentionDropdown
47
50
  });
48
51
  module.exports = __toCommonJS(exports_src);
49
52
 
50
- //# debugId=00144F398F840CEA64756E2164756E21
53
+ // src/MentionInput.tsx
54
+ var import_react5 = require("react");
55
+
56
+ // src/MentionDropdown.tsx
57
+ var import_react = require("react");
58
+ var jsx_dev_runtime = require("react/jsx-dev-runtime");
59
+ var MentionDropdown = ({
60
+ items,
61
+ trigger,
62
+ highlightedIndex,
63
+ onHighlight,
64
+ onSelect,
65
+ onLoadMore,
66
+ loading,
67
+ loadingContent,
68
+ position,
69
+ width,
70
+ className,
71
+ positionStrategy = "fixed",
72
+ containerEl
73
+ }) => {
74
+ const listRef = import_react.useRef(null);
75
+ const sentinelRef = import_react.useRef(null);
76
+ import_react.useEffect(() => {
77
+ if (!onLoadMore || !sentinelRef.current)
78
+ return;
79
+ const sentinel = sentinelRef.current;
80
+ const observer = new IntersectionObserver((entries) => {
81
+ if (entries[0]?.isIntersecting) {
82
+ onLoadMore();
83
+ }
84
+ }, { root: listRef.current, threshold: 0.1 });
85
+ observer.observe(sentinel);
86
+ return () => observer.disconnect();
87
+ }, [onLoadMore]);
88
+ import_react.useEffect(() => {
89
+ const container = listRef.current;
90
+ if (!container)
91
+ return;
92
+ const highlighted = container.querySelector(`[data-mentionize-option-index="${highlightedIndex}"]`);
93
+ if (highlighted) {
94
+ highlighted.scrollIntoView({ block: "nearest" });
95
+ }
96
+ }, [highlightedIndex]);
97
+ const maxHeight = 240;
98
+ const gap = 4;
99
+ const spaceBelow = window.innerHeight - position.top - gap;
100
+ const flipAbove = spaceBelow < maxHeight && position.top > maxHeight;
101
+ let style;
102
+ if (positionStrategy === "absolute" && containerEl) {
103
+ const rect = containerEl.getBoundingClientRect();
104
+ const relLeft = position.left - rect.left;
105
+ const relTop = flipAbove ? position.top - gap - maxHeight - rect.top : position.top + gap - rect.top;
106
+ style = {
107
+ position: "absolute",
108
+ width,
109
+ maxHeight,
110
+ overflowY: "auto",
111
+ zIndex: 50,
112
+ top: relTop,
113
+ left: relLeft
114
+ };
115
+ } else {
116
+ style = {
117
+ position: "fixed",
118
+ width,
119
+ maxHeight,
120
+ overflowY: "auto",
121
+ zIndex: 50,
122
+ ...flipAbove ? { bottom: window.innerHeight - position.top + gap, left: position.left } : { top: position.top + gap, left: position.left }
123
+ };
124
+ }
125
+ const handleMouseDown = import_react.useCallback((e) => {
126
+ e.preventDefault();
127
+ }, []);
128
+ if (!items.length && !loading)
129
+ return null;
130
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
131
+ ref: listRef,
132
+ role: "listbox",
133
+ className,
134
+ style,
135
+ "data-mentionize-dropdown": "",
136
+ onMouseDown: handleMouseDown,
137
+ children: [
138
+ items.map((item, i) => {
139
+ const isHighlighted = i === highlightedIndex;
140
+ const optCls = typeof trigger.optionClassName === "function" ? trigger.optionClassName(item) : trigger.optionClassName;
141
+ if (trigger.renderOption) {
142
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
143
+ role: "option",
144
+ "aria-selected": isHighlighted,
145
+ className: optCls,
146
+ "data-mentionize-option-index": i,
147
+ "data-mentionize-option-highlighted": isHighlighted || undefined,
148
+ onMouseEnter: () => onHighlight(i),
149
+ onClick: () => onSelect(item),
150
+ children: trigger.renderOption(item, isHighlighted)
151
+ }, i, false, undefined, this);
152
+ }
153
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
154
+ role: "option",
155
+ "aria-selected": isHighlighted,
156
+ className: optCls,
157
+ "data-mentionize-option-index": i,
158
+ "data-mentionize-option": "",
159
+ "data-mentionize-option-highlighted": isHighlighted || undefined,
160
+ onMouseEnter: () => onHighlight(i),
161
+ onClick: () => onSelect(item),
162
+ children: trigger.displayText(item)
163
+ }, i, false, undefined, this);
164
+ }),
165
+ loading && /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
166
+ "data-mentionize-loading": "",
167
+ children: loadingContent ?? "Loading..."
168
+ }, undefined, false, undefined, this),
169
+ onLoadMore && !loading && /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
170
+ ref: sentinelRef,
171
+ style: { height: 1 },
172
+ "aria-hidden": true
173
+ }, undefined, false, undefined, this)
174
+ ]
175
+ }, undefined, true, undefined, this);
176
+ };
177
+
178
+ // src/MentionHighlighter.tsx
179
+ var import_react2 = __toESM(require("react"));
180
+ function textToNodes(text) {
181
+ const parts = text.split(`
182
+ `);
183
+ const nodes = [];
184
+ for (let i = 0;i < parts.length; i++) {
185
+ if (i > 0)
186
+ nodes.push(import_react2.default.createElement("br", { key: `br-${i}` }));
187
+ if (parts[i])
188
+ nodes.push(parts[i]);
189
+ }
190
+ return nodes;
191
+ }
192
+ var MentionHighlighter = ({
193
+ visible,
194
+ mentions,
195
+ triggers,
196
+ textareaRef,
197
+ getItemForMention,
198
+ className,
199
+ style
200
+ }) => {
201
+ const ref = import_react2.useRef(null);
202
+ import_react2.useEffect(() => {
203
+ const textarea = textareaRef.current;
204
+ const highlighter = ref.current;
205
+ if (!textarea || !highlighter)
206
+ return;
207
+ const onScroll = () => {
208
+ highlighter.scrollTop = textarea.scrollTop;
209
+ highlighter.scrollLeft = textarea.scrollLeft;
210
+ };
211
+ textarea.addEventListener("scroll", onScroll);
212
+ return () => textarea.removeEventListener("scroll", onScroll);
213
+ }, [textareaRef]);
214
+ const children = import_react2.useMemo(() => {
215
+ const sorted = mentions.slice().sort((a, b) => a.start - b.start);
216
+ if (!sorted.length)
217
+ return textToNodes(visible);
218
+ const triggerMap = new Map;
219
+ for (const t of triggers) {
220
+ triggerMap.set(t.trigger, t);
221
+ }
222
+ const nodes = [];
223
+ let last = 0;
224
+ for (let i = 0;i < sorted.length; i++) {
225
+ const m = sorted[i];
226
+ if (last < m.start) {
227
+ nodes.push(...textToNodes(visible.slice(last, m.start)));
228
+ }
229
+ const mentionText = visible.slice(m.start, m.end);
230
+ const t = triggerMap.get(m.trigger);
231
+ let cls = "mentionize-mention";
232
+ if (t) {
233
+ if (typeof t.mentionClassName === "function") {
234
+ const item = getItemForMention?.(m.trigger, m.key) ?? null;
235
+ cls = t.mentionClassName({
236
+ key: m.key,
237
+ displayText: m.displayText,
238
+ trigger: m.trigger,
239
+ item
240
+ });
241
+ } else if (t.mentionClassName) {
242
+ cls = t.mentionClassName;
243
+ }
244
+ }
245
+ nodes.push(import_react2.default.createElement("span", {
246
+ key: `mention-${i}`,
247
+ className: cls,
248
+ "data-mentionize-trigger": m.trigger,
249
+ "data-mentionize-key": m.key
250
+ }, mentionText));
251
+ last = m.end;
252
+ }
253
+ if (last < visible.length) {
254
+ nodes.push(...textToNodes(visible.slice(last)));
255
+ }
256
+ return nodes;
257
+ }, [visible, mentions, triggers, getItemForMention]);
258
+ import_react2.useLayoutEffect(() => {
259
+ const el = ref.current;
260
+ if (!el)
261
+ return;
262
+ const spans = el.querySelectorAll("[data-mentionize-trigger]");
263
+ for (let i = 0;i < spans.length; i++) {
264
+ const span = spans[i];
265
+ span.style.marginLeft = "";
266
+ span.style.marginRight = "";
267
+ const cs = getComputedStyle(span);
268
+ const extraLeft = parseFloat(cs.paddingLeft) + parseFloat(cs.borderLeftWidth) + parseFloat(cs.marginLeft);
269
+ const extraRight = parseFloat(cs.paddingRight) + parseFloat(cs.borderRightWidth) + parseFloat(cs.marginRight);
270
+ if (extraLeft)
271
+ span.style.marginLeft = `${-extraLeft}px`;
272
+ if (extraRight)
273
+ span.style.marginRight = `${-extraRight}px`;
274
+ }
275
+ });
276
+ return import_react2.default.createElement("div", {
277
+ ref,
278
+ className,
279
+ style: {
280
+ position: "absolute",
281
+ inset: 0,
282
+ pointerEvents: "none",
283
+ overflow: "auto",
284
+ ...style
285
+ },
286
+ "aria-hidden": true,
287
+ "data-mentionize-highlighter": ""
288
+ }, ...children);
289
+ };
290
+
291
+ // src/useCaretPosition.ts
292
+ var import_react3 = require("react");
293
+ var SHARED_STYLE_PROPS = [
294
+ "whiteSpace",
295
+ "overflowWrap",
296
+ "wordBreak",
297
+ "padding",
298
+ "paddingTop",
299
+ "paddingRight",
300
+ "paddingBottom",
301
+ "paddingLeft",
302
+ "fontFamily",
303
+ "fontSize",
304
+ "fontWeight",
305
+ "lineHeight",
306
+ "letterSpacing",
307
+ "tabSize",
308
+ "boxSizing",
309
+ "borderWidth",
310
+ "borderStyle"
311
+ ];
312
+ function useCaretPosition(dropdownWidth) {
313
+ const mirrorRef = import_react3.useRef(null);
314
+ const getCaretPosition = import_react3.useCallback((textarea, caretIndex, textOverride) => {
315
+ const mirror = mirrorRef.current;
316
+ if (!mirror)
317
+ return null;
318
+ const caret = caretIndex ?? textarea.selectionStart;
319
+ const source = textOverride ?? textarea.value;
320
+ const before = source.slice(0, caret);
321
+ const computed = getComputedStyle(textarea);
322
+ for (const prop of SHARED_STYLE_PROPS) {
323
+ mirror.style[prop] = computed[prop];
324
+ }
325
+ mirror.style.width = `${textarea.offsetWidth}px`;
326
+ mirror.textContent = before;
327
+ const span = document.createElement("span");
328
+ span.textContent = "​";
329
+ mirror.appendChild(span);
330
+ mirror.scrollTop = textarea.scrollTop;
331
+ const spanRect = span.getBoundingClientRect();
332
+ const mirrorRect = mirror.getBoundingClientRect();
333
+ const textareaRect = textarea.getBoundingClientRect();
334
+ const top = textareaRect.top + (spanRect.top - mirrorRect.top) - textarea.scrollTop + span.offsetHeight;
335
+ let left = textareaRect.left + (spanRect.left - mirrorRect.left);
336
+ if (left + dropdownWidth > window.innerWidth - 8) {
337
+ left = window.innerWidth - dropdownWidth - 8;
338
+ }
339
+ if (left < 8)
340
+ left = 8;
341
+ mirror.innerHTML = "";
342
+ return {
343
+ top: Math.min(Math.max(top, 8), window.innerHeight - 8),
344
+ left
345
+ };
346
+ }, [dropdownWidth]);
347
+ return { mirrorRef, getCaretPosition };
348
+ }
349
+
350
+ // src/useMentionEngine.ts
351
+ var import_react4 = require("react");
352
+
353
+ // src/utils.ts
354
+ function escapeRegex(s) {
355
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
356
+ }
357
+
358
+ // src/useMentionEngine.ts
359
+ function detectMentionsInText(visibleText, triggers, getCache) {
360
+ const all = [];
361
+ for (const t of triggers) {
362
+ const triggerChar = t.trigger;
363
+ if (!visibleText.includes(triggerChar))
364
+ continue;
365
+ const cache = getCache(triggerChar);
366
+ const knownItems = [];
367
+ if (t.options) {
368
+ for (const item of t.options) {
369
+ knownItems.push({
370
+ displayText: t.displayText(item),
371
+ key: getItemKey(t, item),
372
+ item
373
+ });
374
+ }
375
+ }
376
+ for (const [key, item] of cache.entries()) {
377
+ if (item !== null) {
378
+ const dt = t.displayText(item);
379
+ if (!knownItems.some((k) => k.key === key)) {
380
+ knownItems.push({ displayText: dt, key, item });
381
+ }
382
+ }
383
+ }
384
+ if (!knownItems.length)
385
+ continue;
386
+ const compiled = knownItems.map((ki) => {
387
+ const pat = "^" + escapeRegex(ki.displayText).replace(/\s+/g, "\\s+");
388
+ return { ...ki, re: new RegExp(pat, "i") };
389
+ });
390
+ const positions = [];
391
+ let idx = visibleText.indexOf(triggerChar);
392
+ while (idx !== -1) {
393
+ positions.push(idx);
394
+ idx = visibleText.indexOf(triggerChar, idx + 1);
395
+ }
396
+ const candidates = [];
397
+ for (const pos of positions) {
398
+ const after = visibleText.slice(pos + triggerChar.length);
399
+ for (const c of compiled) {
400
+ const match = c.re.exec(after);
401
+ if (!match)
402
+ continue;
403
+ const matched = match[0];
404
+ const end = pos + triggerChar.length + matched.length;
405
+ const next = visibleText[end];
406
+ if (next && !/[\s,.:;!?)}\]]/.test(next))
407
+ continue;
408
+ candidates.push({
409
+ trigger: triggerChar,
410
+ displayText: matched,
411
+ key: c.key,
412
+ start: pos,
413
+ end
414
+ });
415
+ }
416
+ }
417
+ candidates.sort((a, b) => a.start !== b.start ? a.start - b.start : b.end - b.start - (a.end - a.start));
418
+ for (const c of candidates) {
419
+ const overlaps = all.some((f) => Math.max(f.start, c.start) < Math.min(f.end, c.end));
420
+ if (!overlaps)
421
+ all.push(c);
422
+ }
423
+ }
424
+ return all;
425
+ }
426
+ function mentionsEqual(a, b) {
427
+ if (a.length !== b.length)
428
+ return false;
429
+ for (let i = 0;i < a.length; i++) {
430
+ const ai = a[i];
431
+ const bi = b[i];
432
+ if (ai.start !== bi.start || ai.end !== bi.end || ai.key !== bi.key)
433
+ return false;
434
+ }
435
+ return true;
436
+ }
437
+ function useMentionEngine(options) {
438
+ const {
439
+ triggers,
440
+ value: controlledValue,
441
+ defaultValue,
442
+ onChange,
443
+ onMentionsChange
444
+ } = options;
445
+ const lastRawRef = import_react4.useRef(controlledValue ?? defaultValue ?? "");
446
+ const suppressEmitRef = import_react4.useRef(false);
447
+ const caretPosRef = import_react4.useRef(null);
448
+ const prevMentionsRef = import_react4.useRef([]);
449
+ const cacheRef = import_react4.useRef(new Map);
450
+ function getCache(triggerChar) {
451
+ let map = cacheRef.current.get(triggerChar);
452
+ if (!map) {
453
+ map = new Map;
454
+ cacheRef.current.set(triggerChar, map);
455
+ }
456
+ return map;
457
+ }
458
+ import_react4.useEffect(() => {
459
+ for (const t of triggers) {
460
+ if (t.options) {
461
+ const cache = getCache(t.trigger);
462
+ for (const item of t.options) {
463
+ const serialized = t.serialize(item);
464
+ const re = new RegExp(t.pattern.source, t.pattern.flags.replace("g", ""));
465
+ const m = re.exec(serialized);
466
+ if (m) {
467
+ const { key } = t.parseMatch(m);
468
+ cache.set(key, item);
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }, [triggers]);
474
+ const rawToVisible = import_react4.useCallback((raw) => {
475
+ let result = raw;
476
+ for (const t of triggers) {
477
+ const globalRe = new RegExp(t.pattern.source, t.pattern.flags.includes("g") ? t.pattern.flags : t.pattern.flags + "g");
478
+ const parts = [];
479
+ let lastIndex = 0;
480
+ let m;
481
+ globalRe.lastIndex = 0;
482
+ while ((m = globalRe.exec(result)) !== null) {
483
+ parts.push(result.slice(lastIndex, m.index));
484
+ const parsed = t.parseMatch(m);
485
+ const cache = getCache(t.trigger);
486
+ if (parsed.item !== undefined) {
487
+ if (!cache.has(parsed.key) || cache.get(parsed.key) === null) {
488
+ cache.set(parsed.key, parsed.item);
489
+ }
490
+ } else if (!cache.has(parsed.key)) {
491
+ cache.set(parsed.key, null);
492
+ }
493
+ parts.push(t.trigger + parsed.displayText);
494
+ lastIndex = m.index + m[0].length;
495
+ if (!t.pattern.flags.includes("g"))
496
+ break;
497
+ }
498
+ parts.push(result.slice(lastIndex));
499
+ result = parts.join("");
500
+ }
501
+ return result;
502
+ }, [triggers]);
503
+ const [visible, setVisible] = import_react4.useState(() => rawToVisible(controlledValue ?? defaultValue ?? ""));
504
+ const mentions = import_react4.useMemo(() => {
505
+ const newMentions = detectMentionsInText(visible, triggers, getCache);
506
+ if (mentionsEqual(prevMentionsRef.current, newMentions)) {
507
+ return prevMentionsRef.current;
508
+ }
509
+ prevMentionsRef.current = newMentions;
510
+ return newMentions;
511
+ }, [visible, triggers]);
512
+ const visibleToRaw = import_react4.useCallback((vis, mentionsList) => {
513
+ if (!mentionsList.length)
514
+ return vis;
515
+ const ordered = mentionsList.slice().sort((a, b) => a.start - b.start);
516
+ let raw = "";
517
+ let last = 0;
518
+ for (const m of ordered) {
519
+ raw += vis.slice(last, m.start);
520
+ const t = triggers.find((tr) => tr.trigger === m.trigger);
521
+ if (t) {
522
+ const cache = getCache(t.trigger);
523
+ const item = cache.get(m.key);
524
+ if (item !== null && item !== undefined) {
525
+ raw += t.serialize(item);
526
+ } else {
527
+ raw += vis.slice(m.start, m.end);
528
+ }
529
+ } else {
530
+ raw += vis.slice(m.start, m.end);
531
+ }
532
+ last = m.end;
533
+ }
534
+ raw += vis.slice(last);
535
+ return raw;
536
+ }, [triggers]);
537
+ const emitSync = import_react4.useCallback((newVisible) => {
538
+ const newMentions = detectMentionsInText(newVisible, triggers, getCache);
539
+ if (!mentionsEqual(prevMentionsRef.current, newMentions)) {
540
+ prevMentionsRef.current = newMentions;
541
+ }
542
+ const raw = visibleToRaw(newVisible, prevMentionsRef.current);
543
+ if (raw !== lastRawRef.current) {
544
+ lastRawRef.current = raw;
545
+ onChange?.(raw);
546
+ }
547
+ onMentionsChange?.(prevMentionsRef.current);
548
+ }, [triggers, visibleToRaw, onChange, onMentionsChange]);
549
+ import_react4.useEffect(() => {
550
+ if (controlledValue === undefined)
551
+ return;
552
+ if (controlledValue === lastRawRef.current)
553
+ return;
554
+ lastRawRef.current = controlledValue;
555
+ suppressEmitRef.current = true;
556
+ setVisible(rawToVisible(controlledValue));
557
+ }, [controlledValue, rawToVisible]);
558
+ const [activeTrigger, setActiveTrigger] = import_react4.useState(null);
559
+ const [searchState, setSearchState] = import_react4.useState({
560
+ items: [],
561
+ page: 0,
562
+ hasMore: false,
563
+ loading: false
564
+ });
565
+ const [highlightIndex, setHighlightIndex] = import_react4.useState(0);
566
+ const searchAbortRef = import_react4.useRef(null);
567
+ const detectActiveTrigger = import_react4.useCallback((text, caretPos) => {
568
+ const prefix = text.slice(0, caretPos);
569
+ for (const t of triggers) {
570
+ const triggerChar = t.trigger;
571
+ const re = new RegExp(escapeRegex(triggerChar) + "([^\\n" + escapeRegex(triggerChar) + "]*)$");
572
+ const match = re.exec(prefix);
573
+ if (match && match[1] !== undefined) {
574
+ const query = match[1];
575
+ if (/\s/.test(query))
576
+ continue;
577
+ return {
578
+ trigger: t,
579
+ query,
580
+ startPos: match.index
581
+ };
582
+ }
583
+ }
584
+ return null;
585
+ }, [triggers]);
586
+ const filteredOptions = import_react4.useMemo(() => {
587
+ if (!activeTrigger)
588
+ return [];
589
+ const t = activeTrigger.trigger;
590
+ const q = activeTrigger.query.toLowerCase();
591
+ if (t.onSearch) {
592
+ return searchState.items;
593
+ }
594
+ if (t.options) {
595
+ if (!q)
596
+ return t.options;
597
+ return t.options.filter((item) => t.displayText(item).toLowerCase().includes(q));
598
+ }
599
+ return [];
600
+ }, [activeTrigger, searchState.items]);
601
+ import_react4.useEffect(() => {
602
+ if (!activeTrigger?.trigger.onSearch)
603
+ return;
604
+ const t = activeTrigger.trigger;
605
+ const query = activeTrigger.query;
606
+ searchAbortRef.current?.abort();
607
+ const controller = new AbortController;
608
+ searchAbortRef.current = controller;
609
+ setSearchState((s) => ({ ...s, loading: true, page: 0 }));
610
+ const timer = setTimeout(async () => {
611
+ try {
612
+ const result = await t.onSearch(query, 0);
613
+ if (controller.signal.aborted)
614
+ return;
615
+ setSearchState({
616
+ items: result.items,
617
+ page: 0,
618
+ hasMore: result.hasMore,
619
+ loading: false
620
+ });
621
+ } catch {
622
+ if (controller.signal.aborted)
623
+ return;
624
+ setSearchState((s) => ({ ...s, loading: false }));
625
+ }
626
+ }, 150);
627
+ return () => {
628
+ clearTimeout(timer);
629
+ controller.abort();
630
+ };
631
+ }, [activeTrigger?.trigger, activeTrigger?.query]);
632
+ const loadMore = import_react4.useCallback(async () => {
633
+ if (!activeTrigger?.trigger.onSearch || searchState.loading || !searchState.hasMore)
634
+ return;
635
+ const t = activeTrigger.trigger;
636
+ const nextPage = searchState.page + 1;
637
+ setSearchState((s) => ({ ...s, loading: true }));
638
+ try {
639
+ const result = await t.onSearch(activeTrigger.query, nextPage);
640
+ setSearchState((s) => ({
641
+ items: [...s.items, ...result.items],
642
+ page: nextPage,
643
+ hasMore: result.hasMore,
644
+ loading: false
645
+ }));
646
+ } catch {
647
+ setSearchState((s) => ({ ...s, loading: false }));
648
+ }
649
+ }, [activeTrigger, searchState]);
650
+ const handleTextChange = import_react4.useCallback((newText, caretPos) => {
651
+ caretPosRef.current = caretPos;
652
+ setVisible(newText);
653
+ emitSync(newText);
654
+ const detected = detectActiveTrigger(newText, caretPos);
655
+ if (detected) {
656
+ setActiveTrigger(detected);
657
+ setHighlightIndex(0);
658
+ } else {
659
+ setActiveTrigger(null);
660
+ }
661
+ }, [detectActiveTrigger, emitSync]);
662
+ const getItemForMention = import_react4.useCallback((triggerChar, key) => {
663
+ const cache = getCache(triggerChar);
664
+ return cache.get(key) ?? null;
665
+ }, []);
666
+ const selectOption = import_react4.useCallback((item, textarea) => {
667
+ if (!activeTrigger)
668
+ return;
669
+ const t = activeTrigger.trigger;
670
+ if (t.onSelect) {
671
+ const before2 = visible.slice(0, activeTrigger.startPos);
672
+ const after2 = visible.slice(textarea.selectionStart);
673
+ const savedStartPos = activeTrigger.startPos;
674
+ setActiveTrigger(null);
675
+ const result = t.onSelect(item);
676
+ const applyResult = (value) => {
677
+ if (value !== null) {
678
+ const newVis2 = before2 + value + after2;
679
+ const pos2 = savedStartPos + value.length;
680
+ caretPosRef.current = pos2;
681
+ setVisible(newVis2);
682
+ emitSync(newVis2);
683
+ } else {
684
+ const newVis2 = before2 + after2;
685
+ caretPosRef.current = savedStartPos;
686
+ setVisible(newVis2);
687
+ emitSync(newVis2);
688
+ }
689
+ };
690
+ if (result instanceof Promise) {
691
+ result.then(applyResult);
692
+ } else {
693
+ applyResult(result);
694
+ }
695
+ return;
696
+ }
697
+ const cache = getCache(t.trigger);
698
+ const key = getItemKey(t, item);
699
+ cache.set(key, item);
700
+ const displayText = t.displayText(item);
701
+ const mentionText = t.trigger + displayText;
702
+ const before = visible.slice(0, activeTrigger.startPos);
703
+ const after = visible.slice(textarea.selectionStart);
704
+ const newVis = before + mentionText + " " + after;
705
+ const pos = before.length + mentionText.length + 1;
706
+ caretPosRef.current = pos;
707
+ setVisible(newVis);
708
+ emitSync(newVis);
709
+ setActiveTrigger(null);
710
+ }, [activeTrigger, visible, emitSync]);
711
+ const closeSuggestions = import_react4.useCallback(() => {
712
+ setActiveTrigger(null);
713
+ setSearchState({ items: [], page: 0, hasMore: false, loading: false });
714
+ }, []);
715
+ const handleKeyDown = import_react4.useCallback((e, textarea) => {
716
+ if (!activeTrigger)
717
+ return false;
718
+ const len = filteredOptions.length;
719
+ if (!len && !searchState.loading)
720
+ return false;
721
+ if (e.key === "ArrowDown") {
722
+ e.preventDefault();
723
+ const next = (highlightIndex + 1) % Math.max(len, 1);
724
+ setHighlightIndex(next);
725
+ if (len - 1 - next <= 3 && searchState.hasMore && !searchState.loading) {
726
+ loadMore();
727
+ }
728
+ return true;
729
+ }
730
+ if (e.key === "ArrowUp") {
731
+ e.preventDefault();
732
+ setHighlightIndex((highlightIndex - 1 + Math.max(len, 1)) % Math.max(len, 1));
733
+ return true;
734
+ }
735
+ if (e.key === "Enter") {
736
+ e.preventDefault();
737
+ const item = filteredOptions[highlightIndex];
738
+ if (item)
739
+ selectOption(item, textarea);
740
+ return true;
741
+ }
742
+ if (e.key === "Escape") {
743
+ e.preventDefault();
744
+ closeSuggestions();
745
+ return true;
746
+ }
747
+ return false;
748
+ }, [
749
+ activeTrigger,
750
+ filteredOptions,
751
+ highlightIndex,
752
+ searchState,
753
+ selectOption,
754
+ closeSuggestions,
755
+ loadMore
756
+ ]);
757
+ return {
758
+ visible,
759
+ setVisible,
760
+ mentions,
761
+ activeTrigger,
762
+ filteredOptions,
763
+ highlightIndex,
764
+ setHighlightIndex,
765
+ searchLoading: searchState.loading,
766
+ searchHasMore: searchState.hasMore,
767
+ handleTextChange,
768
+ handleKeyDown,
769
+ selectOption,
770
+ closeSuggestions,
771
+ loadMore,
772
+ rawToVisible,
773
+ visibleToRaw,
774
+ caretPosRef,
775
+ getItemForMention
776
+ };
777
+ }
778
+ function getItemKey(trigger, item) {
779
+ const serialized = trigger.serialize(item);
780
+ const re = new RegExp(trigger.pattern.source, trigger.pattern.flags.replace("g", ""));
781
+ const m = re.exec(serialized);
782
+ if (m) {
783
+ return trigger.parseMatch(m).key;
784
+ }
785
+ return serialized;
786
+ }
787
+
788
+ // src/MentionInput.tsx
789
+ var jsx_dev_runtime2 = require("react/jsx-dev-runtime");
790
+ var SHARED_STYLE = {
791
+ whiteSpace: "pre-wrap",
792
+ overflowWrap: "anywhere",
793
+ wordBreak: "break-word",
794
+ padding: "0.5rem 0.75rem",
795
+ fontFamily: "inherit",
796
+ fontSize: "inherit",
797
+ lineHeight: "inherit",
798
+ letterSpacing: "normal",
799
+ boxSizing: "border-box"
800
+ };
801
+ var MentionInput = import_react5.forwardRef(({
802
+ triggers,
803
+ value,
804
+ defaultValue,
805
+ onChange,
806
+ onMentionsChange,
807
+ placeholder,
808
+ disabled,
809
+ rows = 4,
810
+ className,
811
+ inputClassName,
812
+ highlighterClassName,
813
+ dropdownClassName,
814
+ dropdownWidth = 250,
815
+ loadingContent,
816
+ renderDropdown,
817
+ dropdownPositionStrategy = "fixed",
818
+ "aria-label": ariaLabel,
819
+ "aria-describedby": ariaDescribedBy
820
+ }, ref) => {
821
+ const textareaRef = import_react5.useRef(null);
822
+ const containerRef = import_react5.useRef(null);
823
+ import_react5.useImperativeHandle(ref, () => textareaRef.current);
824
+ const engine = useMentionEngine({
825
+ triggers,
826
+ value,
827
+ defaultValue,
828
+ onChange,
829
+ onMentionsChange
830
+ });
831
+ import_react5.useLayoutEffect(() => {
832
+ const pos = engine.caretPosRef.current;
833
+ const ta = textareaRef.current;
834
+ if (pos !== null && ta && document.activeElement === ta) {
835
+ ta.setSelectionRange(pos, pos);
836
+ engine.caretPosRef.current = null;
837
+ }
838
+ });
839
+ const { mirrorRef, getCaretPosition } = useCaretPosition(dropdownWidth);
840
+ const [dropdownPos, setDropdownPos] = import_react5.useState(null);
841
+ import_react5.useEffect(() => {
842
+ if (engine.activeTrigger && textareaRef.current) {
843
+ requestAnimationFrame(() => {
844
+ const pos = getCaretPosition(textareaRef.current);
845
+ if (pos)
846
+ setDropdownPos(pos);
847
+ });
848
+ } else {
849
+ setDropdownPos(null);
850
+ }
851
+ }, [engine.activeTrigger, engine.visible, getCaretPosition]);
852
+ const handleChange = import_react5.useCallback((e) => {
853
+ engine.handleTextChange(e.target.value, e.target.selectionStart);
854
+ }, [engine.handleTextChange]);
855
+ const handleKeyDown = import_react5.useCallback((e) => {
856
+ if (textareaRef.current) {
857
+ engine.handleKeyDown(e, textareaRef.current);
858
+ }
859
+ }, [engine.handleKeyDown]);
860
+ const handlePaste = import_react5.useCallback((e) => {
861
+ const ta = textareaRef.current;
862
+ if (!ta)
863
+ return;
864
+ const txt = e.clipboardData.getData("text");
865
+ const start = ta.selectionStart;
866
+ const end = ta.selectionEnd;
867
+ const newText = engine.visible.slice(0, start) + txt + engine.visible.slice(end);
868
+ e.preventDefault();
869
+ engine.handleTextChange(newText, start + txt.length);
870
+ }, [engine.visible, engine.handleTextChange]);
871
+ const handleBlur = import_react5.useCallback(() => {
872
+ setTimeout(() => engine.closeSuggestions(), 150);
873
+ }, [engine.closeSuggestions]);
874
+ const handleSelect = import_react5.useCallback((item) => {
875
+ if (textareaRef.current) {
876
+ engine.selectOption(item, textareaRef.current);
877
+ }
878
+ }, [engine.selectOption]);
879
+ const showDropdown = engine.activeTrigger !== null && dropdownPos !== null;
880
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV("div", {
881
+ ref: containerRef,
882
+ className,
883
+ style: { position: "relative" },
884
+ "data-mentionize-container": "",
885
+ children: [
886
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(MentionHighlighter, {
887
+ visible: engine.visible,
888
+ mentions: engine.mentions,
889
+ triggers,
890
+ textareaRef,
891
+ getItemForMention: engine.getItemForMention,
892
+ className: highlighterClassName,
893
+ style: SHARED_STYLE
894
+ }, undefined, false, undefined, this),
895
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV("textarea", {
896
+ ref: textareaRef,
897
+ value: engine.visible,
898
+ onChange: handleChange,
899
+ onKeyDown: handleKeyDown,
900
+ onPaste: handlePaste,
901
+ onBlur: handleBlur,
902
+ rows,
903
+ placeholder,
904
+ disabled,
905
+ "aria-label": ariaLabel,
906
+ "aria-describedby": ariaDescribedBy,
907
+ "aria-autocomplete": "list",
908
+ "aria-expanded": showDropdown,
909
+ className: inputClassName,
910
+ style: {
911
+ ...SHARED_STYLE,
912
+ position: "relative",
913
+ width: "100%",
914
+ resize: "vertical",
915
+ background: "transparent",
916
+ color: "transparent",
917
+ caretColor: "CanvasText",
918
+ zIndex: 10
919
+ },
920
+ "data-mentionize-input": ""
921
+ }, undefined, false, undefined, this),
922
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV("div", {
923
+ ref: mirrorRef,
924
+ "aria-hidden": true,
925
+ style: {
926
+ position: "absolute",
927
+ top: 0,
928
+ left: -9999,
929
+ visibility: "hidden",
930
+ ...SHARED_STYLE
931
+ },
932
+ "data-mentionize-mirror": ""
933
+ }, undefined, false, undefined, this),
934
+ showDropdown && engine.activeTrigger && (renderDropdown ? renderDropdown({
935
+ items: engine.filteredOptions,
936
+ highlightedIndex: engine.highlightIndex,
937
+ onSelect: handleSelect,
938
+ onHighlight: engine.setHighlightIndex,
939
+ loading: engine.searchLoading,
940
+ onLoadMore: engine.searchHasMore ? engine.loadMore : undefined
941
+ }) : /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(MentionDropdown, {
942
+ items: engine.filteredOptions,
943
+ trigger: engine.activeTrigger.trigger,
944
+ highlightedIndex: engine.highlightIndex,
945
+ onHighlight: engine.setHighlightIndex,
946
+ onSelect: handleSelect,
947
+ onLoadMore: engine.searchHasMore ? engine.loadMore : undefined,
948
+ loading: engine.searchLoading,
949
+ loadingContent,
950
+ position: dropdownPos,
951
+ width: dropdownWidth,
952
+ className: dropdownClassName,
953
+ positionStrategy: dropdownPositionStrategy,
954
+ containerEl: dropdownPositionStrategy === "absolute" ? containerRef.current : undefined
955
+ }, undefined, false, undefined, this))
956
+ ]
957
+ }, undefined, true, undefined, this);
958
+ });
959
+ MentionInput.displayName = "MentionInput";
960
+
961
+ //# debugId=B75C97134089AEDC64756E2164756E21
51
962
  //# sourceMappingURL=index.js.map