@adatechnology/conversations-ui 0.6.0 → 0.7.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.
Files changed (88) hide show
  1. package/README.md +126 -19
  2. package/dist/{chunk-HOKNKXIT.js → chunk-HMCOTZAX.js} +1 -1
  3. package/dist/{chunk-FAQLC3TX.js → chunk-L3OFAQPS.js} +2 -4
  4. package/dist/{chunk-CBYEHPX6.js → chunk-PJNR6UMN.js} +63 -1
  5. package/dist/flows/index.js +3 -5
  6. package/dist/index.js +834 -764
  7. package/dist/participant/index.d.ts +18 -1
  8. package/dist/participant/index.js +1188 -313
  9. package/dist/preview/index.js +2 -3
  10. package/dist/styles.css +591 -19
  11. package/package.json +1 -1
  12. package/src/cssBlocks.test-helper.ts +31 -0
  13. package/src/legacyStyles.golden.test.ts +2 -31
  14. package/src/participant/ParticipantAuthorAvatar.test.tsx +30 -0
  15. package/src/participant/ParticipantAuthorAvatar.tsx +24 -0
  16. package/src/participant/ParticipantBackButton.tsx +14 -0
  17. package/src/participant/ParticipantBubbleStatus.tsx +67 -0
  18. package/src/participant/ParticipantChannelBadges.tsx +13 -4
  19. package/src/participant/ParticipantComposer.tsx +8 -40
  20. package/src/participant/ParticipantComposerControls.tsx +83 -0
  21. package/src/participant/ParticipantConversationAvatar.tsx +13 -0
  22. package/src/participant/ParticipantConversations.tsx +7 -0
  23. package/src/participant/ParticipantConversationsOptions.test.tsx +87 -0
  24. package/src/participant/ParticipantCopyAnnouncer.tsx +48 -0
  25. package/src/participant/ParticipantCopyableToken.tsx +51 -0
  26. package/src/participant/ParticipantDayDivider.tsx +18 -0
  27. package/src/participant/ParticipantInbox.tsx +3 -1
  28. package/src/participant/ParticipantInboxRow.tsx +6 -4
  29. package/src/participant/ParticipantMessageBubble.test.tsx +52 -5
  30. package/src/participant/ParticipantMessageBubble.tsx +25 -81
  31. package/src/participant/ParticipantMessageNodes.tsx +54 -0
  32. package/src/participant/ParticipantMessageText.test.tsx +106 -0
  33. package/src/participant/ParticipantMessageText.tsx +52 -0
  34. package/src/participant/ParticipantProtocolBadge.tsx +15 -7
  35. package/src/participant/ParticipantProtocolView.test.tsx +4 -6
  36. package/src/participant/ParticipantScrollToLatest.test.tsx +153 -0
  37. package/src/participant/ParticipantScrollToLatest.tsx +40 -0
  38. package/src/participant/ParticipantThread.test.tsx +37 -0
  39. package/src/participant/ParticipantThread.tsx +45 -95
  40. package/src/participant/ParticipantThreadAvatarSequence.test.tsx +65 -0
  41. package/src/participant/ParticipantThreadHeader.test.tsx +181 -0
  42. package/src/participant/ParticipantThreadHeader.tsx +73 -0
  43. package/src/participant/ParticipantThreadScreen.tsx +24 -10
  44. package/src/participant/ParticipantTimeline.tsx +103 -0
  45. package/src/participant/index.ts +1 -0
  46. package/src/participant/participantAutoGrow.test.ts +43 -0
  47. package/src/participant/participantAutoGrow.ts +12 -0
  48. package/src/participant/participantAvatar.test.ts +74 -0
  49. package/src/participant/participantAvatar.ts +48 -0
  50. package/src/participant/participantBubbleTail.test.tsx +107 -0
  51. package/src/participant/participantChatDesign.test.tsx +198 -0
  52. package/src/participant/participantCopyAnnouncement.test.ts +52 -0
  53. package/src/participant/participantCopyAnnouncement.ts +37 -0
  54. package/src/participant/participantCss.test-helper.ts +34 -0
  55. package/src/participant/participantDay.test.ts +22 -0
  56. package/src/participant/participantDay.ts +8 -0
  57. package/src/participant/participantDocuments.ts +45 -0
  58. package/src/participant/participantHeaderA11y.test.tsx +85 -0
  59. package/src/participant/participantHeaderDesign.test.tsx +111 -0
  60. package/src/participant/participantLabels.ts +11 -0
  61. package/src/participant/participantMessageAtoms.ts +53 -0
  62. package/src/participant/participantMessageCopyables.test.ts +141 -0
  63. package/src/participant/participantMessageCopyables.ts +76 -0
  64. package/src/participant/participantMessageFormat.test-helper.ts +21 -0
  65. package/src/participant/participantMessageFormat.test.ts +177 -0
  66. package/src/participant/participantMessageFormat.ts +135 -0
  67. package/src/participant/participantMessageFormat.types.ts +22 -0
  68. package/src/participant/participantMessageInline.test.ts +82 -0
  69. package/src/participant/participantMessageInline.ts +99 -0
  70. package/src/participant/participantMessageLinks.test.ts +100 -0
  71. package/src/participant/participantMessageLinks.ts +54 -0
  72. package/src/participant/participantMessageTextStyle.test.ts +137 -0
  73. package/src/participant/participantScrollFocus.test.ts +22 -0
  74. package/src/participant/participantScrollFocus.ts +10 -0
  75. package/src/participant/participantScrollView.test.ts +74 -0
  76. package/src/participant/participantScrollView.ts +38 -0
  77. package/src/participant/participantScrollbars.test.ts +156 -0
  78. package/src/participant/participantSubjectIcon.test.tsx +27 -0
  79. package/src/participant/participantSubjectIcon.ts +26 -0
  80. package/src/participant/useParticipantCopyFeedback.ts +33 -0
  81. package/src/participant/useParticipantThreadScroll.ts +10 -2
  82. package/src/participant/useRevealActiveChip.ts +20 -0
  83. package/src/participant/useScrollAwayState.ts +24 -0
  84. package/src/participant/useStickToBottom.ts +16 -2
  85. package/src/participantLayoutShrink.test.ts +82 -0
  86. package/src/styles.css +140 -21
  87. package/dist/chunk-MRVFFJEH.js +0 -64
  88. package/dist/chunk-OVFCLRQQ.js +0 -84
@@ -1,6 +1,3 @@
1
- import {
2
- MessageText
3
- } from "../chunk-OVFCLRQQ.js";
4
1
  import {
5
2
  StatusTicks,
6
3
  channelCapabilityFor,
@@ -8,10 +5,9 @@ import {
8
5
  formatTimestamp,
9
6
  isSameDay
10
7
  } from "../chunk-O6457XHK.js";
11
- import "../chunk-CBYEHPX6.js";
12
8
 
13
9
  // src/participant/ParticipantConversationsScreen.tsx
14
- import { useMemo as useMemo4, useReducer as useReducer3, useRef as useRef7, useState as useState7 } from "react";
10
+ import { useMemo as useMemo5, useReducer as useReducer5, useRef as useRef9, useState as useState7 } from "react";
15
11
 
16
12
  // src/participant/participantLoadView.ts
17
13
  function resolveLoadView({ status, hasItems }) {
@@ -37,6 +33,7 @@ var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
37
33
  me: "Me",
38
34
  newMessages: "New messages",
39
35
  loadOlder: "Load older messages",
36
+ scrollToLatest: "Scroll to latest message",
40
37
  closedNotice: "This conversation is closed",
41
38
  messageInputLabel: "Message",
42
39
  messageInputPlaceholder: "Write a message\u2026",
@@ -75,7 +72,10 @@ var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
75
72
  channelWhatsapp: "WhatsApp",
76
73
  channelEmail: "Email",
77
74
  channelPortal: "Portal",
78
- channelWebchat: "Web chat"
75
+ channelWebchat: "Web chat",
76
+ externalLinkHint: "opens in a new tab",
77
+ copyValue: "Copy",
78
+ valueCopied: "Copied"
79
79
  };
80
80
  function formatParticipantLabel(template, count) {
81
81
  return template.replace("{count}", String(count));
@@ -110,14 +110,19 @@ var ICONS = {
110
110
  Globe,
111
111
  MessagesSquare
112
112
  };
113
- function BadgeContent({ descriptor, labels, separator }) {
113
+ function BadgeContent({ descriptor, labels, separator, iconSize }) {
114
114
  const Icon = ICONS[descriptor.iconName];
115
115
  return /* @__PURE__ */ jsxs(Fragment, { children: [
116
- /* @__PURE__ */ jsx(Icon, { size: 14, "aria-hidden": true }),
116
+ /* @__PURE__ */ jsx(Icon, { size: iconSize, "aria-hidden": true }),
117
117
  /* @__PURE__ */ jsx("span", { className: "cv-p-sr-only", children: `${resolveParticipantChannelLabel(descriptor, labels)}${separator}` })
118
118
  ] });
119
119
  }
120
- function ParticipantChannelBadges({ channels, labels, variant = "list" }) {
120
+ function ParticipantChannelBadges({
121
+ channels,
122
+ labels,
123
+ variant = "list",
124
+ iconSize = 14
125
+ }) {
121
126
  const descriptors = resolveParticipantChannels(channels);
122
127
  if (descriptors.length === 0) return null;
123
128
  if (variant === "inline") {
@@ -128,12 +133,27 @@ function ParticipantChannelBadges({ channels, labels, variant = "list" }) {
128
133
  {
129
134
  descriptor,
130
135
  labels,
131
- separator: index < descriptors.length - 1 ? ", " : ""
136
+ separator: index < descriptors.length - 1 ? ", " : "",
137
+ iconSize
132
138
  }
133
139
  ) }, descriptor.channel))
134
140
  ] });
135
141
  }
136
- return /* @__PURE__ */ jsx("ul", { className: "cv-p-channels", "aria-label": labels.channelsGroup, children: descriptors.map((descriptor) => /* @__PURE__ */ jsx("li", { className: `cv-p-channel cv-p-channel--${descriptor.channel}`, children: /* @__PURE__ */ jsx(BadgeContent, { descriptor, labels, separator: "" }) }, descriptor.channel)) });
142
+ return /* @__PURE__ */ jsx("ul", { className: "cv-p-channels", "aria-label": labels.channelsGroup, children: descriptors.map((descriptor) => /* @__PURE__ */ jsx("li", { className: `cv-p-channel cv-p-channel--${descriptor.channel}`, children: /* @__PURE__ */ jsx(BadgeContent, { descriptor, labels, separator: "", iconSize }) }, descriptor.channel)) });
143
+ }
144
+
145
+ // src/participant/participantSubjectIcon.ts
146
+ function isDrawable(node) {
147
+ return node !== null && node !== void 0 && node !== false && node !== "";
148
+ }
149
+ function resolveConversationIcon({
150
+ conversation,
151
+ group,
152
+ renderSubjectIcon
153
+ }) {
154
+ const custom = renderSubjectIcon?.(conversation);
155
+ if (isDrawable(custom)) return custom;
156
+ return isDrawable(group?.icon) ? group?.icon : void 0;
137
157
  }
138
158
 
139
159
  // src/participant/ParticipantInboxRow.tsx
@@ -144,11 +164,12 @@ function formatRowTime(iso, locale) {
144
164
  return date.toLocaleDateString(locale, { day: "2-digit", month: "2-digit" });
145
165
  }
146
166
  function RowIcon({ props, kind }) {
147
- const custom = props.renderSubjectIcon?.(props.conversation);
148
- if (custom !== null && custom !== void 0 && custom !== false && custom !== "") {
149
- return /* @__PURE__ */ jsx2("span", { className: "cv-p-row__icon cv-p-inbox__icon", "aria-hidden": "true", children: custom });
167
+ const { conversation, group, renderSubjectIcon } = props;
168
+ const hostIcon = renderSubjectIcon?.(conversation);
169
+ if (isDrawable(hostIcon)) {
170
+ return /* @__PURE__ */ jsx2("span", { className: "cv-p-row__icon cv-p-inbox__icon", "aria-hidden": "true", children: hostIcon });
150
171
  }
151
- return /* @__PURE__ */ jsx2("span", { className: "cv-p-row__icon", "aria-hidden": "true", children: props.group?.icon ?? kind.slice(0, 2) });
172
+ return /* @__PURE__ */ jsx2("span", { className: "cv-p-row__icon", "aria-hidden": "true", children: resolveConversationIcon({ conversation, group }) ?? kind.slice(0, 2) });
152
173
  }
153
174
  function ParticipantInboxRow(props) {
154
175
  const { conversation, group, labels, locale, onSelect } = props;
@@ -220,6 +241,41 @@ function ParticipantLoading({ labels }) {
220
241
  ] });
221
242
  }
222
243
 
244
+ // src/participant/useRevealActiveChip.ts
245
+ import { useEffect, useRef } from "react";
246
+
247
+ // src/participant/participantScrollView.ts
248
+ var BADGE_CAP = 99;
249
+ function resolveScrollToLatestView(params) {
250
+ const { isAwayFromBottom, newMessagesCount, hasMessages } = params;
251
+ if (!isAwayFromBottom || !hasMessages) return { isVisible: false, badge: void 0 };
252
+ if (newMessagesCount <= 0) return { isVisible: true, badge: void 0 };
253
+ return { isVisible: true, badge: newMessagesCount > BADGE_CAP ? `${BADGE_CAP}+` : String(newMessagesCount) };
254
+ }
255
+ function scrollToLatestOptions(params) {
256
+ return { top: params.scrollHeight, behavior: params.isReducedMotion ? "auto" : "smooth" };
257
+ }
258
+ function scrollIntoViewOptions(isReducedMotion) {
259
+ return { block: "nearest", inline: "nearest", behavior: isReducedMotion ? "auto" : "smooth" };
260
+ }
261
+ function readPrefersReducedMotion() {
262
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
263
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
264
+ }
265
+
266
+ // src/participant/useRevealActiveChip.ts
267
+ function useRevealActiveChip(active) {
268
+ const strip = useRef(null);
269
+ const previousActive = useRef(void 0);
270
+ useEffect(() => {
271
+ const wasActive = previousActive.current;
272
+ previousActive.current = active;
273
+ if (wasActive === void 0 || wasActive === active) return;
274
+ strip.current?.querySelector('[aria-pressed="true"]')?.scrollIntoView(scrollIntoViewOptions(readPrefersReducedMotion()));
275
+ }, [active]);
276
+ return strip;
277
+ }
278
+
223
279
  // src/participant/ParticipantInbox.tsx
