@anvia/react-ui 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +14 -1
  2. package/dist/attachment/index.js +2 -2
  3. package/dist/chat/index.d.ts +29 -4
  4. package/dist/chat/index.js +4 -4
  5. package/dist/{chunk-QHP4GSRG.js → chunk-32QFNRXR.js} +2 -2
  6. package/dist/{chunk-MXYHYD3O.js → chunk-657E5BPN.js} +2 -2
  7. package/dist/{chunk-5IHC7D4U.js → chunk-BCCU3VT5.js} +1 -1
  8. package/dist/chunk-BCCU3VT5.js.map +1 -0
  9. package/dist/{chunk-5Z2IWZ5O.js → chunk-FZANAMWH.js} +3 -3
  10. package/dist/{chunk-OKTDYMSA.js → chunk-GXB4VINL.js} +2 -2
  11. package/dist/{chunk-YDMDLFU4.js → chunk-JUNV3MUR.js} +2 -2
  12. package/dist/{chunk-IHRCKGRS.js → chunk-SE7I6ZXJ.js} +2 -2
  13. package/dist/{chunk-4Z7LULAM.js → chunk-TOBAQM2G.js} +2 -2
  14. package/dist/{chunk-LRAQW3ZK.js → chunk-ZQ327YX4.js} +689 -61
  15. package/dist/chunk-ZQ327YX4.js.map +1 -0
  16. package/dist/completion/index.js +2 -2
  17. package/dist/human-input/index.js +2 -2
  18. package/dist/image/index.js +2 -2
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +9 -9
  21. package/dist/message/index.js +3 -3
  22. package/dist/selection-toolbar/index.js +2 -2
  23. package/dist/shared.d.ts +1 -1
  24. package/dist/shared.js +1 -1
  25. package/dist/styles.css +20 -0
  26. package/dist/thread-C2-Xo08o.d.ts +102 -0
  27. package/dist/thread-list/index.js +2 -2
  28. package/package.json +12 -2
  29. package/dist/chunk-5IHC7D4U.js.map +0 -1
  30. package/dist/chunk-LRAQW3ZK.js.map +0 -1
  31. package/dist/thread-DYPvlsJa.d.ts +0 -38
  32. /package/dist/{chunk-QHP4GSRG.js.map → chunk-32QFNRXR.js.map} +0 -0
  33. /package/dist/{chunk-MXYHYD3O.js.map → chunk-657E5BPN.js.map} +0 -0
  34. /package/dist/{chunk-5Z2IWZ5O.js.map → chunk-FZANAMWH.js.map} +0 -0
  35. /package/dist/{chunk-OKTDYMSA.js.map → chunk-GXB4VINL.js.map} +0 -0
  36. /package/dist/{chunk-YDMDLFU4.js.map → chunk-JUNV3MUR.js.map} +0 -0
  37. /package/dist/{chunk-IHRCKGRS.js.map → chunk-SE7I6ZXJ.js.map} +0 -0
  38. /package/dist/{chunk-4Z7LULAM.js.map → chunk-TOBAQM2G.js.map} +0 -0
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  Message
3
- } from "./chunk-5Z2IWZ5O.js";
3
+ } from "./chunk-FZANAMWH.js";
4
4
  import {
5
5
  Attachment
6
- } from "./chunk-QHP4GSRG.js";
6
+ } from "./chunk-32QFNRXR.js";
7
7
  import {
8
8
  composeRefs,
9
9
  renderPrimitive
@@ -16,9 +16,18 @@ import {
16
16
  useChatContext,
17
17
  useComposer,
18
18
  useThread
19
- } from "./chunk-5IHC7D4U.js";
19
+ } from "./chunk-BCCU3VT5.js";
20
20
 
21
21
  // src/chat/composer.tsx
22
+ import { mergeAttributes } from "@tiptap/core";
23
+ import Document from "@tiptap/extension-document";
24
+ import HardBreak from "@tiptap/extension-hard-break";
25
+ import Mention from "@tiptap/extension-mention";
26
+ import Paragraph from "@tiptap/extension-paragraph";
27
+ import Placeholder from "@tiptap/extension-placeholder";
28
+ import Text from "@tiptap/extension-text";
29
+ import { PluginKey } from "@tiptap/pm/state";
30
+ import { EditorContent, useEditor } from "@tiptap/react";
22
31
  import {
23
32
  forwardRef,
24
33
  useCallback,
@@ -31,14 +40,18 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
31
40
  var ComposerRoot = forwardRef(function ComposerRoot2({
32
41
  attachments: attachmentsProp,
33
42
  defaultAttachments,
43
+ defaultEntities,
34
44
  defaultInput = "",
35
45
  defaultQuote,
46
+ entities: entitiesProp,
36
47
  input: inputProp,
37
48
  onAttachmentsChange,
49
+ onEntitiesChange,
38
50
  onInputChange,
39
51
  onQuoteChange,
40
52
  quote: quoteProp,
41
53
  submitMessage,
54
+ triggers: triggersProp,
42
55
  onSubmit,
43
56
  ...props
44
57
  }, ref) {
@@ -47,11 +60,17 @@ var ComposerRoot = forwardRef(function ComposerRoot2({
47
60
  const [uncontrolledAttachments, setUncontrolledAttachments] = useState(() => [
48
61
  ...defaultAttachments ?? []
49
62
  ]);
63
+ const [uncontrolledEntities, setUncontrolledEntities] = useState(() => [
64
+ ...defaultEntities ?? []
65
+ ]);
50
66
  const [uncontrolledQuote, setUncontrolledQuote] = useState(
51
67
  defaultQuote
52
68
  );
69
+ const [activeTrigger, setActiveTriggerState] = useState();
53
70
  const input = inputProp ?? uncontrolledInput;
54
71
  const attachments = attachmentsProp ?? uncontrolledAttachments;
72
+ const entities = entitiesProp ?? uncontrolledEntities;
73
+ const triggers = triggersProp ?? EMPTY_COMPOSER_TRIGGERS;
55
74
  const rawQuote = quoteProp ?? uncontrolledQuote;
56
75
  const rawQuoteMessageId = rawQuote?.messageId;
57
76
  const rawQuoteText = rawQuote?.text;
@@ -61,10 +80,14 @@ var ComposerRoot = forwardRef(function ComposerRoot2({
61
80
  );
62
81
  const attachmentsRef = useRef(attachments);
63
82
  attachmentsRef.current = attachments;
83
+ const entitiesRef = useRef(entities);
84
+ entitiesRef.current = entities;
85
+ const defaultSubmissionInFlightRef = useRef(false);
64
86
  const inputControlled = inputProp !== void 0;
65
87
  const attachmentsControlled = attachmentsProp !== void 0;
88
+ const entitiesControlled = entitiesProp !== void 0;
66
89
  const quoteControlled = quoteProp !== void 0;
67
- const hasMessageContent = input.trim().length > 0 || attachments.length > 0 || quote !== void 0;
90
+ const hasMessageContent = input.trim().length > 0 || attachments.length > 0 || entities.length > 0 || quote !== void 0;
68
91
  const canSubmit = hasMessageContent && chat.status !== "streaming";
69
92
  const canStop = chat.status === "streaming";
70
93
  const setInput = useCallback(
@@ -87,6 +110,17 @@ var ComposerRoot = forwardRef(function ComposerRoot2({
87
110
  },
88
111
  [attachmentsControlled, onAttachmentsChange]
89
112
  );
113
+ const setEntities = useCallback(
114
+ (update) => {
115
+ const nextEntities = typeof update === "function" ? update(entitiesRef.current) : update;
116
+ entitiesRef.current = nextEntities;
117
+ if (!entitiesControlled) {
118
+ setUncontrolledEntities(nextEntities);
119
+ }
120
+ onEntitiesChange?.(nextEntities);
121
+ },
122
+ [entitiesControlled, onEntitiesChange]
123
+ );
90
124
  const setQuote = useCallback(
91
125
  (nextQuote) => {
92
126
  const normalizedQuote = normalizeQuote(nextQuote);
@@ -119,42 +153,54 @@ var ComposerRoot = forwardRef(function ComposerRoot2({
119
153
  const clear = useCallback(() => {
120
154
  setInput("");
121
155
  setAttachments([]);
156
+ setEntities([]);
157
+ setActiveTriggerState(void 0);
122
158
  clearQuote();
123
- }, [clearQuote, setAttachments, setInput]);
159
+ }, [clearQuote, setAttachments, setEntities, setInput]);
124
160
  const submit = useCallback(async () => {
125
161
  const prompt = input;
126
- if (prompt.trim().length === 0 && attachments.length === 0 && quote === void 0 || chat.status === "streaming") {
162
+ if (prompt.trim().length === 0 && attachments.length === 0 && entities.length === 0 && quote === void 0 || chat.status === "streaming") {
127
163
  return;
128
164
  }
129
165
  if (submitMessage !== void 0) {
130
166
  await submitMessage({
131
167
  input: prompt,
132
168
  attachments,
169
+ entities,
133
170
  chat,
134
171
  quote,
135
172
  clear
136
173
  });
137
174
  return;
138
175
  }
139
- const submittedText = quote === void 0 ? prompt : promptWithQuote(prompt, quote);
140
- if (attachments.length === 0) {
141
- if (quote === void 0) {
142
- await chat.sendMessage(prompt);
143
- } else {
144
- await chat.sendMessage({
145
- text: submittedText,
146
- metadata: { quote }
147
- });
148
- }
149
- } else {
150
- await chat.sendMessage({
151
- text: submittedText,
152
- attachments,
153
- ...quote === void 0 ? {} : { metadata: { quote } }
154
- });
176
+ if (defaultSubmissionInFlightRef.current) {
177
+ return;
178
+ }
179
+ const submittedAttachments = [...attachments];
180
+ const submittedEntities = [...entities];
181
+ const submittedQuote = quote === void 0 ? void 0 : { ...quote };
182
+ const submittedText = submittedQuote === void 0 ? prompt : promptWithQuote(prompt, submittedQuote);
183
+ const metadata = composerSubmitMetadata(submittedQuote, submittedEntities);
184
+ const payload = submittedAttachments.length === 0 ? metadata === void 0 ? prompt : {
185
+ text: submittedText,
186
+ metadata
187
+ } : {
188
+ text: submittedText,
189
+ attachments: submittedAttachments,
190
+ ...metadata === void 0 ? {} : { metadata }
191
+ };
192
+ defaultSubmissionInFlightRef.current = true;
193
+ try {
194
+ const submission = chat.sendMessage(payload);
195
+ clear();
196
+ await submission;
197
+ } finally {
198
+ defaultSubmissionInFlightRef.current = false;
155
199
  }
156
- clear();
157
- }, [attachments, chat, clear, input, quote, submitMessage]);
200
+ }, [attachments, chat, clear, entities, input, quote, submitMessage]);
201
+ const setActiveTrigger = useCallback((update) => {
202
+ setActiveTriggerState(update);
203
+ }, []);
158
204
  const value = useMemo(
159
205
  () => ({
160
206
  input,
@@ -164,9 +210,14 @@ var ComposerRoot = forwardRef(function ComposerRoot2({
164
210
  addAttachment,
165
211
  removeAttachment,
166
212
  clearAttachments,
213
+ entities,
214
+ setEntities,
167
215
  quote,
168
216
  setQuote,
169
217
  clearQuote,
218
+ triggers,
219
+ activeTrigger,
220
+ setActiveTrigger,
170
221
  submit,
171
222
  stop: chat.stop,
172
223
  status: chat.status,
@@ -176,19 +227,24 @@ var ComposerRoot = forwardRef(function ComposerRoot2({
176
227
  [
177
228
  addAttachment,
178
229
  attachments,
230
+ activeTrigger,
179
231
  canStop,
180
232
  canSubmit,
181
233
  chat.status,
182
234
  chat.stop,
183
235
  clearAttachments,
184
236
  clearQuote,
237
+ entities,
185
238
  input,
186
239
  quote,
187
240
  removeAttachment,
188
241
  setAttachments,
242
+ setActiveTrigger,
243
+ setEntities,
189
244
  setInput,
190
245
  setQuote,
191
- submit
246
+ submit,
247
+ triggers
192
248
  ]
193
249
  );
194
250
  const handleSubmit = useCallback(
@@ -264,42 +320,71 @@ var ComposerClearQuote = forwardRef(
264
320
  );
265
321
  var ComposerInput = forwardRef(function ComposerInput2({
266
322
  autoResize = true,
323
+ disabled,
267
324
  maxRows: maxRowsProp,
268
325
  minRows: minRowsProp,
269
- onChange,
270
326
  onKeyDown,
271
- rows,
327
+ placeholder,
328
+ style,
272
329
  ...props
273
330
  }, ref) {
274
331
  const composer = useComposer();
275
- const inputRef = useRef(null);
276
- const wasAutoResizeRef = useRef(autoResize);
277
- const composedInputRef = useMemo(() => composeRefs(inputRef, ref), [ref]);
278
- const minRows = normalizeRows(minRowsProp ?? rows ?? 1, 1);
332
+ const composerRef = useRef(composer);
333
+ composerRef.current = composer;
334
+ const editorReadyRef = useRef(false);
335
+ const syncingFromEditorRef = useRef(false);
336
+ const triggers = composer.triggers;
337
+ const minRows = normalizeRows(minRowsProp ?? 1, 1);
279
338
  const maxRows = normalizeRows(maxRowsProp ?? 6, minRows);
280
- useEffect(() => {
281
- const wasAutoResize = wasAutoResizeRef.current;
282
- wasAutoResizeRef.current = autoResize;
283
- if (!autoResize) {
284
- if (wasAutoResize) {
285
- resetComposerInputSize(inputRef.current);
286
- }
287
- return;
288
- }
289
- resizeComposerInput(inputRef.current, { maxRows, minRows });
290
- });
291
- const handleChange = useCallback(
292
- (event) => {
293
- onChange?.(event);
294
- if (!event.defaultPrevented) {
295
- composer.setInput(event.currentTarget.value);
296
- if (autoResize) {
297
- resizeComposerInput(event.currentTarget, { maxRows, minRows });
339
+ const extensions = useMemo(
340
+ () => composerInputExtensions({
341
+ composerRef,
342
+ placeholder,
343
+ triggers
344
+ }),
345
+ [placeholder, triggers]
346
+ );
347
+ const editor = useEditor(
348
+ {
349
+ extensions,
350
+ content: plainTextToComposerContent(composer.input),
351
+ editable: composer.status !== "streaming",
352
+ editorProps: {
353
+ attributes: {
354
+ "aria-label": props["aria-label"] ?? "Message",
355
+ "aria-multiline": "true",
356
+ "data-anvia-composer-editor": "",
357
+ role: "textbox"
358
+ }
359
+ },
360
+ onUpdate: ({ editor: updatedEditor }) => {
361
+ if (!editorReadyRef.current) {
362
+ return;
298
363
  }
364
+ const snapshot = composerSnapshotFromEditor(
365
+ updatedEditor,
366
+ composerRef.current.entities,
367
+ composerRef.current.triggers
368
+ );
369
+ syncingFromEditorRef.current = true;
370
+ composerRef.current.setInput(snapshot.input);
371
+ composerRef.current.setEntities(snapshot.entities);
299
372
  }
300
373
  },
301
- [autoResize, composer, maxRows, minRows, onChange]
374
+ [extensions]
302
375
  );
376
+ const ariaDisabled = props["aria-disabled"] === true || props["aria-disabled"] === "true";
377
+ const composerDisabled = disabled === true || ariaDisabled || composer.status === "streaming";
378
+ const inputStyle = useMemo(() => {
379
+ if (!autoResize) {
380
+ return style;
381
+ }
382
+ const rowsStyle = {
383
+ ["--anvia-composer-min-rows"]: minRows,
384
+ ["--anvia-composer-max-rows"]: maxRows
385
+ };
386
+ return style === void 0 ? rowsStyle : { ...rowsStyle, ...style };
387
+ }, [autoResize, maxRows, minRows, style]);
303
388
  const handleKeyDown = useCallback(
304
389
  (event) => {
305
390
  onKeyDown?.(event);
@@ -307,25 +392,474 @@ var ComposerInput = forwardRef(function ComposerInput2({
307
392
  return;
308
393
  }
309
394
  event.preventDefault();
310
- void composer.submit();
395
+ void composerRef.current.submit();
311
396
  },
312
- [composer, onKeyDown]
397
+ [onKeyDown]
313
398
  );
399
+ useEffect(() => {
400
+ if (editor === null) {
401
+ return;
402
+ }
403
+ editor.setEditable(!composerDisabled, false);
404
+ }, [composerDisabled, editor]);
405
+ useEffect(() => {
406
+ if (editor === null) {
407
+ return;
408
+ }
409
+ const handleKeyDown2 = (event) => {
410
+ if (submitComposerFromEditorKeyDown(event, composerRef)) {
411
+ event.stopPropagation();
412
+ }
413
+ };
414
+ editor.view.dom.addEventListener("keydown", handleKeyDown2, true);
415
+ return () => {
416
+ editor.view.dom.removeEventListener("keydown", handleKeyDown2, true);
417
+ };
418
+ }, [editor]);
419
+ useEffect(() => {
420
+ if (editor === null) {
421
+ return;
422
+ }
423
+ editor.commands.setContent(plainTextToComposerContent(composerRef.current.input), {
424
+ emitUpdate: false
425
+ });
426
+ editorReadyRef.current = true;
427
+ return () => {
428
+ editorReadyRef.current = false;
429
+ };
430
+ }, [editor]);
431
+ useEffect(() => {
432
+ if (editor === null || !editorReadyRef.current) {
433
+ return;
434
+ }
435
+ if (syncingFromEditorRef.current) {
436
+ syncingFromEditorRef.current = false;
437
+ return;
438
+ }
439
+ const snapshot = composerSnapshotFromEditor(editor, composer.entities, composer.triggers);
440
+ if (snapshot.input === composer.input) {
441
+ return;
442
+ }
443
+ editor.commands.setContent(plainTextToComposerContent(composer.input), {
444
+ emitUpdate: false
445
+ });
446
+ }, [composer, editor]);
314
447
  return renderPrimitive(
315
- "textarea",
448
+ "div",
316
449
  {
317
450
  ...props,
318
- "aria-label": props["aria-label"] ?? "Message",
319
- disabled: props.disabled ?? composer.status === "streaming",
320
- onChange: handleChange,
451
+ children: /* @__PURE__ */ jsx(EditorContent, { editor }),
452
+ "aria-disabled": composerDisabled ? true : props["aria-disabled"],
453
+ "data-anvia-composer-input": "",
454
+ "data-state": composerDisabled ? "disabled" : "enabled",
455
+ "data-auto-resize": autoResize ? "" : void 0,
321
456
  onKeyDown: handleKeyDown,
322
- rows: autoResize ? minRows : rows,
323
- value: composer.input,
324
- "data-anvia-composer-input": ""
457
+ style: inputStyle
325
458
  },
326
- composedInputRef
459
+ ref
327
460
  );
328
461
  });
462
+ var ComposerTextareaInput = forwardRef(
463
+ function ComposerTextareaInput2({
464
+ autoResize = true,
465
+ maxRows: maxRowsProp,
466
+ minRows: minRowsProp,
467
+ onChange,
468
+ onKeyDown,
469
+ rows,
470
+ ...props
471
+ }, ref) {
472
+ const composer = useComposer();
473
+ const inputRef = useRef(null);
474
+ const wasAutoResizeRef = useRef(autoResize);
475
+ const composedInputRef = useMemo(() => composeRefs(inputRef, ref), [ref]);
476
+ const minRows = normalizeRows(minRowsProp ?? rows ?? 1, 1);
477
+ const maxRows = normalizeRows(maxRowsProp ?? 6, minRows);
478
+ useEffect(() => {
479
+ const wasAutoResize = wasAutoResizeRef.current;
480
+ wasAutoResizeRef.current = autoResize;
481
+ if (!autoResize) {
482
+ if (wasAutoResize) {
483
+ resetComposerInputSize(inputRef.current);
484
+ }
485
+ return;
486
+ }
487
+ resizeComposerInput(inputRef.current, { maxRows, minRows });
488
+ });
489
+ const handleChange = useCallback(
490
+ (event) => {
491
+ onChange?.(event);
492
+ if (!event.defaultPrevented) {
493
+ composer.setInput(event.currentTarget.value);
494
+ composer.setEntities([]);
495
+ if (autoResize) {
496
+ resizeComposerInput(event.currentTarget, { maxRows, minRows });
497
+ }
498
+ }
499
+ },
500
+ [autoResize, composer, maxRows, minRows, onChange]
501
+ );
502
+ const handleKeyDown = useCallback(
503
+ (event) => {
504
+ onKeyDown?.(event);
505
+ if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || event.nativeEvent.isComposing) {
506
+ return;
507
+ }
508
+ event.preventDefault();
509
+ void composer.submit();
510
+ },
511
+ [composer, onKeyDown]
512
+ );
513
+ return renderPrimitive(
514
+ "textarea",
515
+ {
516
+ ...props,
517
+ "aria-label": props["aria-label"] ?? "Message",
518
+ disabled: props.disabled ?? composer.status === "streaming",
519
+ onChange: handleChange,
520
+ onKeyDown: handleKeyDown,
521
+ rows: autoResize ? minRows : rows,
522
+ value: composer.input,
523
+ "data-anvia-composer-input": "",
524
+ "data-anvia-composer-textarea-input": ""
525
+ },
526
+ composedInputRef
527
+ );
528
+ }
529
+ );
530
+ var COMPOSER_ENTITY_NODE = "composerEntity";
531
+ var EMPTY_COMPOSER_TRIGGERS = [];
532
+ var ComposerEntityExtension = Mention.extend({
533
+ name: COMPOSER_ENTITY_NODE,
534
+ addAttributes() {
535
+ return {
536
+ id: {
537
+ default: null,
538
+ parseHTML: (element) => element.getAttribute("data-id"),
539
+ renderHTML: (attributes) => attributes.id === void 0 || attributes.id === null ? {} : { "data-id": String(attributes.id) }
540
+ },
541
+ label: {
542
+ default: null,
543
+ parseHTML: (element) => element.getAttribute("data-label"),
544
+ renderHTML: (attributes) => attributes.label === void 0 || attributes.label === null ? {} : { "data-label": String(attributes.label) }
545
+ },
546
+ mentionSuggestionChar: {
547
+ default: "@",
548
+ parseHTML: (element) => element.getAttribute("data-mention-suggestion-char"),
549
+ renderHTML: (attributes) => ({
550
+ "data-mention-suggestion-char": attributes.mentionSuggestionChar
551
+ })
552
+ },
553
+ triggerId: {
554
+ default: null,
555
+ parseHTML: (element) => element.getAttribute("data-trigger-id"),
556
+ renderHTML: (attributes) => attributes.triggerId === void 0 || attributes.triggerId === null ? {} : { "data-trigger-id": String(attributes.triggerId) }
557
+ },
558
+ text: {
559
+ default: null,
560
+ parseHTML: (element) => element.getAttribute("data-text"),
561
+ renderHTML: (attributes) => attributes.text === void 0 || attributes.text === null ? {} : { "data-text": String(attributes.text) }
562
+ },
563
+ data: {
564
+ default: null,
565
+ parseHTML: (element) => dataFromAttribute(element.getAttribute("data-entity-data")),
566
+ renderHTML: (attributes) => attributes.data === void 0 || attributes.data === null ? {} : { "data-entity-data": JSON.stringify(attributes.data) }
567
+ }
568
+ };
569
+ }
570
+ });
571
+ function composerInputExtensions({
572
+ composerRef,
573
+ placeholder,
574
+ triggers
575
+ }) {
576
+ return [
577
+ Document,
578
+ Paragraph,
579
+ Text,
580
+ HardBreak,
581
+ Placeholder.configure(placeholder === void 0 ? {} : { placeholder }),
582
+ ComposerEntityExtension.configure({
583
+ HTMLAttributes: {
584
+ "data-anvia-composer-entity": ""
585
+ },
586
+ deleteTriggerWithBackspace: true,
587
+ renderText: ({ node }) => composerEntityText(node.attrs),
588
+ renderHTML: ({ node, options }) => [
589
+ "span",
590
+ mergeAttributes(options.HTMLAttributes, {
591
+ "data-anvia-composer-entity": ""
592
+ }),
593
+ composerEntityText(node.attrs)
594
+ ],
595
+ suggestions: triggers.map((trigger) => composerSuggestion(trigger, composerRef))
596
+ })
597
+ ];
598
+ }
599
+ function submitComposerFromEditorKeyDown(event, composerRef) {
600
+ if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || event.isComposing || composerRef.current.activeTrigger !== void 0) {
601
+ return false;
602
+ }
603
+ event.preventDefault();
604
+ void composerRef.current.submit();
605
+ return true;
606
+ }
607
+ function composerSuggestion(trigger, composerRef) {
608
+ const pluginKey = new PluginKey(`anvia-composer-trigger-${trigger.id}`);
609
+ return {
610
+ pluginKey,
611
+ char: trigger.char,
612
+ ...trigger.allowSpaces === void 0 ? {} : { allowSpaces: trigger.allowSpaces },
613
+ ...trigger.allowedPrefixes === void 0 ? {} : { allowedPrefixes: trigger.allowedPrefixes },
614
+ ...trigger.startOfLine === void 0 ? {} : { startOfLine: trigger.startOfLine },
615
+ ...trigger.minQueryLength === void 0 ? {} : { minQueryLength: trigger.minQueryLength },
616
+ items: ({ query, signal }) => resolveTriggerItems(trigger, query, composerRef.current, signal),
617
+ command: ({
618
+ editor,
619
+ range,
620
+ props
621
+ }) => {
622
+ const nodeAfter = editor.view.state.selection.$to.nodeAfter;
623
+ const overrideSpace = nodeAfter?.text?.startsWith(" ");
624
+ const insertRange = {
625
+ from: range.from,
626
+ to: overrideSpace ? range.to + 1 : range.to
627
+ };
628
+ editor.chain().focus().insertContentAt(insertRange, [
629
+ {
630
+ type: COMPOSER_ENTITY_NODE,
631
+ attrs: props
632
+ },
633
+ {
634
+ type: "text",
635
+ text: " "
636
+ }
637
+ ]).run();
638
+ editor.view.dom.ownerDocument.defaultView?.getSelection()?.collapseToEnd();
639
+ composerRef.current.setActiveTrigger(void 0);
640
+ },
641
+ render: () => ({
642
+ onStart: (props) => {
643
+ updateComposerTriggerState(trigger, props, composerRef);
644
+ },
645
+ onUpdate: (props) => {
646
+ updateComposerTriggerState(trigger, props, composerRef);
647
+ },
648
+ onExit: () => {
649
+ composerRef.current.setActiveTrigger(
650
+ (current) => current?.trigger.id === trigger.id ? void 0 : current
651
+ );
652
+ },
653
+ onKeyDown: (props) => handleComposerTriggerKeyDown(trigger, props, composerRef)
654
+ })
655
+ };
656
+ }
657
+ function resolveTriggerItems(trigger, query, composer, signal) {
658
+ if (Array.isArray(trigger.items)) {
659
+ const normalizedQuery = query.toLowerCase();
660
+ return trigger.items.filter((item) => item.label.toLowerCase().includes(normalizedQuery));
661
+ }
662
+ return trigger.items({
663
+ trigger,
664
+ query,
665
+ input: composer.input,
666
+ entities: composer.entities,
667
+ signal
668
+ });
669
+ }
670
+ function updateComposerTriggerState(trigger, props, composerRef) {
671
+ const current = composerRef.current.activeTrigger;
672
+ const selectedIndex = current?.trigger.id === trigger.id ? clampIndex(current.selectedIndex, props.items.length) : 0;
673
+ const rect = rectFromSuggestion(props);
674
+ composerRef.current.setActiveTrigger({
675
+ trigger,
676
+ query: props.query,
677
+ items: props.items,
678
+ loading: props.loading,
679
+ selectedIndex,
680
+ ...rect === void 0 ? {} : { rect },
681
+ selectItem: (item) => {
682
+ if (item.disabled) {
683
+ return;
684
+ }
685
+ props.command(triggerItemToEntityAttrs(trigger, item));
686
+ },
687
+ setSelectedIndex: (index) => {
688
+ composerRef.current.setActiveTrigger(
689
+ (active) => active?.trigger.id === trigger.id ? { ...active, selectedIndex: clampIndex(index, active.items.length) } : active
690
+ );
691
+ }
692
+ });
693
+ }
694
+ function handleComposerTriggerKeyDown(trigger, { event }, composerRef) {
695
+ const activeTrigger = composerRef.current.activeTrigger;
696
+ if (activeTrigger?.trigger.id !== trigger.id) {
697
+ return false;
698
+ }
699
+ if (event.key === "ArrowDown") {
700
+ event.preventDefault();
701
+ activeTrigger.setSelectedIndex(activeTrigger.selectedIndex + 1);
702
+ return true;
703
+ }
704
+ if (event.key === "ArrowUp") {
705
+ event.preventDefault();
706
+ activeTrigger.setSelectedIndex(activeTrigger.selectedIndex - 1);
707
+ return true;
708
+ }
709
+ if (event.key === "Enter" || event.key === "Tab") {
710
+ event.preventDefault();
711
+ const item = activeTrigger.items[activeTrigger.selectedIndex];
712
+ if (item !== void 0) {
713
+ activeTrigger.selectItem(item);
714
+ }
715
+ return true;
716
+ }
717
+ if (event.key === "Escape") {
718
+ composerRef.current.setActiveTrigger(void 0);
719
+ return false;
720
+ }
721
+ return false;
722
+ }
723
+ function triggerItemToEntityAttrs(trigger, item) {
724
+ return {
725
+ id: item.id,
726
+ label: item.label,
727
+ mentionSuggestionChar: trigger.char,
728
+ triggerId: trigger.id,
729
+ text: triggerItemText(trigger, item),
730
+ data: item.data
731
+ };
732
+ }
733
+ function triggerItemText(trigger, item) {
734
+ return item.text ?? `${trigger.char}${item.label}`;
735
+ }
736
+ function rectFromSuggestion(props) {
737
+ const rect = props.clientRect?.();
738
+ if (rect === void 0 || rect === null) {
739
+ return void 0;
740
+ }
741
+ return {
742
+ top: rect.top,
743
+ right: rect.right,
744
+ bottom: rect.bottom,
745
+ left: rect.left,
746
+ width: rect.width,
747
+ height: rect.height
748
+ };
749
+ }
750
+ function clampIndex(index, length) {
751
+ if (length <= 0) {
752
+ return 0;
753
+ }
754
+ return (index + length) % length;
755
+ }
756
+ function plainTextToComposerContent(input) {
757
+ const lines = input.length === 0 ? [""] : input.split(/\r?\n/);
758
+ return {
759
+ type: "doc",
760
+ content: lines.map((line) => ({
761
+ type: "paragraph",
762
+ ...line.length === 0 ? {} : {
763
+ content: [
764
+ {
765
+ type: "text",
766
+ text: line
767
+ }
768
+ ]
769
+ }
770
+ }))
771
+ };
772
+ }
773
+ function composerSnapshotFromEditor(editor, previousEntities, triggers) {
774
+ return composerSnapshotFromContent(editor.getJSON(), previousEntities, triggers);
775
+ }
776
+ function composerSnapshotFromContent(content, previousEntities, triggers) {
777
+ const entities = [];
778
+ let input = "";
779
+ let blockIndex = 0;
780
+ const appendNode = (node) => {
781
+ if (node.type === "doc") {
782
+ for (const child of node.content ?? []) {
783
+ appendNode(child);
784
+ }
785
+ return;
786
+ }
787
+ if (node.type === "paragraph") {
788
+ if (blockIndex > 0) {
789
+ input += "\n";
790
+ }
791
+ blockIndex += 1;
792
+ for (const child of node.content ?? []) {
793
+ appendNode(child);
794
+ }
795
+ return;
796
+ }
797
+ if (node.type === "text") {
798
+ input += node.text ?? "";
799
+ return;
800
+ }
801
+ if (node.type === "hardBreak") {
802
+ input += "\n";
803
+ return;
804
+ }
805
+ if (node.type === COMPOSER_ENTITY_NODE) {
806
+ const attrs = node.attrs;
807
+ const entity = composerEntityFromAttrs(attrs, input.length, previousEntities, triggers);
808
+ input += entity.text;
809
+ entities.push(entity);
810
+ return;
811
+ }
812
+ for (const child of node.content ?? []) {
813
+ appendNode(child);
814
+ }
815
+ };
816
+ appendNode(content);
817
+ return { input, entities };
818
+ }
819
+ function composerEntityFromAttrs(attrs, from, previousEntities, triggers) {
820
+ const id = stringAttr(attrs?.id) ?? "";
821
+ const trigger = stringAttr(attrs?.mentionSuggestionChar) ?? "@";
822
+ const triggerId = stringAttr(attrs?.triggerId) ?? triggers.find((candidate) => candidate.char === trigger)?.id ?? trigger;
823
+ const label = stringAttr(attrs?.label) ?? id;
824
+ const text = composerEntityText(attrs);
825
+ const previous = previousEntities.find(
826
+ (entity) => entity.id === id && entity.triggerId === triggerId && entity.trigger === trigger
827
+ );
828
+ const data = attrs?.data ?? previous?.data;
829
+ return {
830
+ id,
831
+ triggerId,
832
+ trigger,
833
+ label,
834
+ text,
835
+ range: {
836
+ from,
837
+ to: from + text.length
838
+ },
839
+ ...data === void 0 ? {} : { data }
840
+ };
841
+ }
842
+ function composerEntityText(attrs) {
843
+ const text = stringAttr(attrs?.text);
844
+ if (text !== void 0) {
845
+ return text;
846
+ }
847
+ const trigger = stringAttr(attrs?.mentionSuggestionChar) ?? "@";
848
+ return `${trigger}${stringAttr(attrs?.label) ?? stringAttr(attrs?.id) ?? ""}`;
849
+ }
850
+ function stringAttr(value) {
851
+ return typeof value === "string" ? value : void 0;
852
+ }
853
+ function dataFromAttribute(value) {
854
+ if (value === null) {
855
+ return null;
856
+ }
857
+ try {
858
+ return JSON.parse(value);
859
+ } catch {
860
+ return null;
861
+ }
862
+ }
329
863
  function resizeComposerInput(input, { maxRows, minRows }) {
330
864
  if (input === null) {
331
865
  return;
@@ -384,6 +918,19 @@ function normalizeQuote(quote) {
384
918
  messageId: quote.messageId
385
919
  };
386
920
  }
921
+ function composerSubmitMetadata(quote, entities) {
922
+ if (quote === void 0 && entities.length === 0) {
923
+ return void 0;
924
+ }
925
+ return {
926
+ ...quote === void 0 ? {} : { quote },
927
+ ...entities.length === 0 ? {} : {
928
+ composer: {
929
+ entities
930
+ }
931
+ }
932
+ };
933
+ }
387
934
  var ComposerSubmit = forwardRef(
388
935
  function ComposerSubmit2(props, ref) {
389
936
  const composer = useComposer();
@@ -429,6 +976,84 @@ var ComposerStop = forwardRef(function ComposerStop2({ onClick, ...props }, ref)
429
976
  ref
430
977
  );
431
978
  });
979
+ var ComposerTriggerMenu = forwardRef(
980
+ function ComposerTriggerMenu2({ children, keepMounted = false, style, ...props }, ref) {
981
+ const composer = useComposer();
982
+ const activeTrigger = composer.activeTrigger;
983
+ const empty = activeTrigger === void 0;
984
+ if (empty && !keepMounted) {
985
+ return null;
986
+ }
987
+ const menuStyle = activeTrigger?.rect === void 0 ? style : {
988
+ position: "fixed",
989
+ top: activeTrigger.rect.bottom,
990
+ left: activeTrigger.rect.left,
991
+ ...style
992
+ };
993
+ const renderedChildren = activeTrigger === void 0 ? typeof children === "function" ? null : children : typeof children === "function" ? children(activeTrigger) : children ?? activeTrigger.items.map((item, index) => /* @__PURE__ */ jsx(ComposerTriggerItem, { index }, `${item.id}:${index.toString()}`));
994
+ return renderPrimitive(
995
+ "div",
996
+ {
997
+ ...props,
998
+ children: renderedChildren,
999
+ role: props.role ?? "listbox",
1000
+ style: menuStyle,
1001
+ "data-anvia-composer-trigger-menu": "",
1002
+ "data-empty": empty ? "" : void 0,
1003
+ "data-loading": activeTrigger?.loading ? "" : void 0,
1004
+ "data-trigger": activeTrigger?.trigger.char,
1005
+ "data-trigger-id": activeTrigger?.trigger.id
1006
+ },
1007
+ ref
1008
+ );
1009
+ }
1010
+ );
1011
+ var ComposerTriggerItem = forwardRef(
1012
+ function ComposerTriggerItem2({ children, index, item: itemProp, onClick, onMouseEnter, ...props }, ref) {
1013
+ const composer = useComposer();
1014
+ const activeTrigger = composer.activeTrigger;
1015
+ const itemIndex = index ?? 0;
1016
+ const item = itemProp ?? activeTrigger?.items[itemIndex];
1017
+ if (activeTrigger === void 0 || item === void 0) {
1018
+ return null;
1019
+ }
1020
+ const selected = activeTrigger.selectedIndex === itemIndex;
1021
+ const disabled = props.disabled ?? item.disabled ?? false;
1022
+ const renderedChildren = typeof children === "function" ? children(item, activeTrigger) : children ?? item.label;
1023
+ const handleMouseEnter = (event) => {
1024
+ onMouseEnter?.(event);
1025
+ if (!event.defaultPrevented && !disabled) {
1026
+ activeTrigger.setSelectedIndex(itemIndex);
1027
+ }
1028
+ };
1029
+ const handleClick = (event) => {
1030
+ onClick?.(event);
1031
+ if (!event.defaultPrevented && !disabled) {
1032
+ activeTrigger.selectItem(item);
1033
+ }
1034
+ };
1035
+ return renderPrimitive(
1036
+ "button",
1037
+ {
1038
+ ...props,
1039
+ children: renderedChildren,
1040
+ disabled,
1041
+ onClick: handleClick,
1042
+ onMouseEnter: handleMouseEnter,
1043
+ role: props.role ?? "option",
1044
+ type: props.type ?? "button",
1045
+ "aria-selected": selected,
1046
+ "data-anvia-composer-trigger-item": "",
1047
+ "data-disabled": disabled ? "" : void 0,
1048
+ "data-item-id": item.id,
1049
+ "data-selected": selected ? "" : void 0,
1050
+ "data-trigger": activeTrigger.trigger.char,
1051
+ "data-trigger-id": activeTrigger.trigger.id
1052
+ },
1053
+ ref
1054
+ );
1055
+ }
1056
+ );
432
1057
  var ComposerAttachments = forwardRef(
433
1058
  function ComposerAttachments2({ children, keepMounted = false, ...props }, ref) {
434
1059
  const composer = useComposer();
@@ -635,6 +1260,9 @@ var Composer = {
635
1260
  Quote: ComposerQuotePreview,
636
1261
  ClearQuote: ComposerClearQuote,
637
1262
  Input: ComposerInput,
1263
+ TextareaInput: ComposerTextareaInput,
1264
+ TriggerMenu: ComposerTriggerMenu,
1265
+ TriggerItem: ComposerTriggerItem,
638
1266
  Attachments: ComposerAttachments,
639
1267
  AddAttachment: ComposerAddAttachment,
640
1268
  AttachmentInput: ComposerAttachmentInput,
@@ -924,4 +1552,4 @@ export {
924
1552
  Composer,
925
1553
  Thread
926
1554
  };
927
- //# sourceMappingURL=chunk-LRAQW3ZK.js.map
1555
+ //# sourceMappingURL=chunk-ZQ327YX4.js.map