224
280
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
225
281
  var ALL_FILTER = "all";
@@ -234,7 +290,8 @@ function sectionTitle(section, props) {
234
290
  return props.subjectGroups.find((group) => group.subjectType === section.key)?.label ?? section.key;
235
291
  }
236
292
  function Filters({ filters, active, labels, onChange }) {
237
- return /* @__PURE__ */ jsx5("div", { className: "cv-p-filters", role: "group", "aria-label": labels.filtersGroup, children: filters.map((filter) => /* @__PURE__ */ jsxs5(
293
+ const strip = useRevealActiveChip(active);
294
+ return /* @__PURE__ */ jsx5("div", { className: "cv-p-filters", role: "group", "aria-label": labels.filtersGroup, ref: strip, children: filters.map((filter) => /* @__PURE__ */ jsxs5(
238
295
  "button",
239
296
  {
240
297
  type: "button",
@@ -304,10 +361,90 @@ function ParticipantInbox(props) {
304
361
  }
305
362
 
306
363
  // src/participant/ParticipantThreadScreen.tsx
307
- import { useEffect as useEffect8, useMemo as useMemo3, useState as useState6 } from "react";
364
+ import { useEffect as useEffect11, useMemo as useMemo4, useState as useState6 } from "react";
365
+
366
+ // src/participant/ParticipantBackButton.tsx
367
+ import { ArrowLeft } from "lucide-react";
368
+ import { jsx as jsx6 } from "react/jsx-runtime";
369
+ function ParticipantBackButton({ label, onBack }) {
370
+ return /* @__PURE__ */ jsx6("button", { type: "button", className: "cv-p-thread__back", "aria-label": label, onClick: onBack, children: /* @__PURE__ */ jsx6(ArrowLeft, { size: 24, "aria-hidden": "true", focusable: "false" }) });
371
+ }
308
372
 
309
373
  // src/participant/ParticipantComposer.tsx
310
- import { useRef, useState } from "react";
374
+ import { useState } from "react";
375
+
376
+ // src/participant/ParticipantComposerControls.tsx
377
+ import { useLayoutEffect, useRef as useRef2 } from "react";
378
+ import { Paperclip, Send } from "lucide-react";
379
+
380
+ // src/participant/participantAutoGrow.ts
381
+ function fitTextareaHeight(target) {
382
+ target.style.height = "auto";
383
+ target.style.height = `${target.scrollHeight + (target.offsetHeight - target.clientHeight)}px`;
384
+ }
385
+
386
+ // src/participant/ParticipantComposerControls.tsx
387
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
388
+ function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
389
+ const fileInputRef = useRef2(null);
390
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
391
+ /* @__PURE__ */ jsx7(
392
+ "input",
393
+ {
394
+ ref: fileInputRef,
395
+ className: "cv-p-composer__file-input",
396
+ type: "file",
397
+ multiple: true,
398
+ hidden: true,
399
+ accept: acceptedTypes?.join(","),
400
+ onChange: onChosen
401
+ }
402
+ ),
403
+ /* @__PURE__ */ jsx7(
404
+ "button",
405
+ {
406
+ type: "button",
407
+ className: "cv-p-composer__attach",
408
+ "aria-label": labels.attach,
409
+ disabled,
410
+ onClick: () => fileInputRef.current?.click(),
411
+ children: /* @__PURE__ */ jsx7(Paperclip, { size: 24, "aria-hidden": "true", focusable: "false" })
412
+ }
413
+ )
414
+ ] });
415
+ }
416
+ function SendButton({ label, disabled }) {
417
+ return /* @__PURE__ */ jsx7(
418
+ "button",
419
+ {
420
+ type: "submit",
421
+ className: "cv-p-button cv-p-button--primary cv-p-composer__send",
422
+ "aria-label": label,
423
+ disabled,
424
+ children: /* @__PURE__ */ jsx7(Send, { size: 20, "aria-hidden": "true", focusable: "false" })
425
+ }
426
+ );
427
+ }
428
+ function MessageField({ value, label, placeholder, disabled, maxLength, onChange }) {
429
+ const fieldRef = useRef2(null);
430
+ useLayoutEffect(() => {
431
+ if (fieldRef.current) fitTextareaHeight(fieldRef.current);
432
+ }, [value]);
433
+ return /* @__PURE__ */ jsx7(
434
+ "textarea",
435
+ {
436
+ ref: fieldRef,
437
+ className: "cv-p-composer__input",
438
+ "aria-label": label,
439
+ placeholder,
440
+ rows: 1,
441
+ value,
442
+ maxLength,
443
+ disabled,
444
+ onChange: (event) => onChange(event.target.value)
445
+ }
446
+ );
447
+ }
311
448
 
312
449
  // src/participant/participantFileValidation.ts
313
450
  function matchesAcceptedType(mimeType, acceptedTypes) {
@@ -342,45 +479,24 @@ function applyQuickReply(currentText, replyText) {
342
479
  }
343
480
 
344
481
  // src/participant/ParticipantComposer.tsx
345
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
482
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
346
483
  function QuickReplyStrip({ quickReplies, labels, onPick }) {
347
- return /* @__PURE__ */ jsx6("div", { className: "cv-p-quick", role: "group", "aria-label": labels.quickRepliesGroup, children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx6("button", { type: "button", className: "cv-p-chip", onClick: () => onPick(quickReply.body), children: quickReply.title }, quickReply.id)) });
484
+ return /* @__PURE__ */ jsx8("div", { className: "cv-p-quick", role: "group", "aria-label": labels.quickRepliesGroup, children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx8("button", { type: "button", className: "cv-p-chip", onClick: () => onPick(quickReply.body), children: quickReply.title }, quickReply.id)) });
348
485
  }
349
486
  function FileList({ files, labels, onRemove }) {
350
487
  if (files.length === 0) return null;
351
- return /* @__PURE__ */ jsx6("ul", { className: "cv-p-files", "aria-label": labels.attachmentsList, children: files.map((file, index) => /* @__PURE__ */ jsxs6("li", { className: "cv-p-files__item", children: [
352
- /* @__PURE__ */ jsx6("span", { className: "cv-p-files__name", children: file.name }),
353
- /* @__PURE__ */ jsx6("span", { className: "cv-p-files__size", children: formatFileSize(file.size) }),
354
- /* @__PURE__ */ jsxs6("button", { type: "button", className: "cv-p-files__remove", "aria-label": `${labels.removeAttachment}: ${file.name}`, onClick: () => onRemove(file), children: [
355
- /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\xD7" }),
356
- /* @__PURE__ */ jsx6("span", { className: "cv-p-sr-only", children: labels.removeAttachment })
488
+ return /* @__PURE__ */ jsx8("ul", { className: "cv-p-files", "aria-label": labels.attachmentsList, children: files.map((file, index) => /* @__PURE__ */ jsxs7("li", { className: "cv-p-files__item", children: [
489
+ /* @__PURE__ */ jsx8("span", { className: "cv-p-files__name", children: file.name }),
490
+ /* @__PURE__ */ jsx8("span", { className: "cv-p-files__size", children: formatFileSize(file.size) }),
491
+ /* @__PURE__ */ jsxs7("button", { type: "button", className: "cv-p-files__remove", "aria-label": `${labels.removeAttachment}: ${file.name}`, onClick: () => onRemove(file), children: [
492
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\xD7" }),
493
+ /* @__PURE__ */ jsx8("span", { className: "cv-p-sr-only", children: labels.removeAttachment })
357
494
  ] })
358
495
  ] }, `${file.name}-${file.size}-${index}`)) });
359
496
  }
360
497
  function rejectionText(rejection, labels) {
361
498
  return rejection === "tooLarge" ? labels.attachmentTooLarge : labels.attachmentTypeNotAccepted;
362
499
  }
363
- function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
364
- const fileInputRef = useRef(null);
365
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
366
- /* @__PURE__ */ jsx6(
367
- "input",
368
- {
369
- ref: fileInputRef,
370
- className: "cv-p-composer__file-input",
371
- type: "file",
372
- multiple: true,
373
- hidden: true,
374
- accept: acceptedTypes?.join(","),
375
- onChange: onChosen
376
- }
377
- ),
378
- /* @__PURE__ */ jsxs6("button", { type: "button", className: "cv-p-button", disabled, onClick: () => fileInputRef.current?.click(), children: [
379
- /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\uFF0B" }),
380
- /* @__PURE__ */ jsx6("span", { className: "cv-p-sr-only", children: labels.attach })
381
- ] })
382
- ] });
383
- }
384
500
  function useFileSelection(props) {
385
501
  const [rejection, setRejection] = useState(void 0);
386
502
  const { files, onFilesChange, channel } = props;
@@ -406,51 +522,737 @@ function ParticipantComposer(props) {
406
522
  event.preventDefault();
407
523
  if (canSend) onSend();
408
524
  }
409
- return /* @__PURE__ */ jsxs6("form", { className: "cv-p-composer", onSubmit: handleSubmit, children: [
410
- props.quickReplies && props.quickReplies.length > 0 ? /* @__PURE__ */ jsx6(QuickReplyStrip, { quickReplies: props.quickReplies, labels, onPick: (body) => onChange(applyQuickReply(value, body)) }) : null,
411
- /* @__PURE__ */ jsx6(FileList, { files, labels, onRemove: handleRemoveFile }),
412
- rejection ? /* @__PURE__ */ jsx6("p", { className: "cv-p-composer__error", role: "alert", children: rejectionText(rejection, labels) }) : null,
413
- /* @__PURE__ */ jsxs6("div", { className: "cv-p-composer__row", children: [
414
- channelCapabilityFor(channel).attachments.accepted ? /* @__PURE__ */ jsx6(AttachControl, { labels, disabled, acceptedTypes: props.acceptedTypes, onChosen: handleFilesChosen }) : null,
415
- /* @__PURE__ */ jsx6(
416
- "textarea",
525
+ return /* @__PURE__ */ jsxs7("form", { className: "cv-p-composer", onSubmit: handleSubmit, children: [
526
+ props.quickReplies && props.quickReplies.length > 0 ? /* @__PURE__ */ jsx8(QuickReplyStrip, { quickReplies: props.quickReplies, labels, onPick: (body) => onChange(applyQuickReply(value, body)) }) : null,
527
+ /* @__PURE__ */ jsx8(FileList, { files, labels, onRemove: handleRemoveFile }),
528
+ rejection ? /* @__PURE__ */ jsx8("p", { className: "cv-p-composer__error", role: "alert", children: rejectionText(rejection, labels) }) : null,
529
+ /* @__PURE__ */ jsxs7("div", { className: "cv-p-composer__row", children: [
530
+ channelCapabilityFor(channel).attachments.accepted ? /* @__PURE__ */ jsx8(AttachControl, { labels, disabled, acceptedTypes: props.acceptedTypes, onChosen: handleFilesChosen }) : null,
531
+ /* @__PURE__ */ jsx8(
532
+ MessageField,
417
533
  {
418
- className: "cv-p-composer__input",
419
- "aria-label": labels.messageInputLabel,
420
- placeholder: labels.messageInputPlaceholder,
421
- rows: 1,
422
534
  value,
423
- maxLength: props.maxLength,
535
+ label: labels.messageInputLabel,
536
+ placeholder: labels.messageInputPlaceholder,
424
537
  disabled,
425
- onChange: (event) => onChange(event.target.value)
538
+ maxLength: props.maxLength,
539
+ onChange
426
540
  }
427
541
  ),
428
- /* @__PURE__ */ jsxs6("button", { type: "submit", className: "cv-p-button cv-p-button--primary", disabled: !canSend, children: [
429
- /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\u27A4" }),
430
- /* @__PURE__ */ jsx6("span", { className: "cv-p-sr-only", children: labels.send })
431
- ] })
542
+ /* @__PURE__ */ jsx8(SendButton, { label: labels.send, disabled: !canSend })
432
543
  ] })
433
544
  ] });
434
545
  }
435
546
 
436
- // src/participant/participantBubbleActions.ts
437
- function bindPendingActions(actions, clientMessageId) {
438
- const { retryHost } = actions;
439
- return {
440
- retryLocal: () => actions.retryLocal(clientMessageId),
441
- discardLocal: () => actions.discardLocal(clientMessageId),
442
- editLocal: () => actions.editLocal(clientMessageId),
443
- ...retryHost ? { retryHost: () => retryHost(clientMessageId) } : {}
547
+ // src/participant/ParticipantCopyAnnouncer.tsx
548
+ import { createContext, useCallback, useContext, useEffect as useEffect2, useReducer, useRef as useRef3 } from "react";
549
+
550
+ // src/participant/participantCopyAnnouncement.ts
551
+ var INITIAL_COPY_ANNOUNCEMENT = { text: "", tick: 0 };
552
+ var REPEAT_MARKER = "\u200B";
553
+ function copyAnnouncementReducer(state, action) {
554
+ if (action.type === "announce") return { text: action.text, tick: state.tick + 1 };
555
+ return { ...state, text: "" };
556
+ }
557
+ function resolveLiveText(params) {
558
+ const { copy, notice, baselineNotice } = params;
559
+ if (copy.text !== "") {
560
+ const marker = copy.tick % 2 === 0 ? REPEAT_MARKER : "";
561
+ return { text: `${copy.text}${marker}`, baselineNotice: baselineNotice ?? notice };
562
+ }
563
+ if (baselineNotice !== void 0 && baselineNotice === notice) return { text: "", baselineNotice };
564
+ return { text: notice, baselineNotice: void 0 };
565
+ }
566
+
567
+ // src/participant/ParticipantCopyAnnouncer.tsx
568
+ import { jsx as jsx9 } from "react/jsx-runtime";
569
+ var ANNOUNCEMENT_MS = 3e3;
570
+ var AnnounceContext = createContext(() => void 0);
571
+ var AnnouncementContext = createContext(INITIAL_COPY_ANNOUNCEMENT);
572
+ function useAnnounceCopy() {
573
+ return useContext(AnnounceContext);
574
+ }
575
+ function ParticipantCopyAnnouncer({ children }) {
576
+ const [announcement, dispatch] = useReducer(copyAnnouncementReducer, INITIAL_COPY_ANNOUNCEMENT);
577
+ const announce = useCallback((text) => dispatch({ type: "announce", text }), []);
578
+ useEffect2(() => {
579
+ if (announcement.text === "") return void 0;
580
+ const timer = setTimeout(() => dispatch({ type: "expired" }), ANNOUNCEMENT_MS);
581
+ return () => clearTimeout(timer);
582
+ }, [announcement.tick, announcement.text]);
583
+ return /* @__PURE__ */ jsx9(AnnounceContext.Provider, { value: announce, children: /* @__PURE__ */ jsx9(AnnouncementContext.Provider, { value: announcement, children }) });
584
+ }
585
+ function ParticipantThreadLive({ notice }) {
586
+ const copy = useContext(AnnouncementContext);
587
+ const baselineNotice = useRef3(void 0);
588
+ const resolution = resolveLiveText({ copy, notice, baselineNotice: baselineNotice.current });
589
+ baselineNotice.current = resolution.baselineNotice;
590
+ return /* @__PURE__ */ jsx9("div", { className: "cv-p-thread__live", role: "status", "aria-live": "polite", children: resolution.text });
591
+ }
592
+
593
+ // src/participant/ParticipantScrollToLatest.tsx
594
+ import { ChevronDown } from "lucide-react";
595
+
596
+ // src/participant/participantScrollFocus.ts
597
+ var SCROLLER_SELECTOR = ".cv-p-thread__scroll";
598
+ function focusScrollerOf(button) {
599
+ button.closest(SCROLLER_SELECTOR)?.focus({ preventScroll: true });
600
+ }
601
+
602
+ // src/participant/ParticipantScrollToLatest.tsx
603
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
604
+ function ParticipantScrollToLatest({
605
+ label,
606
+ isAwayFromBottom,
607
+ hasMessages,
608
+ newMessagesCount,
609
+ onClick
610
+ }) {
611
+ const { isVisible, badge } = resolveScrollToLatestView({ isAwayFromBottom, newMessagesCount, hasMessages });
612
+ if (!isVisible || onClick === void 0) return null;
613
+ return /* @__PURE__ */ jsx10("div", { className: "cv-p-thread__latest", children: /* @__PURE__ */ jsxs8(
614
+ "button",
615
+ {
616
+ type: "button",
617
+ className: "cv-p-thread__latest-button",
618
+ "aria-label": label,
619
+ onClick: (event) => {
620
+ focusScrollerOf(event.currentTarget);
621
+ onClick();
622
+ },
623
+ children: [
624
+ /* @__PURE__ */ jsx10(ChevronDown, { size: 24, "aria-hidden": "true", focusable: "false" }),
625
+ badge ? /* @__PURE__ */ jsx10("span", { className: "cv-p-thread__latest-badge", "aria-hidden": "true", children: badge }) : null
626
+ ]
627
+ }
628
+ ) });
629
+ }
630
+
631
+ // src/participant/ParticipantAuthorAvatar.tsx
632
+ import { User } from "lucide-react";
633
+
634
+ // src/participant/participantAvatar.ts
635
+ var NAME_PARTICLES = /* @__PURE__ */ new Set([
636
+ "a",
637
+ "e",
638
+ "o",
639
+ "da",
640
+ "das",
641
+ "de",
642
+ "del",
643
+ "di",
644
+ "do",
645
+ "dos",
646
+ "du",
647
+ "la",
648
+ "le",
649
+ "van",
650
+ "von"
651
+ ]);
652
+ function lettersOf(word) {
653
+ return word.replace(/[^\p{L}\p{M}]/gu, "");
654
+ }
655
+ function firstLetter(word) {
656
+ return (Array.from(word.normalize("NFC"))[0] ?? "").toUpperCase();
657
+ }
658
+ function authorInitials(name) {
659
+ const [first, ...rest] = (name ?? "").split(/\s+/).map(lettersOf).filter(Boolean);
660
+ if (first === void 0) return void 0;
661
+ const last = rest.filter((word) => !NAME_PARTICLES.has(word.toLowerCase())).pop();
662
+ return `${firstLetter(first)}${last === void 0 ? "" : firstLetter(last)}`;
663
+ }
664
+ function shouldShowAvatar({ previousAuthor, author, isMine }) {
665
+ if (isMine) return false;
666
+ if (previousAuthor === void 0) return true;
667
+ return previousAuthor !== author;
668
+ }
669
+
670
+ // src/participant/ParticipantAuthorAvatar.tsx
671
+ import { jsx as jsx11 } from "react/jsx-runtime";
672
+ function ParticipantAuthorAvatar({ name, render }) {
673
+ const fromHost = render?.({ name });
674
+ const hasHostContent = !(fromHost == null || fromHost === false || fromHost === "");
675
+ const initials = authorInitials(name);
676
+ const fallback = initials ?? /* @__PURE__ */ jsx11(User, { size: 16, "aria-hidden": true });
677
+ return /* @__PURE__ */ jsx11("span", { className: "cv-p-avatar", "aria-hidden": "true", children: hasHostContent ? fromHost : fallback });
678
+ }
679
+
680
+ // src/participant/participantDay.ts
681
+ function pad(value) {
682
+ return String(value).padStart(2, "0");
683
+ }
684
+ function toDateTimeAttribute(date) {
685
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
686
+ }
687
+
688
+ // src/participant/ParticipantDayDivider.tsx
689
+ import { jsx as jsx12 } from "react/jsx-runtime";
690
+ function ParticipantDayDivider({ day, locale }) {
691
+ const label = day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" });
692
+ return /* @__PURE__ */ jsx12("div", { className: "cv-p-day", role: "separator", "aria-label": label, children: /* @__PURE__ */ jsx12("time", { className: "cv-p-day__label", dateTime: toDateTimeAttribute(day), children: label }) });
693
+ }
694
+
695
+ // src/participant/ParticipantBubbleStatus.tsx
696
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
697
+ function statusText(status, labels) {
698
+ const byStatus = {
699
+ sending: labels.statusSending,
700
+ queued: labels.statusQueued,
701
+ sent: labels.statusSent,
702
+ delivered: labels.statusDelivered,
703
+ read: labels.statusRead,
704
+ failed: labels.statusFailed
444
705
  };
706
+ return byStatus[status];
445
707
  }
446
- function resolveParticipantBubbleActions(item, handlers) {
447
- if (item.kind === "server" || item.displayState !== "failed") return {};
448
- if (item.origin === "host") return handlers.retryHost ? { onRetry: handlers.retryHost } : {};
449
- return { onRetry: handlers.retryLocal, onDiscard: handlers.discardLocal, onEdit: handlers.editLocal };
708
+ function OwnStatus({ status, labels, onRetry }) {
709
+ const ticksStatus = status === "sending" ? "queued" : status;
710
+ const isVisibleText = status === "sending" || status === "queued" || status === "failed";
711
+ if (status === "failed" && onRetry) {
712
+ return /* @__PURE__ */ jsxs9("button", { type: "button", className: "cv-p-bubble__retry", onClick: onRetry, children: [
713
+ /* @__PURE__ */ jsx13(StatusTicks, { status: "failed", appearance: "stylesheet" }),
714
+ /* @__PURE__ */ jsx13("span", { children: labels.statusFailed })
715
+ ] });
716
+ }
717
+ const text = status === "failed" ? labels.statusFailedShort : statusText(status, labels);
718
+ return /* @__PURE__ */ jsxs9("span", { className: "cv-p-bubble__status", children: [
719
+ /* @__PURE__ */ jsx13(StatusTicks, { status: ticksStatus, appearance: "stylesheet" }),
720
+ /* @__PURE__ */ jsx13("span", { className: isVisibleText ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
721
+ ] });
722
+ }
723
+ function FailedActions({ labels, onDiscard, onEdit }) {
724
+ if (!onDiscard && !onEdit) return null;
725
+ return /* @__PURE__ */ jsxs9("span", { className: "cv-p-bubble__actions", children: [
726
+ onEdit ? /* @__PURE__ */ jsx13("button", { type: "button", className: "cv-p-bubble__action", onClick: onEdit, children: labels.edit }) : null,
727
+ onDiscard ? /* @__PURE__ */ jsx13("button", { type: "button", className: "cv-p-bubble__action", onClick: onDiscard, children: labels.discard }) : null
728
+ ] });
729
+ }
730
+
731
+ // src/participant/ParticipantMessageText.tsx
732
+ import { useMemo } from "react";
733
+
734
+ // src/participant/ParticipantCopyableToken.tsx
735
+ import { Check, Copy } from "lucide-react";
736
+
737
+ // src/participant/useParticipantCopyFeedback.ts
738
+ import { useEffect as useEffect3, useReducer as useReducer2 } from "react";
739
+
740
+ // src/participant/participantProtocolCopy.ts
741
+ async function copyProtocolToClipboard(protocol, clipboard) {
742
+ if (clipboard === void 0) return false;
743
+ try {
744
+ await clipboard.writeText(protocol);
745
+ return true;
746
+ } catch {
747
+ return false;
748
+ }
749
+ }
750
+ var INITIAL_PROTOCOL_COPY_STATE = { copyCount: 0, isCopied: false };
751
+ function protocolCopyReducer(state, action) {
752
+ if (action.type === "copied") return { copyCount: state.copyCount + 1, isCopied: true };
753
+ return { ...state, isCopied: false };
754
+ }
755
+
756
+ // src/participant/useParticipantCopyFeedback.ts
757
+ var COPIED_FEEDBACK_MS = 2e3;
758
+ function useParticipantCopyFeedback() {
759
+ const [state, dispatch] = useReducer2(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
760
+ const announce = useAnnounceCopy();
761
+ const { isCopied, copyCount } = state;
762
+ useEffect3(() => {
763
+ if (!isCopied) return void 0;
764
+ const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_FEEDBACK_MS);
765
+ return () => clearTimeout(timer);
766
+ }, [isCopied, copyCount]);
767
+ async function copyValue(value, announcement) {
768
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
769
+ if (!await copyProtocolToClipboard(value, clipboard)) return;
770
+ dispatch({ type: "copied" });
771
+ announce(announcement);
772
+ }
773
+ return { isCopied, copy: (value, announcement) => void copyValue(value, announcement) };
774
+ }
775
+
776
+ // src/participant/ParticipantCopyableToken.tsx
777
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
778
+ function ParticipantCopyableToken({ copyKind, value, labels }) {
779
+ const { isCopied, copy } = useParticipantCopyFeedback();
780
+ return /* @__PURE__ */ jsxs10(
781
+ "button",
782
+ {
783
+ type: "button",
784
+ className: `cv-p-copyable cv-p-copyable--${copyKind}`,
785
+ "aria-label": `${labels.copyValue} ${value}`,
786
+ onClick: () => copy(value, labels.valueCopied),
787
+ children: [
788
+ value,
789
+ isCopied ? /* @__PURE__ */ jsxs10("span", { className: "cv-p-copyable__done", "aria-hidden": "true", children: [
790
+ /* @__PURE__ */ jsx14(Check, { size: 12, "aria-hidden": true }),
791
+ " ",
792
+ labels.valueCopied
793
+ ] }) : null
794
+ ]
795
+ }
796
+ );
797
+ }
798
+ function ParticipantLinkCopyButton({ value, labels }) {
799
+ const { isCopied, copy } = useParticipantCopyFeedback();
800
+ return /* @__PURE__ */ jsx14(
801
+ "button",
802
+ {
803
+ type: "button",
804
+ className: "cv-p-text__link-copy",
805
+ "aria-label": `${labels.copyValue} ${value}`,
806
+ onClick: () => copy(value, labels.valueCopied),
807
+ children: isCopied ? /* @__PURE__ */ jsx14(Check, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx14(Copy, { size: 14, "aria-hidden": true })
808
+ }
809
+ );
810
+ }
811
+
812
+ // src/participant/ParticipantMessageNodes.tsx
813
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
814
+ var LINK_REL = "noopener noreferrer nofollow ugc";
815
+ function renderLink(node, labels, key) {
816
+ const isExternal = /^https?:/i.test(node.href);
817
+ return /* @__PURE__ */ jsxs11("span", { className: "cv-p-text__link-group", children: [
818
+ /* @__PURE__ */ jsxs11("a", { className: "cv-p-text__link", href: node.href, rel: LINK_REL, target: isExternal ? "_blank" : void 0, children: [
819
+ node.label,
820
+ isExternal ? /* @__PURE__ */ jsxs11("span", { className: "cv-p-sr-only", children: [
821
+ " (",
822
+ labels.externalLinkHint,
823
+ ")"
824
+ ] }) : null
825
+ ] }),
826
+ /* @__PURE__ */ jsx15(ParticipantLinkCopyButton, { value: node.href, labels })
827
+ ] }, key);
828
+ }
829
+ function renderInlineNodes(nodes, labels) {
830
+ return nodes.map((node, key) => {
831
+ switch (node.kind) {
832
+ case "text":
833
+ return node.value;
834
+ case "strong":
835
+ return /* @__PURE__ */ jsx15("strong", { children: renderInlineNodes(node.children, labels) }, key);
836
+ case "em":
837
+ return /* @__PURE__ */ jsx15("em", { children: renderInlineNodes(node.children, labels) }, key);
838
+ case "del":
839
+ return /* @__PURE__ */ jsx15("del", { children: renderInlineNodes(node.children, labels) }, key);
840
+ case "code":
841
+ return /* @__PURE__ */ jsx15("code", { className: "cv-p-text__code", children: node.value }, key);
842
+ case "link":
843
+ return renderLink(node, labels, key);
844
+ case "copyable":
845
+ return /* @__PURE__ */ jsx15(ParticipantCopyableToken, { copyKind: node.copyKind, value: node.value, labels }, key);
846
+ }
847
+ });
848
+ }
849
+
850
+ // src/participant/participantDocuments.ts
851
+ var CNPJ_FIRST_WEIGHTS = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
852
+ var CNPJ_SECOND_WEIGHTS = [6, ...CNPJ_FIRST_WEIGHTS];
853
+ function charValue(character) {
854
+ return character.charCodeAt(0) - 48;
855
+ }
856
+ function isRepeatedSequence(value) {
857
+ return new Set(value).size === 1;
858
+ }
859
+ function weightedSum(characters, weights) {
860
+ let sum = 0;
861
+ for (let index = 0; index < weights.length; index += 1) {
862
+ sum += charValue(characters.charAt(index)) * (weights[index] ?? 0);
863
+ }
864
+ return sum;
865
+ }
866
+ function cnpjCheckDigit(characters, weights) {
867
+ const remainder = weightedSum(characters, weights) % 11;
868
+ return remainder < 2 ? 0 : 11 - remainder;
869
+ }
870
+ function cpfCheckDigit(digits, length) {
871
+ const weights = Array.from({ length }, (_, index) => length + 1 - index);
872
+ return weightedSum(digits, weights) * 10 % 11 % 10;
873
+ }
874
+ function isValidCpf(digits) {
875
+ if (!/^\d{11}$/.test(digits) || isRepeatedSequence(digits)) return false;
876
+ return cpfCheckDigit(digits, 9) === charValue(digits.charAt(9)) && cpfCheckDigit(digits, 10) === charValue(digits.charAt(10));
877
+ }
878
+ function isValidCnpj(characters) {
879
+ if (!/^[A-Z0-9]{12}\d{2}$/.test(characters) || isRepeatedSequence(characters)) return false;
880
+ const first = cnpjCheckDigit(characters, CNPJ_FIRST_WEIGHTS);
881
+ if (first !== charValue(characters.charAt(12))) return false;
882
+ return cnpjCheckDigit(characters, CNPJ_SECOND_WEIGHTS) === charValue(characters.charAt(13));
883
+ }
884
+
885
+ // src/participant/participantMessageCopyables.ts
886
+ var DOCUMENT_SEPARATORS = /[.\-/\s]/g;
887
+ var ACCESS_KEY_PATTERN = /\d{44}|\d{4}(?: \d{4}){10}/gu;
888
+ var CNPJ_PATTERN = /[A-Z0-9]{2}\.[A-Z0-9]{3}\.[A-Z0-9]{3}\/[A-Z0-9]{4}-\d{2}|[A-Z0-9]{12}\d{2}/gu;
889
+ var CPF_PATTERN = /\d{3}\.\d{3}\.\d{3}-\d{2}|\d{11}/gu;
890
+ var BRAZIL_PHONE_PATTERN = /(?:\+?55[\s.-]?)?(?:\([1-9][1-9]\)|[1-9][1-9])[\s.-]?(?:9\d{4}|[2-5]\d{3})[\s.-]?\d{4}/gu;
891
+ var INTERNATIONAL_PHONE_PATTERN = /\+\d{1,3}(?:[\s.-]?\d{2,5}){2,4}/gu;
892
+ var EMAIL_LOCAL = /^[\p{L}\p{N}._%+-]+$/u;
893
+ var EMAIL_DOMAIN = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)*\.\p{L}{2,}$/u;
894
+ function bare(raw) {
895
+ return raw.replace(DOCUMENT_SEPARATORS, "");
896
+ }
897
+ function isWordCharacter(character) {
898
+ return character !== void 0 && /[\p{L}\p{N}]/u.test(character);
899
+ }
900
+ function hasPhoneDigitCount(raw) {
901
+ const count = raw.replace(/\D/g, "").length;
902
+ return count >= 10 && count <= 15;
903
+ }
904
+ function isFormattedBrazilPhone(raw) {
905
+ return /^\+|\(|^\d{2}[\s.-]/u.test(raw) && hasPhoneDigitCount(raw);
906
+ }
907
+ var SCANNERS = [
908
+ { copyKind: "accessKey", pattern: ACCESS_KEY_PATTERN, isValid: (raw) => !/^(\d)\1+$/.test(bare(raw)) },
909
+ { copyKind: "cnpj", pattern: CNPJ_PATTERN, isValid: (raw) => isValidCnpj(bare(raw)) },
910
+ { copyKind: "cpf", pattern: CPF_PATTERN, isValid: (raw) => isValidCpf(bare(raw)) },
911
+ { copyKind: "phone", pattern: BRAZIL_PHONE_PATTERN, isValid: isFormattedBrazilPhone },
912
+ {
913
+ copyKind: "phone",
914
+ pattern: INTERNATIONAL_PHONE_PATTERN,
915
+ isValid: (raw) => hasPhoneDigitCount(raw) && !raw.startsWith("+55")
916
+ }
917
+ ];
918
+ function scan(text, scanner) {
919
+ const atoms = [];
920
+ for (const match of text.matchAll(scanner.pattern)) {
921
+ const [value] = match;
922
+ const start = match.index;
923
+ const end = start + value.length;
924
+ if (isWordCharacter(text[start - 1]) || isWordCharacter(text[end])) continue;
925
+ if (!scanner.isValid(value)) continue;
926
+ atoms.push({ start, end, node: { kind: "copyable", copyKind: scanner.copyKind, value } });
927
+ }
928
+ return atoms;
929
+ }
930
+ function findEmailAtoms(text) {
931
+ const atoms = [];
932
+ for (let at = text.indexOf("@"); at !== -1; at = text.indexOf("@", at + 1)) {
933
+ let start = at;
934
+ while (start > 0 && /[\p{L}\p{N}._%+-]/u.test(text[start - 1] ?? "")) start -= 1;
935
+ let end = at + 1;
936
+ while (end < text.length && /[\p{L}\p{N}.-]/u.test(text[end] ?? "")) end += 1;
937
+ while (end > at + 1 && /[.-]/.test(text[end - 1] ?? "")) end -= 1;
938
+ const isValid = EMAIL_LOCAL.test(text.slice(start, at)) && EMAIL_DOMAIN.test(text.slice(at + 1, end));
939
+ if (isValid) {
940
+ atoms.push({ start, end, node: { kind: "copyable", copyKind: "email", value: text.slice(start, end) } });
941
+ }
942
+ }
943
+ return atoms;
944
+ }
945
+ function findCopyableAtoms(text) {
946
+ return [...SCANNERS.flatMap((scanner) => scan(text, scanner)), ...findEmailAtoms(text)];
947
+ }
948
+
949
+ // src/participant/participantMessageLinks.ts
950
+ var ALLOWED_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
951
+ var URL_PATTERN = /https?:\/\/[^\s<>`\u0000-\u001f]+/giu;
952
+ var TRAILING_PUNCTUATION = `.,;:!?*_~'"]}>`;
953
+ function isAllowedLinkHref(href) {
954
+ try {
955
+ const url = new URL(href);
956
+ return ALLOWED_LINK_PROTOCOLS.has(url.protocol) && url.username === "" && url.password === "";
957
+ } catch {
958
+ return false;
959
+ }
960
+ }
961
+ function isAlphanumeric(character) {
962
+ return character !== void 0 && /[\p{L}\p{N}]/u.test(character);
963
+ }
964
+ function countOf(text, character) {
965
+ let count = 0;
966
+ for (const current of text) if (current === character) count += 1;
967
+ return count;
968
+ }
969
+ function trimUrl(raw) {
970
+ let openCount = countOf(raw, "(");
971
+ let closeCount = countOf(raw, ")");
972
+ let end = raw.length;
973
+ while (end > 0) {
974
+ const last = raw.charAt(end - 1);
975
+ if (last === ")" && closeCount > openCount) closeCount -= 1;
976
+ else if (!TRAILING_PUNCTUATION.includes(last)) break;
977
+ end -= 1;
978
+ }
979
+ return raw.slice(0, end);
980
+ }
981
+ function toLinkAtom(text, start, raw) {
982
+ const label = trimUrl(raw);
983
+ if (/^https?:\/\/$/i.test(label) || isAlphanumeric(text[start - 1])) return void 0;
984
+ const href = label.replace(/^https?/i, (scheme) => scheme.toLowerCase());
985
+ if (!isAllowedLinkHref(href)) return void 0;
986
+ return { start, end: start + label.length, node: { kind: "link", href, label } };
987
+ }
988
+ function findLinkAtoms(text) {
989
+ const atoms = [];
990
+ for (const match of text.matchAll(URL_PATTERN)) {
991
+ const atom = toLinkAtom(text, match.index, match[0]);
992
+ if (atom) atoms.push(atom);
993
+ }
994
+ return atoms;
995
+ }
996
+
997
+ // src/participant/participantMessageAtoms.ts
998
+ var TRIPLE_FENCE = "```";
999
+ function spanAt(text, open) {
1000
+ const isTriple = text.startsWith(TRIPLE_FENCE, open);
1001
+ const width = isTriple ? 3 : 1;
1002
+ const close = text.indexOf(isTriple ? TRIPLE_FENCE : "`", open + width);
1003
+ if (close === -1) return void 0;
1004
+ return { contentStart: open + width, close, width };
1005
+ }
1006
+ function findCodeAtoms(text) {
1007
+ const atoms = [];
1008
+ let cursor = text.indexOf("`");
1009
+ while (cursor !== -1) {
1010
+ const span = spanAt(text, cursor);
1011
+ if (span === void 0) {
1012
+ if (!text.startsWith(TRIPLE_FENCE, cursor)) break;
1013
+ cursor = text.indexOf("`", cursor + 3);
1014
+ continue;
1015
+ }
1016
+ if (span.close === span.contentStart) {
1017
+ cursor = text.indexOf("`", cursor + span.width);
1018
+ continue;
1019
+ }
1020
+ const end = span.close + span.width;
1021
+ atoms.push({ start: cursor, end, node: { kind: "code", value: text.slice(span.contentStart, span.close) } });
1022
+ cursor = text.indexOf("`", end);
1023
+ }
1024
+ return atoms;
1025
+ }
1026
+ function findInlineAtoms(text) {
1027
+ const candidates = [...findCodeAtoms(text), ...findLinkAtoms(text), ...findCopyableAtoms(text)];
1028
+ const ordered = candidates.map((atom, rank) => ({ atom, rank })).sort((a, b) => a.atom.start - b.atom.start || a.rank - b.rank);
1029
+ const accepted = [];
1030
+ let lastEnd = 0;
1031
+ for (const { atom } of ordered) {
1032
+ if (atom.start < lastEnd) continue;
1033
+ accepted.push(atom);
1034
+ lastEnd = atom.end;
1035
+ }
1036
+ return accepted;
1037
+ }
1038
+
1039
+ // src/participant/participantMessageInline.ts
1040
+ var MARKS = { "*": "strong", _: "em", "~": "del" };
1041
+ function isWord(character) {
1042
+ return character !== void 0 && /[\p{L}\p{N}]/u.test(character);
1043
+ }
1044
+ function isSpace(character) {
1045
+ return character === void 0 || /\s/u.test(character);
1046
+ }
1047
+ function characterBefore(text, index) {
1048
+ if (index <= 0) return void 0;
1049
+ const code = text.charCodeAt(index - 1);
1050
+ const isLowSurrogate = code >= 56320 && code <= 57343;
1051
+ if (isLowSurrogate && index >= 2) return String.fromCodePoint(text.codePointAt(index - 2) ?? code);
1052
+ return text.charAt(index - 1);
1053
+ }
1054
+ function buildContext(text) {
1055
+ const atoms = findInlineAtoms(text);
1056
+ const isMasked = new Array(text.length).fill(false);
1057
+ for (const atom of atoms) isMasked.fill(true, atom.start, atom.end);
1058
+ const closers = { "*": [], _: [], "~": [] };
1059
+ for (let index = 1; index < text.length; index += 1) {
1060
+ const character = text.charAt(index);
1061
+ const list = closers[character];
1062
+ const isCloser = list !== void 0 && !isMasked[index] && !isSpace(text[index - 1]) && !isWord(text[index + 1]);
1063
+ if (isCloser) list.push(index);
1064
+ }
1065
+ return { text, atomAt: new Map(atoms.map((atom) => [atom.start, atom])), isMasked, closers };
1066
+ }
1067
+ function isOpener(context, index) {
1068
+ const { text } = context;
1069
+ const next = text[index + 1];
1070
+ if (context.isMasked[index] || next === void 0 || isSpace(next) || next === text[index]) return false;
1071
+ return !isWord(characterBefore(text, index));
1072
+ }
1073
+ function findCloser(context, mark, from, limit) {
1074
+ const list = context.closers[mark] ?? [];
1075
+ let low = 0;
1076
+ let high = list.length;
1077
+ while (low < high) {
1078
+ const middle = low + high >> 1;
1079
+ if ((list[middle] ?? 0) < from) low = middle + 1;
1080
+ else high = middle;
1081
+ }
1082
+ const found = list[low];
1083
+ return found !== void 0 && found < limit ? found : void 0;
1084
+ }
1085
+ function parseRange(context, start, end) {
1086
+ const nodes = [];
1087
+ let textStart = start;
1088
+ let index = start;
1089
+ const flush = (until) => {
1090
+ if (until > textStart) nodes.push({ kind: "text", value: context.text.slice(textStart, until) });
1091
+ };
1092
+ while (index < end) {
1093
+ const atom = context.atomAt.get(index);
1094
+ const mark = MARKS[context.text.charAt(index)];
1095
+ const closer = mark !== void 0 && !atom && isOpener(context, index) ? findCloser(context, context.text.charAt(index), index + 2, end) : void 0;
1096
+ if (atom) {
1097
+ flush(index);
1098
+ nodes.push(atom.node);
1099
+ index = atom.end;
1100
+ } else if (mark !== void 0 && closer !== void 0) {
1101
+ flush(index);
1102
+ nodes.push({ kind: mark, children: parseRange(context, index + 1, closer) });
1103
+ index = closer + 1;
1104
+ } else {
1105
+ index += 1;
1106
+ continue;
1107
+ }
1108
+ textStart = index;
1109
+ }
1110
+ flush(end);
1111
+ return nodes;
1112
+ }
1113
+ function parseInline(line) {
1114
+ return parseRange(buildContext(line), 0, line.length);
1115
+ }
1116
+
1117
+ // src/participant/participantMessageFormat.ts
1118
+ var MAX_FORMATTED_LENGTH = 8e3;
1119
+ var FENCE = "```";
1120
+ var LANGUAGE_LABEL = /^[a-z][a-z0-9+#.-]{0,19}$/i;
1121
+ var QUOTE_PREFIX = /^> /;
1122
+ var BULLET_PREFIX = /^[-*•] +(?=\S)/;
1123
+ var ORDERED_PREFIX = /^(\d{1,3})[.)] +(?=\S)/;
1124
+ function joinLines(lines) {
1125
+ const nodes = [];
1126
+ lines.forEach((line, index) => {
1127
+ if (index > 0) nodes.push({ kind: "text", value: "\n" });
1128
+ nodes.push(...parseInline(line));
1129
+ });
1130
+ return mergeText(nodes);
1131
+ }
1132
+ function mergeText(nodes) {
1133
+ const merged = [];
1134
+ for (const node of nodes) {
1135
+ const previous = merged[merged.length - 1];
1136
+ if (node.kind === "text" && previous?.kind === "text") {
1137
+ merged[merged.length - 1] = { kind: "text", value: previous.value + node.value };
1138
+ } else {
1139
+ merged.push(node);
1140
+ }
1141
+ }
1142
+ return merged;
1143
+ }
1144
+ function plainParagraph(text) {
1145
+ return [{ kind: "paragraph", children: [{ kind: "text", value: text }] }];
1146
+ }
1147
+ function readFence(lines, index) {
1148
+ const first = (lines[index] ?? "").trimStart();
1149
+ if (!first.startsWith(FENCE)) return void 0;
1150
+ const inner = first.slice(FENCE.length);
1151
+ if (inner.trimEnd().endsWith(FENCE) && inner.trim() !== FENCE) {
1152
+ return { value: inner.trimEnd().slice(0, -FENCE.length), nextIndex: index + 1 };
1153
+ }
1154
+ const hasLabel = LANGUAGE_LABEL.test(inner.trim());
1155
+ const body = inner === "" || hasLabel ? [] : [inner];
1156
+ for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
1157
+ const line = (lines[cursor] ?? "").trimEnd();
1158
+ if (!line.endsWith(FENCE)) {
1159
+ body.push(line);
1160
+ continue;
1161
+ }
1162
+ const last = line.slice(0, -FENCE.length);
1163
+ if (last !== "") body.push(last);
1164
+ if (last !== "" && hasLabel && body.length === 1) body.unshift(inner);
1165
+ return { value: body.join("\n"), nextIndex: cursor + 1 };
1166
+ }
1167
+ return void 0;
1168
+ }
1169
+ function startsSpecialBlock(line) {
1170
+ const trimmed = line.trimStart();
1171
+ return trimmed.startsWith(FENCE) || QUOTE_PREFIX.test(line) || BULLET_PREFIX.test(line) || ORDERED_PREFIX.test(line);
1172
+ }
1173
+ function readQuote(lines, index) {
1174
+ const quoted = [];
1175
+ let cursor = index;
1176
+ while (cursor < lines.length && QUOTE_PREFIX.test(lines[cursor] ?? "")) {
1177
+ quoted.push((lines[cursor] ?? "").slice(2));
1178
+ cursor += 1;
1179
+ }
1180
+ return { block: { kind: "quote", children: joinLines(quoted) }, nextIndex: cursor };
1181
+ }
1182
+ function readList(lines, index) {
1183
+ const ordered = ORDERED_PREFIX.test(lines[index] ?? "");
1184
+ const prefix = ordered ? ORDERED_PREFIX : BULLET_PREFIX;
1185
+ const start = ordered ? Number(ORDERED_PREFIX.exec(lines[index] ?? "")?.[1] ?? 1) : 1;
1186
+ const items = [];
1187
+ let cursor = index;
1188
+ while (cursor < lines.length && prefix.test(lines[cursor] ?? "")) {
1189
+ items.push(parseInline((lines[cursor] ?? "").replace(prefix, "")));
1190
+ cursor += 1;
1191
+ }
1192
+ return { block: { kind: "list", ordered, start, items }, nextIndex: cursor };
1193
+ }
1194
+ function readParagraph(lines, index) {
1195
+ const collected = [lines[index] ?? ""];
1196
+ let cursor = index + 1;
1197
+ while (cursor < lines.length && !startsSpecialBlock(lines[cursor] ?? "")) {
1198
+ collected.push(lines[cursor] ?? "");
1199
+ cursor += 1;
1200
+ }
1201
+ return { block: { kind: "paragraph", children: joinLines(collected) }, nextIndex: cursor };
1202
+ }
1203
+ function readBlock(lines, index, canFence) {
1204
+ const line = lines[index] ?? "";
1205
+ const fence = canFence ? readFence(lines, index) : void 0;
1206
+ if (fence) return { block: { kind: "pre", value: fence.value }, nextIndex: fence.nextIndex };
1207
+ if (QUOTE_PREFIX.test(line)) return readQuote(lines, index);
1208
+ if (BULLET_PREFIX.test(line) || ORDERED_PREFIX.test(line)) return readList(lines, index);
1209
+ return readParagraph(lines, index);
1210
+ }
1211
+ function parseParticipantMessage(text) {
1212
+ if (typeof text !== "string" || text.trim() === "") return [];
1213
+ if (text.length > MAX_FORMATTED_LENGTH) return plainParagraph(text);
1214
+ const lines = text.split(/\r\n?|\n/);
1215
+ const blocks = [];
1216
+ let canFence = true;
1217
+ let index = 0;
1218
+ while (index < lines.length) {
1219
+ const { block, nextIndex } = readBlock(lines, index, canFence);
1220
+ if (block.kind === "paragraph" && startsFenceLine(lines[index])) canFence = false;
1221
+ blocks.push(block);
1222
+ index = nextIndex;
1223
+ }
1224
+ return blocks;
1225
+ }
1226
+ function startsFenceLine(line) {
1227
+ return (line ?? "").trimStart().startsWith(FENCE);
1228
+ }
1229
+
1230
+ // src/participant/ParticipantMessageText.tsx
1231
+ import { jsx as jsx16 } from "react/jsx-runtime";
1232
+ function renderBlock(block, labels, key) {
1233
+ if (block.kind === "paragraph") return /* @__PURE__ */ jsx16("span", { children: renderInlineNodes(block.children, labels) }, key);
1234
+ if (block.kind === "pre") {
1235
+ return /* @__PURE__ */ jsx16("pre", { className: "cv-p-text__pre", children: /* @__PURE__ */ jsx16("code", { children: block.value }) }, key);
1236
+ }
1237
+ if (block.kind === "quote") {
1238
+ return /* @__PURE__ */ jsx16("blockquote", { className: "cv-p-text__quote", children: renderInlineNodes(block.children, labels) }, key);
1239
+ }
1240
+ const items = block.items.map((item, index) => /* @__PURE__ */ jsx16("li", { children: renderInlineNodes(item, labels) }, index));
1241
+ if (block.ordered) {
1242
+ return /* @__PURE__ */ jsx16("ol", { className: "cv-p-text__list", start: block.start === 1 ? void 0 : block.start, children: items }, key);
1243
+ }
1244
+ return /* @__PURE__ */ jsx16("ul", { className: "cv-p-text__list", children: items }, key);
1245
+ }
1246
+ function ParticipantMessageText({ text, labels }) {
1247
+ const blocks = useMemo(() => parseParticipantMessage(text), [text]);
1248
+ if (blocks.length === 0) return null;
1249
+ const [only] = blocks;
1250
+ const children = blocks.length === 1 && only?.kind === "paragraph" ? renderInlineNodes(only.children, labels) : blocks.map((block, key) => renderBlock(block, labels, key));
1251
+ return /* @__PURE__ */ jsx16("div", { className: "cv-p-text", children });
450
1252
  }
451
1253
 
452
1254
  // src/participant/ParticipantAttachmentItem.tsx
453
- import { useEffect, useState as useState2 } from "react";
1255
+ import { useEffect as useEffect4, useState as useState2 } from "react";
454
1256
 
455
1257
  // src/participant/participantAttachmentUrl.ts
456
1258
  var SAFE_ATTACHMENT_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:", "blob:"]);
@@ -463,11 +1265,11 @@ function isSafeAttachmentUrl(url) {
463
1265
  }
464
1266
 
465
1267
  // src/participant/ParticipantAttachmentItem.tsx
466
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1268
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
467
1269
  function useAttachmentUrl(attachment, resolveAttachmentUrl) {
468
1270
  const [url, setUrl] = useState2(void 0);
469
1271
  const disposition = attachment.kind === "document" ? "attachment" : "inline";
470
- useEffect(() => {
1272
+ useEffect4(() => {
471
1273
  let isCurrent = true;
472
1274
  resolveAttachmentUrl(attachment, disposition).then(
473
1275
  (resolved) => {
@@ -486,18 +1288,18 @@ function useAttachmentUrl(attachment, resolveAttachmentUrl) {
486
1288
  function ParticipantAttachmentItem({ attachment, resolveAttachmentUrl, labels }) {
487
1289
  const url = useAttachmentUrl(attachment, resolveAttachmentUrl);
488
1290
  if (attachment.kind === "image") {
489
- return /* @__PURE__ */ jsxs7("figure", { className: "cv-p-attachment cv-p-attachment--image", children: [
490
- url ? /* @__PURE__ */ jsx7("img", { className: "cv-p-attachment__image", src: url, alt: attachment.filename }) : null,
491
- /* @__PURE__ */ jsx7("figcaption", { className: "cv-p-attachment__name", children: attachment.filename })
1291
+ return /* @__PURE__ */ jsxs12("figure", { className: "cv-p-attachment cv-p-attachment--image", children: [
1292
+ url ? /* @__PURE__ */ jsx17("img", { className: "cv-p-attachment__image", src: url, alt: attachment.filename }) : null,
1293
+ /* @__PURE__ */ jsx17("figcaption", { className: "cv-p-attachment__name", children: attachment.filename })
492
1294
  ] });
493
1295
  }
494
1296
  if (attachment.kind === "audio") {
495
- return /* @__PURE__ */ jsxs7("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
496
- /* @__PURE__ */ jsx7("span", { className: "cv-p-attachment__name", children: attachment.filename }),
497
- /* @__PURE__ */ jsx7("audio", { className: "cv-p-attachment__audio", controls: true, preload: "none", src: url })
1297
+ return /* @__PURE__ */ jsxs12("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
1298
+ /* @__PURE__ */ jsx17("span", { className: "cv-p-attachment__name", children: attachment.filename }),
1299
+ /* @__PURE__ */ jsx17("audio", { className: "cv-p-attachment__audio", controls: true, preload: "none", src: url })
498
1300
  ] });
499
1301
  }
500
- return /* @__PURE__ */ jsxs7(
1302
+ return /* @__PURE__ */ jsxs12(
501
1303
  "a",
502
1304
  {
503
1305
  className: "cv-p-attachment cv-p-attachment--document",
@@ -505,8 +1307,8 @@ function ParticipantAttachmentItem({ attachment, resolveAttachmentUrl, labels })
505
1307
  download: attachment.filename,
506
1308
  "aria-label": `${labels.downloadAttachment}: ${attachment.filename}`,
507
1309
  children: [
508
- /* @__PURE__ */ jsx7("span", { className: "cv-p-attachment__name", children: attachment.filename }),
509
- /* @__PURE__ */ jsx7("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
1310
+ /* @__PURE__ */ jsx17("span", { className: "cv-p-attachment__name", children: attachment.filename }),
1311
+ /* @__PURE__ */ jsx17("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
510
1312
  ]
511
1313
  }
512
1314
  );
@@ -550,7 +1352,7 @@ function resolveOwnMessageStatus(params) {
550
1352
  }
551
1353
 
552
1354
  // src/participant/ParticipantMessageBubble.tsx
553
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1355
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
554
1356
  function describeItem(item) {
555
1357
  if (item.kind === "server") {
556
1358
  const { message } = item;
@@ -573,48 +1375,11 @@ function describeItem(item) {
573
1375
  displayState: item.displayState
574
1376
  };
575
1377
  }
576
- function statusText(status, labels) {
577
- const byStatus = {
578
- sending: labels.statusSending,
579
- queued: labels.statusQueued,
580
- sent: labels.statusSent,
581
- delivered: labels.statusDelivered,
582
- read: labels.statusRead,
583
- failed: labels.statusFailed
584
- };
585
- return byStatus[status];
586
- }
587
- function OwnStatus({ status, labels, onRetry }) {
588
- const ticksStatus = status === "sending" ? "queued" : status;
589
- const isVisibleText = status === "sending" || status === "queued" || status === "failed";
590
- if (status === "failed" && onRetry) {
591
- return /* @__PURE__ */ jsxs8("button", { type: "button", className: "cv-p-bubble__retry", onClick: onRetry, children: [
592
- /* @__PURE__ */ jsx8(StatusTicks, { status: "failed", appearance: "stylesheet" }),
593
- /* @__PURE__ */ jsx8("span", { children: labels.statusFailed })
594
- ] });
595
- }
596
- const text = status === "failed" ? labels.statusFailedShort : statusText(status, labels);
597
- return /* @__PURE__ */ jsxs8("span", { className: "cv-p-bubble__status", children: [
598
- /* @__PURE__ */ jsx8(StatusTicks, { status: ticksStatus, appearance: "stylesheet" }),
599
- /* @__PURE__ */ jsx8("span", { className: isVisibleText ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
600
- ] });
601
- }
602
- function FailedActions({ labels, onDiscard, onEdit }) {
603
- if (!onDiscard && !onEdit) return null;
604
- return /* @__PURE__ */ jsxs8("span", { className: "cv-p-bubble__actions", children: [
605
- onEdit ? /* @__PURE__ */ jsx8("button", { type: "button", className: "cv-p-bubble__action", onClick: onEdit, children: labels.edit }) : null,
606
- onDiscard ? /* @__PURE__ */ jsx8("button", { type: "button", className: "cv-p-bubble__action", onClick: onDiscard, children: labels.discard }) : null
607
- ] });
608
- }
609
- function toTextPayload(text, createdAt, isMine) {
610
- return {
611
- id: createdAt,
612
- type: "text",
613
- content: text,
614
- direction: isMine ? "inbound" : "outbound",
615
- sender: isMine ? "customer" : "agent",
616
- timestamp: createdAt
617
- };
1378
+ function bubbleClassName(isMine, hasTail) {
1379
+ const classes = ["cv-p-bubble"];
1380
+ if (isMine) classes.push("cv-p-bubble--mine");
1381
+ if (!hasTail) classes.push("cv-p-bubble--no-tail");
1382
+ return classes.join(" ");
618
1383
  }
619
1384
  function ParticipantMessageBubble({
620
1385
  item,
@@ -623,15 +1388,17 @@ function ParticipantMessageBubble({
623
1388
  resolveAttachmentUrl,
624
1389
  onRetry,
625
1390
  onDiscard,
626
- onEdit
1391
+ onEdit,
1392
+ avatar,
1393
+ tail = true
627
1394
  }) {
628
1395
  const content = describeItem(item);
629
1396
  const { isMine } = content;
630
1397
  const ownStatus = isMine ? resolveOwnMessageStatus({ displayState: content.displayState, serverStatus: content.serverStatus, confirmsRead }) : void 0;
631
- return /* @__PURE__ */ jsxs8("div", { className: isMine ? "cv-p-bubble cv-p-bubble--mine" : "cv-p-bubble", children: [
632
- isMine ? /* @__PURE__ */ jsx8("span", { className: "cv-p-sr-only", children: labels.me }) : content.authorName ? /* @__PURE__ */ jsx8("span", { className: "cv-p-bubble__author", children: content.authorName }) : null,
633
- content.text ? /* @__PURE__ */ jsx8(MessageText, { message: toTextPayload(content.text, content.createdAt, isMine), copyOnClick: false, appearance: "stylesheet" }) : null,
634
- content.attachments.map((attachment) => /* @__PURE__ */ jsx8(
1398
+ const bubble = /* @__PURE__ */ jsxs13("div", { className: bubbleClassName(isMine, tail), children: [
1399
+ isMine ? /* @__PURE__ */ jsx18("span", { className: "cv-p-sr-only", children: labels.me }) : content.authorName ? /* @__PURE__ */ jsx18("span", { className: "cv-p-bubble__author", children: content.authorName }) : null,
1400
+ content.text ? /* @__PURE__ */ jsx18(ParticipantMessageText, { text: content.text, labels }) : null,
1401
+ content.attachments.map((attachment) => /* @__PURE__ */ jsx18(
635
1402
  ParticipantAttachmentItem,
636
1403
  {
637
1404
  attachment,
@@ -640,36 +1407,109 @@ function ParticipantMessageBubble({
640
1407
  },
641
1408
  attachment.id
642
1409
  )),
643
- content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx8("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
644
- /* @__PURE__ */ jsxs8("span", { className: "cv-p-bubble__meta", children: [
645
- /* @__PURE__ */ jsx8("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
646
- ownStatus ? /* @__PURE__ */ jsx8(OwnStatus, { status: ownStatus, labels, onRetry }) : null
1410
+ content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx18("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
1411
+ /* @__PURE__ */ jsxs13("span", { className: "cv-p-bubble__meta", children: [
1412
+ /* @__PURE__ */ jsx18("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
1413
+ ownStatus ? /* @__PURE__ */ jsx18(OwnStatus, { status: ownStatus, labels, onRetry }) : null
647
1414
  ] }),
648
- ownStatus === "failed" ? /* @__PURE__ */ jsx8(FailedActions, { labels, onDiscard, onEdit }) : null
1415
+ ownStatus === "failed" ? /* @__PURE__ */ jsx18(FailedActions, { labels, onDiscard, onEdit }) : null
1416
+ ] });
1417
+ if (avatar === void 0 || isMine) return bubble;
1418
+ return /* @__PURE__ */ jsxs13("div", { className: "cv-p-bubble-row", children: [
1419
+ /* @__PURE__ */ jsx18("span", { className: "cv-p-bubble-row__avatar", children: avatar }),
1420
+ bubble
649
1421
  ] });
650
1422
  }
651
1423
 
652
- // src/participant/ParticipantProtocolBadge.tsx
653
- import { useEffect as useEffect2, useReducer } from "react";
1424
+ // src/participant/participantBubbleActions.ts
1425
+ function bindPendingActions(actions, clientMessageId) {
1426
+ const { retryHost } = actions;
1427
+ return {
1428
+ retryLocal: () => actions.retryLocal(clientMessageId),
1429
+ discardLocal: () => actions.discardLocal(clientMessageId),
1430
+ editLocal: () => actions.editLocal(clientMessageId),
1431
+ ...retryHost ? { retryHost: () => retryHost(clientMessageId) } : {}
1432
+ };
1433
+ }
1434
+ function resolveParticipantBubbleActions(item, handlers) {
1435
+ if (item.kind === "server" || item.displayState !== "failed") return {};
1436
+ if (item.origin === "host") return handlers.retryHost ? { onRetry: handlers.retryHost } : {};
1437
+ return { onRetry: handlers.retryLocal, onDiscard: handlers.discardLocal, onEdit: handlers.editLocal };
1438
+ }
654
1439
 
655
- // src/participant/participantProtocolCopy.ts
656
- async function copyProtocolToClipboard(protocol, clipboard) {
657
- if (clipboard === void 0) return false;
658
- try {
659
- await clipboard.writeText(protocol);
660
- return true;
661
- } catch {
662
- return false;
1440
+ // src/participant/ParticipantTimeline.tsx
1441
+ import { jsx as jsx19 } from "react/jsx-runtime";
1442
+ function createdAtOf2(item) {
1443
+ return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
1444
+ }
1445
+ function keyOf(item) {
1446
+ return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
1447
+ }
1448
+ function authorOf(item) {
1449
+ if (item.kind === "pending") return { isMine: true, author: null };
1450
+ return { isMine: isOwnMessage(item.message), author: item.message.authorName ?? null };
1451
+ }
1452
+ function resolveActions(item, pendingActions) {
1453
+ if (!pendingActions || item.kind === "server") return {};
1454
+ return resolveParticipantBubbleActions(item, bindPendingActions(pendingActions, item.pending.clientMessageId));
1455
+ }
1456
+ function resolveAvatarSlot({ isEnabled, isMine, showAvatar, author, render }) {
1457
+ if (!isEnabled || isMine) return void 0;
1458
+ return showAvatar ? /* @__PURE__ */ jsx19(ParticipantAuthorAvatar, { name: author, render }) : null;
1459
+ }
1460
+ function ParticipantTimeline(props) {
1461
+ const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
1462
+ const confirmsRead = channelCapabilityFor(props.channel ?? "app").confirmsRead;
1463
+ const isAvatarEnabled = props.avatars === "initials" || props.renderAuthorAvatar !== void 0;
1464
+ const nodes = [];
1465
+ let previousDay;
1466
+ let previousAuthor;
1467
+ for (const item of items) {
1468
+ const day = new Date(createdAtOf2(item));
1469
+ if (previousDay === void 0 || !isSameDay(previousDay, day)) {
1470
+ nodes.push(/* @__PURE__ */ jsx19(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
1471
+ previousAuthor = void 0;
1472
+ }
1473
+ previousDay = day;
1474
+ const { isMine, author } = authorOf(item);
1475
+ const showAvatar = shouldShowAvatar({ previousAuthor, author, isMine });
1476
+ previousAuthor = isMine ? void 0 : author;
1477
+ const avatar = resolveAvatarSlot({
1478
+ isEnabled: isAvatarEnabled,
1479
+ isMine,
1480
+ showAvatar,
1481
+ author,
1482
+ render: props.renderAuthorAvatar
1483
+ });
1484
+ nodes.push(
1485
+ /* @__PURE__ */ jsx19(
1486
+ ParticipantMessageBubble,
1487
+ {
1488
+ item,
1489
+ labels,
1490
+ confirmsRead,
1491
+ resolveAttachmentUrl,
1492
+ tail: props.tail,
1493
+ avatar,
1494
+ ...resolveActions(item, pendingActions)
1495
+ },
1496
+ keyOf(item)
1497
+ )
1498
+ );
663
1499
  }
1500
+ return /* @__PURE__ */ jsx19("div", { className: "cv-p-thread__messages", children: nodes });
664
1501
  }
665
- var INITIAL_PROTOCOL_COPY_STATE = { copyCount: 0, isCopied: false };
666
- function protocolCopyReducer(state, action) {
667
- if (action.type === "copied") return { copyCount: state.copyCount + 1, isCopied: true };
668
- return { ...state, isCopied: false };
1502
+
1503
+ // src/participant/ParticipantConversationAvatar.tsx
1504
+ import { jsx as jsx20 } from "react/jsx-runtime";
1505
+ function ParticipantConversationAvatar({ icon }) {
1506
+ return /* @__PURE__ */ jsx20("span", { className: "cv-p-thread__avatar", "aria-hidden": "true", children: icon });
669
1507
  }
670
1508
 
671
1509
  // src/participant/ParticipantProtocolBadge.tsx
672
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1510
+ import { Check as Check2, Copy as Copy2 } from "lucide-react";
1511
+ import { useEffect as useEffect5, useReducer as useReducer3 } from "react";
1512
+ import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
673
1513
  var COPIED_ANNOUNCEMENT_MS = 3e3;
674
1514
  function ParticipantProtocolBadgeView({
675
1515
  protocol,
@@ -678,31 +1518,36 @@ function ParticipantProtocolBadgeView({
678
1518
  onCopy
679
1519
  }) {
680
1520
  const { isCopied, copyCount } = copyState;
681
- return /* @__PURE__ */ jsxs9(Fragment4, { children: [
682
- /* @__PURE__ */ jsxs9("div", { className: "cv-p-protocol cv-p-protocol--header", children: [
683
- /* @__PURE__ */ jsxs9("span", { className: "cv-p-protocol__code", children: [
684
- /* @__PURE__ */ jsxs9("span", { className: "cv-p-sr-only", children: [
1521
+ return /* @__PURE__ */ jsxs14(Fragment4, { children: [
1522
+ /* @__PURE__ */ jsxs14("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
1523
+ /* @__PURE__ */ jsxs14("span", { className: "cv-p-protocol__code", children: [
1524
+ /* @__PURE__ */ jsxs14("span", { className: "cv-p-sr-only", children: [
685
1525
  labels.protocolPrefix,
686
1526
  " "
687
1527
  ] }),
688
1528
  protocol
689
1529
  ] }),
690
- /* @__PURE__ */ jsxs9("button", { type: "button", className: "cv-p-protocol__copy", onClick: onCopy, children: [
691
- /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: isCopied ? labels.protocolCopied : labels.copyProtocol }),
692
- /* @__PURE__ */ jsxs9("span", { className: "cv-p-sr-only", children: [
693
- labels.copyProtocol,
694
- " ",
695
- protocol
696
- ] })
697
- ] })
1530
+ /* @__PURE__ */ jsxs14(
1531
+ "button",
1532
+ {
1533
+ type: "button",
1534
+ className: "cv-p-protocol__copy",
1535
+ "aria-label": `${labels.copyProtocol} ${protocol}`,
1536
+ onClick: onCopy,
1537
+ children: [
1538
+ isCopied ? /* @__PURE__ */ jsx21(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx21(Copy2, { size: 18, "aria-hidden": true }),
1539
+ isCopied ? /* @__PURE__ */ jsx21("span", { className: "cv-p-protocol__copied", "aria-hidden": "true", children: labels.protocolCopied }) : null
1540
+ ]
1541
+ }
1542
+ )
698
1543
  ] }),
699
- /* @__PURE__ */ jsx9("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx9("span", { children: labels.protocolCopied }, copyCount) : null })
1544
+ /* @__PURE__ */ jsx21("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx21("span", { children: labels.protocolCopied }, copyCount) : null })
700
1545
  ] });
701
1546
  }
702
1547
  function ParticipantProtocolBadge({ protocol, labels }) {
703
- const [copyState, dispatch] = useReducer(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
1548
+ const [copyState, dispatch] = useReducer3(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
704
1549
  const { isCopied, copyCount } = copyState;
705
- useEffect2(() => {
1550
+ useEffect5(() => {
706
1551
  if (!isCopied) return void 0;
707
1552
  const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_ANNOUNCEMENT_MS);
708
1553
  return () => clearTimeout(timer);
@@ -711,7 +1556,7 @@ function ParticipantProtocolBadge({ protocol, labels }) {
711
1556
  const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
712
1557
  if (await copyProtocolToClipboard(protocol, clipboard)) dispatch({ type: "copied" });
713
1558
  }
714
- return /* @__PURE__ */ jsx9(
1559
+ return /* @__PURE__ */ jsx21(
715
1560
  ParticipantProtocolBadgeView,
716
1561
  {
717
1562
  protocol,
@@ -722,77 +1567,54 @@ function ParticipantProtocolBadge({ protocol, labels }) {
722
1567
  );
723
1568
  }
724
1569
 
725
- // src/participant/ParticipantThread.tsx
726
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
727
- function createdAtOf2(item) {
728
- return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
729
- }
730
- function keyOf(item) {
731
- return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
732
- }
733
- function resolveActions(item, pendingActions) {
734
- if (!pendingActions || item.kind === "server") return {};
735
- return resolveParticipantBubbleActions(item, bindPendingActions(pendingActions, item.pending.clientMessageId));
1570
+ // src/participant/ParticipantThreadHeader.tsx
1571
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
1572
+ function Title({ conversation, labels, onOpenSubject }) {
1573
+ const { subjectType, subjectId, subjectLabel } = conversation;
1574
+ return /* @__PURE__ */ jsx22("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx22(
1575
+ "button",
1576
+ {
1577
+ type: "button",
1578
+ className: "cv-p-thread__title-button",
1579
+ "aria-label": `${labels.openSubject}: ${subjectLabel}`,
1580
+ onClick: () => onOpenSubject({ subjectType, subjectId }),
1581
+ children: /* @__PURE__ */ jsx22("span", { className: "cv-p-thread__title-text", children: subjectLabel })
1582
+ }
1583
+ ) : /* @__PURE__ */ jsx22("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
736
1584
  }
737
- function Header({ props }) {
738
- const { conversation, labels, onBack, onOpenSubject } = props;
739
- const { subjectType, subjectId } = conversation;
740
- return /* @__PURE__ */ jsxs10("header", { className: "cv-p-thread__header", children: [
741
- onBack ? /* @__PURE__ */ jsxs10("button", { type: "button", className: "cv-p-button cv-p-thread__back", onClick: onBack, children: [
742
- /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "\u2039" }),
743
- /* @__PURE__ */ jsx10("span", { className: "cv-p-sr-only", children: labels.back })
744
- ] }) : null,
745
- /* @__PURE__ */ jsxs10("div", { className: "cv-p-thread__heading", children: [
746
- /* @__PURE__ */ jsx10("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx10(
747
- "button",
748
- {
749
- type: "button",
750
- className: "cv-p-thread__title-button",
751
- "aria-label": `${labels.openSubject}: ${conversation.subjectLabel}`,
752
- onClick: () => onOpenSubject({ subjectType, subjectId }),
753
- children: conversation.subjectLabel
754
- }
755
- ) : conversation.subjectLabel }),
756
- conversation.protocol ? /* @__PURE__ */ jsx10(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
757
- /* @__PURE__ */ jsx10(ParticipantChannelBadges, { channels: conversation.channels, labels })
1585
+ function ParticipantThreadHeader({
1586
+ conversation,
1587
+ labels,
1588
+ subjectGroups,
1589
+ renderSubjectIcon,
1590
+ onBack,
1591
+ onOpenSubject
1592
+ }) {
1593
+ const group = subjectGroups?.find((candidate) => candidate.subjectType === conversation.subjectType);
1594
+ const icon = resolveConversationIcon({ conversation, group, renderSubjectIcon });
1595
+ const hasChannels = resolveParticipantChannels(conversation.channels).length > 0;
1596
+ return /* @__PURE__ */ jsxs15("header", { className: "cv-p-thread__header", children: [
1597
+ onBack ? /* @__PURE__ */ jsx22(ParticipantBackButton, { label: labels.back, onBack }) : null,
1598
+ icon !== void 0 ? /* @__PURE__ */ jsx22(ParticipantConversationAvatar, { icon }) : null,
1599
+ /* @__PURE__ */ jsxs15("div", { className: "cv-p-thread__heading", children: [
1600
+ icon === void 0 && group ? /* @__PURE__ */ jsx22("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
1601
+ /* @__PURE__ */ jsx22(Title, { conversation, labels, onOpenSubject }),
1602
+ conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs15("div", { className: "cv-p-thread__meta", children: [
1603
+ conversation.protocol ? /* @__PURE__ */ jsx22(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
1604
+ /* @__PURE__ */ jsx22(ParticipantChannelBadges, { channels: conversation.channels, labels, variant: "inline", iconSize: 16 })
1605
+ ] }) : null
758
1606
  ] })
759
1607
  ] });
760
1608
  }
761
- function Timeline({ props }) {
762
- const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
763
- const confirmsRead = channelCapabilityFor(props.channel ?? "app").confirmsRead;
764
- const nodes = [];
765
- let previousDay;
766
- for (const item of items) {
767
- const day = new Date(createdAtOf2(item));
768
- if (previousDay === void 0 || !isSameDay(previousDay, day)) {
769
- nodes.push(
770
- /* @__PURE__ */ jsx10("div", { className: "cv-p-day", role: "separator", children: day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" }) }, `day:${keyOf(item)}`)
771
- );
772
- }
773
- previousDay = day;
774
- nodes.push(
775
- /* @__PURE__ */ jsx10(
776
- ParticipantMessageBubble,
777
- {
778
- item,
779
- labels,
780
- confirmsRead,
781
- resolveAttachmentUrl,
782
- ...resolveActions(item, pendingActions)
783
- },
784
- keyOf(item)
785
- )
786
- );
787
- }
788
- return /* @__PURE__ */ jsx10("div", { className: "cv-p-thread__messages", children: nodes });
789
- }
1609
+
1610
+ // src/participant/ParticipantThread.tsx
1611
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
790
1612
  function Footer({ props }) {
791
1613
  const { conversation, labels, draft } = props;
792
1614
  if (conversation.status === "closed") {
793
- return /* @__PURE__ */ jsx10("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
1615
+ return /* @__PURE__ */ jsx23("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
794
1616
  }
795
- return /* @__PURE__ */ jsx10(
1617
+ return /* @__PURE__ */ jsx23(
796
1618
  ParticipantComposer,
797
1619
  {
798
1620
  value: draft.value,
@@ -809,23 +1631,46 @@ function Footer({ props }) {
809
1631
  }
810
1632
  );
811
1633
  }
812
- function ParticipantThread(props) {
1634
+ function ThreadBody(props) {
813
1635
  const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
814
1636
  const subjectCard = renderSubjectCard?.(conversation);
815
1637
  const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
816
- return /* @__PURE__ */ jsxs10("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, children: [
817
- /* @__PURE__ */ jsx10(Header, { props }),
818
- subjectCard ? /* @__PURE__ */ jsx10("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
819
- /* @__PURE__ */ jsxs10("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, children: [
820
- loadView.isLoading ? /* @__PURE__ */ jsx10(ParticipantLoading, { labels }) : null,
821
- loadView.hasError ? /* @__PURE__ */ jsx10(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
822
- hasMore && onLoadOlder ? /* @__PURE__ */ jsx10("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
823
- /* @__PURE__ */ jsx10(Timeline, { props })
1638
+ return /* @__PURE__ */ jsxs16("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, children: [
1639
+ /* @__PURE__ */ jsx23(
1640
+ ParticipantThreadHeader,
1641
+ {
1642
+ conversation,
1643
+ labels,
1644
+ subjectGroups: props.subjectGroups,
1645
+ renderSubjectIcon: props.renderSubjectIcon,
1646
+ onBack: props.onBack,
1647
+ onOpenSubject: props.onOpenSubject
1648
+ }
1649
+ ),
1650
+ subjectCard ? /* @__PURE__ */ jsx23("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
1651
+ /* @__PURE__ */ jsxs16("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
1652
+ loadView.isLoading ? /* @__PURE__ */ jsx23(ParticipantLoading, { labels }) : null,
1653
+ loadView.hasError ? /* @__PURE__ */ jsx23(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
1654
+ hasMore && onLoadOlder ? /* @__PURE__ */ jsx23("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
1655
+ /* @__PURE__ */ jsx23(ParticipantTimeline, { ...props }),
1656
+ /* @__PURE__ */ jsx23(
1657
+ ParticipantScrollToLatest,
1658
+ {
1659
+ label: labels.scrollToLatest,
1660
+ isAwayFromBottom: props.scroll.isAwayFromBottom === true,
1661
+ hasMessages: props.items.length > 0,
1662
+ newMessagesCount: props.newMessagesCount,
1663
+ onClick: props.scroll.scrollToLatest
1664
+ }
1665
+ )
824
1666
  ] }),
825
- /* @__PURE__ */ jsx10("div", { className: "cv-p-thread__live", role: "status", "aria-live": "polite", children: props.newMessagesCount > 0 ? labels.newMessages : "" }),
826
- /* @__PURE__ */ jsx10(Footer, { props })
1667
+ /* @__PURE__ */ jsx23(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
1668
+ /* @__PURE__ */ jsx23(Footer, { props })
827
1669
  ] });
828
1670
  }
1671
+ function ParticipantThread(props) {
1672
+ return /* @__PURE__ */ jsx23(ParticipantCopyAnnouncer, { children: /* @__PURE__ */ jsx23(ThreadBody, { ...props }) });
1673
+ }
829
1674
 
830
1675
  // src/participant/bindAttachmentUrlResolver.ts
831
1676
  function bindAttachmentUrlResolver(api) {
@@ -959,7 +1804,7 @@ function useParticipantDraft(draftsRef, subject) {
959
1804
  }
960
1805
 
961
1806
  // src/participant/useParticipantConversationView.ts
962
- import { useCallback as useCallback3, useMemo as useMemo2 } from "react";
1807
+ import { useCallback as useCallback4, useMemo as useMemo3 } from "react";
963
1808
 
964
1809
  // src/participant/participantSendStates.ts
965
1810
  var NO_ENTRIES = [];
@@ -977,7 +1822,7 @@ function participantSendStatesReducer(states, { subject, action }) {
977
1822
  }
978
1823
 
979
1824
  // src/participant/useParticipantThread.ts
980
- import { useCallback as useCallback2, useEffect as useEffect6, useMemo, useReducer as useReducer2, useRef as useRef4 } from "react";
1825
+ import { useCallback as useCallback3, useEffect as useEffect9, useMemo as useMemo2, useReducer as useReducer4, useRef as useRef6 } from "react";
981
1826
 
982
1827
  // src/participant/participantThreadState.ts
983
1828
  var INITIAL_PARTICIPANT_THREAD_STATE = {
@@ -1013,7 +1858,7 @@ function participantThreadReducer(state, action) {
1013
1858
  }
1014
1859
 
1015
1860
  // src/participant/useParticipantMarkRead.ts
1016
- import { useEffect as useEffect4 } from "react";
1861
+ import { useEffect as useEffect7 } from "react";
1017
1862
 
1018
1863
  // src/participant/shouldMarkRead.ts
1019
1864
  function shouldMarkParticipantRead(params) {
@@ -1025,12 +1870,12 @@ function shouldMarkParticipantRead(params) {
1025
1870
  }
1026
1871
 
1027
1872
  // src/participant/useDocumentVisibility.ts
1028
- import { useEffect as useEffect3, useState as useState4 } from "react";
1873
+ import { useEffect as useEffect6, useState as useState4 } from "react";
1029
1874
  function useDocumentVisibility() {
1030
1875
  const [visibilityState, setVisibilityState] = useState4(
1031
1876
  () => typeof document === "undefined" ? "visible" : document.visibilityState
1032
1877
  );
1033
- useEffect3(() => {
1878
+ useEffect6(() => {
1034
1879
  function handleVisibilityChange() {
1035
1880
  setVisibilityState(document.visibilityState);
1036
1881
  }
@@ -1045,18 +1890,18 @@ function useDocumentVisibility() {
1045
1890
  function useParticipantMarkRead(params) {
1046
1891
  const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
1047
1892
  const visibilityState = useDocumentVisibility();
1048
- useEffect4(() => {
1893
+ useEffect7(() => {
1049
1894
  if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
1050
1895
  void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
1051
1896
  }, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
1052
1897
  }
1053
1898
 
1054
1899
  // src/participant/useParticipantRevalidation.ts
1055
- import { useEffect as useEffect5, useRef as useRef2 } from "react";
1900
+ import { useEffect as useEffect8, useRef as useRef4 } from "react";
1056
1901
  function useParticipantRevalidation(api, onRevalidate) {
1057
- const handlerRef = useRef2(onRevalidate);
1902
+ const handlerRef = useRef4(onRevalidate);
1058
1903
  handlerRef.current = onRevalidate;
1059
- useEffect5(() => {
1904
+ useEffect8(() => {
1060
1905
  function handleFocus() {
1061
1906
  handlerRef.current();
1062
1907
  }
@@ -1083,12 +1928,12 @@ function useParticipantRevalidation(api, onRevalidate) {
1083
1928
  }
1084
1929
 
1085
1930
  // src/participant/useParticipantSend.ts
1086
- import { useCallback, useRef as useRef3 } from "react";
1931
+ import { useCallback as useCallback2, useRef as useRef5 } from "react";
1087
1932
  function useParticipantSend(params) {
1088
1933
  const { apiRef, subject, dispatchSend, onSentMessage } = params;
1089
- const entriesRef = useRef3(params.entries);
1934
+ const entriesRef = useRef5(params.entries);
1090
1935
  entriesRef.current = params.entries;
1091
- const deliver = useCallback(
1936
+ const deliver = useCallback2(
1092
1937
  async (entry) => {
1093
1938
  const { clientMessageId } = entry;
1094
1939
  try {
@@ -1112,7 +1957,7 @@ function useParticipantSend(params) {
1112
1957
  },
1113
1958
  [apiRef, dispatchSend, onSentMessage]
1114
1959
  );
1115
- const send = useCallback(
1960
+ const send = useCallback2(
1116
1961
  async (content) => {
1117
1962
  const entry = createParticipantSendEntry({
1118
1963
  clientMessageId: crypto.randomUUID(),
@@ -1125,7 +1970,7 @@ function useParticipantSend(params) {
1125
1970
  },
1126
1971
  [deliver, dispatchSend, subject]
1127
1972
  );
1128
- const retry = useCallback(
1973
+ const retry = useCallback2(
1129
1974
  async (clientMessageId) => {
1130
1975
  const entry = findFailedEntry(entriesRef.current, clientMessageId);
1131
1976
  if (!entry) return;
@@ -1149,13 +1994,13 @@ function isForSubject(event, subject) {
1149
1994
  function useParticipantThread(params) {
1150
1995
  const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [] } = params;
1151
1996
  const { subjectType, subjectId } = subject;
1152
- const stableSubject = useMemo(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
1153
- const [state, dispatch] = useReducer2(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
1154
- const apiRef = useRef4(api);
1997
+ const stableSubject = useMemo2(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
1998
+ const [state, dispatch] = useReducer4(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
1999
+ const apiRef = useRef6(api);
1155
2000
  apiRef.current = api;
1156
- const activeSubjectKey = useRef4("");
2001
+ const activeSubjectKey = useRef6("");
1157
2002
  const confirmsRead = channelCapabilityFor(channel).confirmsRead;
1158
- const refresh = useCallback2(async () => {
2003
+ const refresh = useCallback3(async () => {
1159
2004
  const requestKey = `${subjectType}:${subjectId}`;
1160
2005
  dispatch({ type: "started" });
1161
2006
  try {
@@ -1168,7 +2013,7 @@ function useParticipantThread(params) {
1168
2013
  }
1169
2014
  }, [stableSubject, subjectType, subjectId]);
1170
2015
  const oldestMessageId = state.messages[0]?.id;
1171
- const loadOlder = useCallback2(async () => {
2016
+ const loadOlder = useCallback3(async () => {
1172
2017
  if (!oldestMessageId) return;
1173
2018
  const requestKey = `${subjectType}:${subjectId}`;
1174
2019
  try {
@@ -1180,9 +2025,9 @@ function useParticipantThread(params) {
1180
2025
  if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
1181
2026
  }
1182
2027
  }, [stableSubject, subjectType, subjectId, oldestMessageId]);
1183
- const handleSentMessage = useCallback2((message) => dispatch({ type: "sendConfirmed", message }), []);
2028
+ const handleSentMessage = useCallback3((message) => dispatch({ type: "sendConfirmed", message }), []);
1184
2029
  const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
1185
- useEffect6(() => {
2030
+ useEffect9(() => {
1186
2031
  activeSubjectKey.current = `${subjectType}:${subjectId}`;
1187
2032
  dispatch({ type: "reset" });
1188
2033
  void refresh();
@@ -1190,12 +2035,12 @@ function useParticipantThread(params) {
1190
2035
  useParticipantRevalidation(api, (event) => {
1191
2036
  if (isForSubject(event, stableSubject)) void refresh();
1192
2037
  });
1193
- useEffect6(() => {
2038
+ useEffect9(() => {
1194
2039
  if (sendEntries.length === 0) return;
1195
2040
  dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
1196
2041
  }, [sendEntries, state.messages, hostPending, dispatchSend]);
1197
2042
  useParticipantMarkRead({ apiRef, subject: stableSubject, unreadCount, messageCount: state.messages.length, onMarkedRead });
1198
- const localPending = useMemo(() => toLocalPending(sendEntries), [sendEntries]);
2043
+ const localPending = useMemo2(() => toLocalPending(sendEntries), [sendEntries]);
1199
2044
  return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
1200
2045
  }
1201
2046
 
@@ -1204,7 +2049,7 @@ function useParticipantConversationView(params) {
1204
2049
  const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
1205
2050
  const { subjectType, subjectId } = subject;
1206
2051
  const sendEntries = selectSendEntries(sendStates, subject);
1207
- const dispatchSend = useCallback3(
2052
+ const dispatchSend = useCallback4(
1208
2053
  (action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
1209
2054
  [dispatchSendStates, subjectType, subjectId]
1210
2055
  );
@@ -1218,7 +2063,7 @@ function useParticipantConversationView(params) {
1218
2063
  dispatchSend,
1219
2064
  hostPending: pendingMessages
1220
2065
  });
1221
- const items = useMemo2(
2066
+ const items = useMemo3(
1222
2067
  () => mergeParticipantMessages({
1223
2068
  serverMessages: thread.messages,
1224
2069
  hostPending: pendingMessages ?? [],
@@ -1231,7 +2076,7 @@ function useParticipantConversationView(params) {
1231
2076
  }
1232
2077
 
1233
2078
  // src/participant/useParticipantThreadScroll.ts
1234
- import { useEffect as useEffect7, useRef as useRef6 } from "react";
2079
+ import { useEffect as useEffect10, useRef as useRef8 } from "react";
1235
2080
 
1236
2081
  // src/participant/participantNewMessages.ts
1237
2082
  function collectServerMessageIds(items) {
@@ -1255,8 +2100,21 @@ function countNewIncomingMessages(previousIds, currentItems) {
1255
2100
  ).length;
1256
2101
  }
1257
2102
 
2103
+ // src/participant/useScrollAwayState.ts
2104
+ import { useCallback as useCallback5 } from "react";
2105
+ function useScrollAwayState(ref, isNearBottom) {
2106
+ const scrollToLatest = useCallback5(() => {
2107
+ const element = ref.current;
2108
+ if (!element) return;
2109
+ element.scrollTo(
2110
+ scrollToLatestOptions({ scrollHeight: element.scrollHeight, isReducedMotion: readPrefersReducedMotion() })
2111
+ );
2112
+ }, [ref]);
2113
+ return { isAwayFromBottom: !isNearBottom, scrollToLatest };
2114
+ }
2115
+
1258
2116
  // src/participant/useStickToBottom.ts
1259
- import { useCallback as useCallback4, useLayoutEffect, useRef as useRef5, useState as useState5 } from "react";
2117
+ import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef7, useState as useState5 } from "react";
1260
2118
 
1261
2119
  // src/participant/participantScroll.ts
1262
2120
  var NEAR_BOTTOM_THRESHOLD_PX = 80;
@@ -1278,11 +2136,17 @@ function resolveScrollAction(params) {
1278
2136
 
1279
2137
  // src/participant/useStickToBottom.ts
1280
2138
  function useStickToBottom({ firstKey, lastKey }) {
1281
- const ref = useRef5(null);
1282
- const snapshot = useRef5({ hasPositioned: false, firstKey: void 0, lastKey: void 0, scrollTop: 0, scrollHeight: 0 });
2139
+ const ref = useRef7(null);
2140
+ const snapshot = useRef7({
2141
+ hasPositioned: false,
2142
+ firstKey: void 0,
2143
+ lastKey: void 0,
2144
+ scrollTop: 0,
2145
+ scrollHeight: 0
2146
+ });
1283
2147
  const [isNearBottom, setIsNearBottom] = useState5(true);
1284
- const isNearBottomRef = useRef5(true);
1285
- const onScroll = useCallback4((event) => {
2148
+ const isNearBottomRef = useRef7(true);
2149
+ const onScroll = useCallback6((event) => {
1286
2150
  const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
1287
2151
  const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
1288
2152
  snapshot.current.scrollTop = scrollTop;
@@ -1290,7 +2154,7 @@ function useStickToBottom({ firstKey, lastKey }) {
1290
2154
  isNearBottomRef.current = isNear;
1291
2155
  setIsNearBottom(isNear);
1292
2156
  }, []);
1293
- useLayoutEffect(() => {
2157
+ useLayoutEffect2(() => {
1294
2158
  const element = ref.current;
1295
2159
  const previous = snapshot.current;
1296
2160
  if (!element) return;
@@ -1331,21 +2195,26 @@ function keyOf2(item) {
1331
2195
  return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
1332
2196
  }
1333
2197
  function useParticipantThreadScroll(items) {
1334
- const { scroll, isNearBottom } = useStickToBottom({ firstKey: keyOf2(items[0]), lastKey: keyOf2(items[items.length - 1]) });
1335
- const seenIds = useRef6(/* @__PURE__ */ new Set());
1336
- useEffect7(() => {
2198
+ const { scroll: base, isNearBottom } = useStickToBottom({
2199
+ firstKey: keyOf2(items[0]),
2200
+ lastKey: keyOf2(items[items.length - 1])
2201
+ });
2202
+ const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
2203
+ const scroll = { ...base, isAwayFromBottom, scrollToLatest };
2204
+ const seenIds = useRef8(/* @__PURE__ */ new Set());
2205
+ useEffect10(() => {
1337
2206
  if (isNearBottom) seenIds.current = collectServerMessageIds(items);
1338
2207
  }, [isNearBottom, items]);
1339
2208
  return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items) };
1340
2209
  }
1341
2210
 
1342
2211
  // src/participant/ParticipantThreadScreen.tsx
1343
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2212
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
1344
2213
  function useOpenedConversation(api, subject, isNeeded) {
1345
2214
  const [opened, setOpened] = useState6({ status: "idle" });
1346
2215
  const { subjectType, subjectId } = subject;
1347
2216
  const canOpen = api.openConversation !== void 0;
1348
- useEffect8(() => {
2217
+ useEffect11(() => {
1349
2218
  if (!isNeeded || !canOpen) return;
1350
2219
  let isCurrent = true;
1351
2220
  setOpened({ status: "loading" });
@@ -1359,13 +2228,13 @@ function useOpenedConversation(api, subject, isNeeded) {
1359
2228
  }, [api, canOpen, isNeeded, subjectType, subjectId]);
1360
2229
  return opened;
1361
2230
  }
1362
- function NotFound({ labels, onBack }) {
1363
- return /* @__PURE__ */ jsxs11("div", { className: "cv-p cv-p-thread", children: [
1364
- /* @__PURE__ */ jsx11("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsxs11("button", { type: "button", className: "cv-p-button cv-p-thread__back", onClick: onBack, children: [
1365
- /* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: "\u2039" }),
1366
- /* @__PURE__ */ jsx11("span", { className: "cv-p-sr-only", children: labels.back })
1367
- ] }) : null }),
1368
- /* @__PURE__ */ jsx11("p", { className: "cv-p-empty", children: labels.notFound })
2231
+ function NotFound({
2232
+ labels,
2233
+ onBack
2234
+ }) {
2235
+ return /* @__PURE__ */ jsxs17("div", { className: "cv-p cv-p-thread", children: [
2236
+ /* @__PURE__ */ jsx24("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx24(ParticipantBackButton, { label: labels.back, onBack }) : null }),
2237
+ /* @__PURE__ */ jsx24("p", { className: "cv-p-empty", children: labels.notFound })
1369
2238
  ] });
1370
2239
  }
1371
2240
  function ParticipantThreadScreen(props) {
@@ -1375,11 +2244,12 @@ function ParticipantThreadScreen(props) {
1375
2244
  );
1376
2245
  const opened = useOpenedConversation(api, selected, listed === void 0);
1377
2246
  const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
1378
- if (conversation) return /* @__PURE__ */ jsx11(LoadedThread, { ...props, conversation });
2247
+ if (conversation) return /* @__PURE__ */ jsx24(LoadedThread, { ...props, conversation });
1379
2248
  const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
1380
2249
  const isListLoading = inbox.status === "idle" || inbox.status === "loading";
1381
- if (isResolving || api.openConversation === void 0 && isListLoading) return /* @__PURE__ */ jsx11("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
1382
- return /* @__PURE__ */ jsx11(NotFound, { labels, onBack: props.onBack });
2250
+ if (isResolving || api.openConversation === void 0 && isListLoading)
2251
+ return /* @__PURE__ */ jsx24("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
2252
+ return /* @__PURE__ */ jsx24(NotFound, { labels, onBack: props.onBack });
1383
2253
  }
1384
2254
  function LoadedThread(props) {
1385
2255
  const { api, selected, conversation, draftsRef, inbox } = props;
@@ -1389,7 +2259,7 @@ function LoadedThread(props) {
1389
2259
  subject: selected,
1390
2260
  onMarkedRead: inbox.markSubjectRead
1391
2261
  });
1392
- const resolveAttachmentUrl = useMemo3(() => bindAttachmentUrlResolver(api), [api]);
2262
+ const resolveAttachmentUrl = useMemo4(() => bindAttachmentUrlResolver(api), [api]);
1393
2263
  const { scroll, newMessagesCount } = useParticipantThreadScroll(items);
1394
2264
  function handleSend() {
1395
2265
  const content = takeForSend();
@@ -1401,7 +2271,7 @@ function LoadedThread(props) {
1401
2271
  restoreFromEntry(entry);
1402
2272
  dispatchSend({ type: "discarded", clientMessageId });
1403
2273
  }
1404
- return /* @__PURE__ */ jsx11(
2274
+ return /* @__PURE__ */ jsx24(
1405
2275
  ParticipantThread,
1406
2276
  {
1407
2277
  conversation,
@@ -1428,7 +2298,12 @@ function LoadedThread(props) {
1428
2298
  ...props.onRetryPending ? { retryHost: props.onRetryPending } : {}
1429
2299
  },
1430
2300
  quickReplies: props.quickReplies,
1431
- renderSubjectCard: props.renderSubjectCard
2301
+ renderSubjectCard: props.renderSubjectCard,
2302
+ subjectGroups: props.subjectGroups,
2303
+ renderSubjectIcon: props.renderSubjectIcon,
2304
+ avatars: props.avatars,
2305
+ renderAuthorAvatar: props.renderAuthorAvatar,
2306
+ tail: props.tail
1432
2307
  }
1433
2308
  );
1434
2309
  }
@@ -1554,16 +2429,16 @@ function buildParticipantThemeStyle(theme) {
1554
2429
  }
1555
2430
 
1556
2431
  // src/participant/ParticipantConversationsScreen.tsx
1557
- import { jsx as jsx12 } from "react/jsx-runtime";
2432
+ import { jsx as jsx25 } from "react/jsx-runtime";
1558
2433
  var ALL_FILTER3 = "all";
1559
2434
  function ParticipantConversationsScreen(props) {
1560
2435
  const { selected, inbox, subjectGroups, classNames } = props;
1561
2436
  const [filter, setFilter] = useState7(ALL_FILTER3);
1562
2437
  const [searchQuery, setSearchQuery] = useState7("");
1563
- const draftsRef = useRef7(/* @__PURE__ */ new Map());
1564
- const [sendStates, dispatchSendStates] = useReducer3(participantSendStatesReducer, /* @__PURE__ */ new Map());
1565
- const labels = useMemo4(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
1566
- const view = useMemo4(
2438
+ const draftsRef = useRef9(/* @__PURE__ */ new Map());
2439
+ const [sendStates, dispatchSendStates] = useReducer5(participantSendStatesReducer, /* @__PURE__ */ new Map());
2440
+ const labels = useMemo5(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
2441
+ const view = useMemo5(
1567
2442
  () => groupParticipantConversations({
1568
2443
  conversations: inbox.conversations,
1569
2444
  subjectGroups,
@@ -1574,7 +2449,7 @@ function ParticipantConversationsScreen(props) {
1574
2449
  );
1575
2450
  const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
1576
2451
  const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
1577
- return /* @__PURE__ */ jsx12("div", { className: rootClassName, style: buildParticipantThemeStyle(props.theme), children: selected === void 0 ? /* @__PURE__ */ jsx12("div", { className: ["cv-p-root__screen", classNames?.inbox].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx12(
2452
+ return /* @__PURE__ */ jsx25("div", { className: rootClassName, style: buildParticipantThemeStyle(props.theme), children: selected === void 0 ? /* @__PURE__ */ jsx25("div", { className: ["cv-p-root__screen", classNames?.inbox].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx25(
1578
2453
  ParticipantInbox,
1579
2454
  {
1580
2455
  view,
@@ -1591,7 +2466,7 @@ function ParticipantConversationsScreen(props) {
1591
2466
  renderSubjectIcon: props.renderSubjectIcon,
1592
2467
  search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
1593
2468
  }
1594
- ) }) : /* @__PURE__ */ jsx12("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx12(
2469
+ ) }) : /* @__PURE__ */ jsx25("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx25(
1595
2470
  ParticipantThreadScreen,
1596
2471
  {
1597
2472
  ...props,
@@ -1606,7 +2481,7 @@ function ParticipantConversationsScreen(props) {
1606
2481
  }
1607
2482
 
1608
2483
  // src/participant/useParticipantInbox.ts
1609
- import { useCallback as useCallback5, useEffect as useEffect9, useReducer as useReducer4 } from "react";
2484
+ import { useCallback as useCallback7, useEffect as useEffect12, useReducer as useReducer6 } from "react";
1610
2485
 
1611
2486
  // src/participant/participantInboxState.ts
1612
2487
  var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
@@ -1656,8 +2531,8 @@ function describeError2(error) {
1656
2531
  return error instanceof Error ? error.message : "unknown error";
1657
2532
  }
1658
2533
  function useParticipantInbox(api) {
1659
- const [state, dispatch] = useReducer4(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
1660
- const refresh = useCallback5(async () => {
2534
+ const [state, dispatch] = useReducer6(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
2535
+ const refresh = useCallback7(async () => {
1661
2536
  dispatch({ type: "started" });
1662
2537
  try {
1663
2538
  const page = await api.listConversations();
@@ -1667,7 +2542,7 @@ function useParticipantInbox(api) {
1667
2542
  }
1668
2543
  }, [api]);
1669
2544
  const { nextCursor } = state;
1670
- const loadMore = useCallback5(async () => {
2545
+ const loadMore = useCallback7(async () => {
1671
2546
  if (!nextCursor) return;
1672
2547
  try {
1673
2548
  const page = await api.listConversations({ cursor: nextCursor });
@@ -1676,10 +2551,10 @@ function useParticipantInbox(api) {
1676
2551
  dispatch({ type: "failed", error: describeError2(error) });
1677
2552
  }
1678
2553
  }, [api, nextCursor]);
1679
- const markSubjectRead = useCallback5((subject) => {
2554
+ const markSubjectRead = useCallback7((subject) => {
1680
2555
  dispatch({ type: "markedRead", subject });
1681
2556
  }, []);
1682
- useEffect9(() => {
2557
+ useEffect12(() => {
1683
2558
  void refresh();
1684
2559
  }, [refresh]);
1685
2560
  useParticipantRevalidation(api, () => {
@@ -1689,11 +2564,11 @@ function useParticipantInbox(api) {
1689
2564
  }
1690
2565
 
1691
2566
  // src/participant/ParticipantConversations.tsx
1692
- import { jsx as jsx13 } from "react/jsx-runtime";
2567
+ import { jsx as jsx26 } from "react/jsx-runtime";
1693
2568
  function ParticipantConversations(props) {
1694
2569
  const inbox = useParticipantInbox(props.api);
1695
2570
  const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
1696
- return /* @__PURE__ */ jsx13(
2571
+ return /* @__PURE__ */ jsx26(
1697
2572
  ParticipantConversationsScreen,
1698
2573
  {
1699
2574
  ...props,