@adatechnology/conversations-ui 0.13.0 → 0.14.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 (35) hide show
  1. package/dist/chunk-RLUNDWGA.js +51 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +5 -43
  4. package/dist/participant/index.d.ts +74 -2
  5. package/dist/participant/index.js +1058 -399
  6. package/dist/styles.css +213 -0
  7. package/package.json +3 -3
  8. package/src/conversationTranscript.ts +6 -2
  9. package/src/participant/ConversationThread.tsx +4 -0
  10. package/src/participant/ParticipantAuthorAvatar.tsx +7 -1
  11. package/src/participant/ParticipantBubbleBody.tsx +18 -3
  12. package/src/participant/ParticipantConversationMenu.tsx +89 -0
  13. package/src/participant/ParticipantConversations.tsx +3 -0
  14. package/src/participant/ParticipantMenuBubble.tsx +106 -0
  15. package/src/participant/ParticipantMessageBubble.tsx +34 -7
  16. package/src/participant/ParticipantMessageInfo.tsx +70 -0
  17. package/src/participant/ParticipantMessageMenu.tsx +168 -0
  18. package/src/participant/ParticipantMessageMenuContext.tsx +109 -0
  19. package/src/participant/ParticipantSelectionBar.tsx +64 -0
  20. package/src/participant/ParticipantThread.tsx +27 -2
  21. package/src/participant/ParticipantThreadScreen.tsx +1 -0
  22. package/src/participant/index.ts +6 -0
  23. package/src/participant/participantConversationMenuDom.test.tsx +166 -0
  24. package/src/participant/participantLabels.ts +40 -0
  25. package/src/participant/participantMessageInfoModel.test.ts +68 -0
  26. package/src/participant/participantMessageInfoModel.ts +80 -0
  27. package/src/participant/participantMessageMenuDom.test.tsx +111 -0
  28. package/src/participant/participantMessageMenuModel.test.ts +43 -0
  29. package/src/participant/participantMessageMenuModel.ts +91 -0
  30. package/src/participant/participantMessageMoment.ts +7 -0
  31. package/src/participant/participantTranscriptionText.test.ts +48 -0
  32. package/src/participant/participantTranscriptionText.ts +28 -0
  33. package/src/participant/useOpenUpwards.ts +22 -0
  34. package/src/participant/useParticipantFailedMenu.ts +3 -0
  35. package/src/styles.css +40 -0
@@ -1,3 +1,6 @@
1
+ import {
2
+ downloadTextFile
3
+ } from "../chunk-RLUNDWGA.js";
1
4
  import {
2
5
  TooltipLayer
3
6
  } from "../chunk-NSOAUYNE.js";
@@ -12,7 +15,7 @@ import {
12
15
  } from "../chunk-WCZNIJ3M.js";
13
16
 
14
17
  // src/participant/ParticipantConversationsScreen.tsx
15
- import { useMemo as useMemo6, useReducer as useReducer6, useRef as useRef21, useState as useState14 } from "react";
18
+ import { useMemo as useMemo7, useReducer as useReducer6, useRef as useRef23, useState as useState18 } from "react";
16
19
 
17
20
  // src/participant/participantLoadView.ts
18
21
  function resolveLoadView({ status, hasItems }) {
@@ -103,6 +106,23 @@ var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
103
106
  edit: "Edit",
104
107
  messageOptions: DEFAULT_MESSAGE_OPTIONS_LABEL,
105
108
  showOriginalText: DEFAULT_SHOW_ORIGINAL_TEXT_LABEL,
109
+ copyMessage: "Copy",
110
+ copySelection: "Copy selection",
111
+ selectMessages: "Select messages",
112
+ selectMessage: "Select message",
113
+ cancelSelection: "Cancel selection",
114
+ selectedCount: "{count} selected",
115
+ messageInfo: "Message info",
116
+ infoAuthor: "From",
117
+ infoSentAt: "Sent",
118
+ infoStatus: "Status",
119
+ infoReadAt: "Read",
120
+ closeMessageInfo: "Close",
121
+ conversationOptions: "Conversation options",
122
+ downloadConversation: "Download conversation",
123
+ transcriptionPrefix: "Transcription",
124
+ copyTranscription: "Copy transcription",
125
+ downloadTranscription: "Download transcription",
106
126
  notFound: "Conversation not found",
107
127
  loading: "Loading\u2026",
108
128
  loadError: "Could not load. Check your connection and try again.",
@@ -426,7 +446,7 @@ function ParticipantInbox(props) {
426
446
  }
427
447
 
428
448
  // src/participant/ParticipantThreadScreen.tsx
429
- import { useEffect as useEffect20, useState as useState13 } from "react";
449
+ import { useEffect as useEffect22, useState as useState17 } from "react";
430
450
 
431
451
  // src/participant/ParticipantBackButton.tsx
432
452
  import { ArrowLeft } from "lucide-react";
@@ -436,7 +456,7 @@ function ParticipantBackButton({ label, onBack }) {
436
456
  }
437
457
 
438
458
  // src/participant/ParticipantThread.tsx
439
- import { useRef as useRef13 } from "react";
459
+ import { useRef as useRef15 } from "react";
440
460
 
441
461
  // src/participant/ParticipantComposer.tsx
442
462
  import { useState as useState3 } from "react";
@@ -1424,10 +1444,312 @@ function ParticipantComposer(props) {
1424
1444
  ] });
1425
1445
  }
1426
1446
 
1447
+ // src/participant/ParticipantConversationMenu.tsx
1448
+ import { EllipsisVertical } from "lucide-react";
1449
+ import { useId as useId2 } from "react";
1450
+
1451
+ // src/participant/participantMessageMoment.ts
1452
+ function formatMessageMoment(iso, locale) {
1453
+ const date = new Date(iso);
1454
+ const day = date.toLocaleDateString(locale, { day: "2-digit", month: "2-digit", year: "numeric" });
1455
+ const time = date.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
1456
+ return `${day} ${time}`;
1457
+ }
1458
+
1459
+ // src/participant/participantTranscriptionText.ts
1460
+ function readyTranscriptions(attachments) {
1461
+ return attachments.flatMap((attachment) => {
1462
+ const transcription = attachment.kind === "audio" ? attachment.transcription : void 0;
1463
+ const text = transcription?.status === "ready" ? transcription.text.trim() : "";
1464
+ return text === "" ? [] : [{ filename: attachment.filename, text }];
1465
+ });
1466
+ }
1467
+ function attachmentForCopy(attachment, transcriptionLabel) {
1468
+ const [ready] = readyTranscriptions([attachment]);
1469
+ return ready ? `[${attachment.filename}] ${transcriptionLabel}: ${ready.text}` : `[${attachment.filename}]`;
1470
+ }
1471
+ function transcriptionFileName(audioFilename, transcriptionLabel) {
1472
+ const base = audioFilename.replace(/\.[^.]+$/, "");
1473
+ const safe = `${base} ${transcriptionLabel}`.replace(/[\\/:*?"<>|]+/g, " ").replace(/\s+/g, " ").trim();
1474
+ return `${safe}.txt`;
1475
+ }
1476
+
1477
+ // src/participant/participantMessageMenuModel.ts
1478
+ function toggleSelectedId(ids, id) {
1479
+ return ids.includes(id) ? ids.filter((candidate) => candidate !== id) : [...ids, id];
1480
+ }
1481
+ function selectedInOrder(messages, ids) {
1482
+ const picked = new Set(ids);
1483
+ return messages.filter((message) => picked.has(message.id));
1484
+ }
1485
+ function copyableText(message) {
1486
+ const text = message.text?.trim() ?? "";
1487
+ return text === "" ? null : text;
1488
+ }
1489
+ function formatMessagesForCopy(messages, format) {
1490
+ return messages.map((message) => {
1491
+ const author = message.isMine ? format.meLabel : message.authorName ?? "";
1492
+ const head = `[${formatMessageMoment(message.createdAt, format.locale)}]${author === "" ? "" : ` ${author}:`}`;
1493
+ const files = message.attachments.map(
1494
+ (attachment) => attachmentForCopy(attachment, format.transcriptionLabel ?? "Transcription")
1495
+ );
1496
+ return [head, copyableText(message) ?? "", ...files].filter((part) => part !== "").join(" ");
1497
+ }).join("\n");
1498
+ }
1499
+ function selectedTextWithin(container) {
1500
+ const selection = typeof window === "undefined" ? null : window.getSelection();
1501
+ if (!container || !selection || selection.isCollapsed) return "";
1502
+ const { anchorNode, focusNode } = selection;
1503
+ if (!anchorNode || !focusNode || !container.contains(anchorNode) || !container.contains(focusNode)) return "";
1504
+ return selection.toString().trim();
1505
+ }
1506
+
1507
+ // src/participant/participantMessageInfoModel.ts
1508
+ function statusLabel(status, labels) {
1509
+ const byStatus = {
1510
+ queued: labels.statusQueued,
1511
+ sent: labels.statusSent,
1512
+ delivered: labels.statusDelivered,
1513
+ read: labels.statusRead,
1514
+ failed: labels.statusFailedShort,
1515
+ bounced: labels.statusFailedShort
1516
+ };
1517
+ return byStatus[status];
1518
+ }
1519
+ function messageInfoRows(message, labels, locale) {
1520
+ const author = message.isMine ? labels.me : message.authorName ?? "";
1521
+ const rows = [];
1522
+ if (author !== "") rows.push({ id: "author", label: labels.infoAuthor ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.infoAuthor ?? "", value: author });
1523
+ rows.push({
1524
+ id: "sent",
1525
+ label: labels.infoSentAt ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.infoSentAt ?? "",
1526
+ value: formatMessageMoment(message.createdAt, locale)
1527
+ });
1528
+ if (!message.isMine) return rows;
1529
+ if (message.status) {
1530
+ rows.push({
1531
+ id: "status",
1532
+ label: labels.infoStatus ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.infoStatus ?? "",
1533
+ value: statusLabel(message.status, labels)
1534
+ });
1535
+ }
1536
+ if (message.readAt) {
1537
+ rows.push({
1538
+ id: "read",
1539
+ label: labels.infoReadAt ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.infoReadAt ?? "",
1540
+ value: formatMessageMoment(message.readAt, locale)
1541
+ });
1542
+ }
1543
+ return rows;
1544
+ }
1545
+ function conversationExportText({ title, messages, ...format }) {
1546
+ return `${title}
1547
+
1548
+ ${formatMessagesForCopy(messages, format)}
1549
+ `;
1550
+ }
1551
+ function conversationExportFileName(title) {
1552
+ const safe = title.replace(/[\\/:*?"<>|]+/g, " ").replace(/\s+/g, " ").trim();
1553
+ return `${safe === "" ? "conversation" : safe}.txt`;
1554
+ }
1555
+
1556
+ // src/participant/useParticipantFailedMenu.ts
1557
+ import { useReducer, useRef as useRef8 } from "react";
1558
+
1559
+ // src/participant/participantFailedMenuState.ts
1560
+ var CLOSED_FAILED_MENU = { isOpen: false, initialFocus: "first" };
1561
+ function failedMenuReducer(state, action) {
1562
+ if (action.type === "open") return { isOpen: true, initialFocus: action.initialFocus };
1563
+ if (action.type === "close") return state.isOpen ? CLOSED_FAILED_MENU : state;
1564
+ return state.isOpen ? CLOSED_FAILED_MENU : { isOpen: true, initialFocus: "first" };
1565
+ }
1566
+ function resolveFailedMenuKey({ key, index, count }) {
1567
+ if (key === "Escape") return { type: "close", shouldRestoreFocus: true };
1568
+ if (key === "Tab") return { type: "close", shouldRestoreFocus: false };
1569
+ if (key === "ArrowDown") return { type: "focus", index: (index + 1) % count };
1570
+ if (key === "ArrowUp") return { type: "focus", index: (index - 1 + count) % count };
1571
+ if (key === "Home") return { type: "focus", index: 0 };
1572
+ if (key === "End") return { type: "focus", index: count - 1 };
1573
+ return void 0;
1574
+ }
1575
+ function resolveFailedMenuTriggerKey(key) {
1576
+ if (key === "ArrowDown") return "first";
1577
+ if (key === "ArrowUp") return "last";
1578
+ return void 0;
1579
+ }
1580
+ var MENU_GAP_PX = 8;
1581
+ function resolveFailedMenuPlacement({ anchorTop, scrollerTop, menuHeight }) {
1582
+ return anchorTop - scrollerTop >= menuHeight + MENU_GAP_PX ? "above" : "below";
1583
+ }
1584
+
1585
+ // src/participant/failedMenuKeyboard.ts
1586
+ function handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }) {
1587
+ const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
1588
+ const index = items.findIndex((item) => item === document.activeElement);
1589
+ const result = resolveFailedMenuKey({ key: event.key, index, count: items.length });
1590
+ if (!result) return;
1591
+ if (result.type === "focus") {
1592
+ event.preventDefault();
1593
+ items[result.index]?.focus();
1594
+ return;
1595
+ }
1596
+ if (result.shouldRestoreFocus) {
1597
+ event.preventDefault();
1598
+ event.stopPropagation();
1599
+ triggerRef.current?.focus();
1600
+ }
1601
+ dispatch({ type: "close" });
1602
+ }
1603
+
1604
+ // src/participant/useFailedMenuEffects.ts
1605
+ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useState as useState4 } from "react";
1606
+ var SCROLLER_SELECTOR = ".cv-p-thread__scroll";
1607
+ function useFailedMenuInitialFocus(state, menuRef) {
1608
+ useEffect6(() => {
1609
+ if (!state.isOpen) return;
1610
+ const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
1611
+ const target = state.initialFocus === "last" ? items[items.length - 1] : items[0];
1612
+ target?.focus();
1613
+ }, [state, menuRef]);
1614
+ }
1615
+ function useFailedMenuOutsideClose(isOpen, containerRef, dispatch) {
1616
+ useEffect6(() => {
1617
+ if (!isOpen) return;
1618
+ function handlePointerDown(event) {
1619
+ if (event.target instanceof Node && containerRef.current?.contains(event.target)) return;
1620
+ dispatch({ type: "close" });
1621
+ }
1622
+ document.addEventListener("pointerdown", handlePointerDown);
1623
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
1624
+ }, [isOpen, containerRef, dispatch]);
1625
+ }
1626
+ function useFailedMenuPlacement(isOpen, { triggerRef, menuRef }) {
1627
+ const [placement, setPlacement] = useState4("above");
1628
+ useLayoutEffect2(() => {
1629
+ const trigger = triggerRef.current;
1630
+ const menu = menuRef.current;
1631
+ if (!isOpen || !trigger || !menu) return;
1632
+ const anchor = trigger.closest(".cv-p-bubble") ?? trigger;
1633
+ const scroller = trigger.closest(SCROLLER_SELECTOR);
1634
+ setPlacement(
1635
+ resolveFailedMenuPlacement({
1636
+ anchorTop: anchor.getBoundingClientRect().top,
1637
+ scrollerTop: scroller?.getBoundingClientRect().top ?? 0,
1638
+ menuHeight: menu.getBoundingClientRect().height
1639
+ })
1640
+ );
1641
+ }, [isOpen, triggerRef, menuRef]);
1642
+ return placement;
1643
+ }
1644
+
1645
+ // src/participant/useParticipantFailedMenu.ts
1646
+ function useParticipantFailedMenu() {
1647
+ const [state, dispatch] = useReducer(failedMenuReducer, CLOSED_FAILED_MENU);
1648
+ const containerRef = useRef8(null);
1649
+ const triggerRef = useRef8(null);
1650
+ const menuRef = useRef8(null);
1651
+ useFailedMenuInitialFocus(state, menuRef);
1652
+ useFailedMenuOutsideClose(state.isOpen, containerRef, dispatch);
1653
+ const placement = useFailedMenuPlacement(state.isOpen, { triggerRef, menuRef });
1654
+ function handleTriggerKeyDown(event) {
1655
+ const initialFocus = resolveFailedMenuTriggerKey(event.key);
1656
+ if (!initialFocus) return;
1657
+ event.preventDefault();
1658
+ dispatch({ type: "open", initialFocus });
1659
+ }
1660
+ function handleBlur(event) {
1661
+ const next = event.relatedTarget;
1662
+ if (next instanceof Node && !containerRef.current?.contains(next)) dispatch({ type: "close" });
1663
+ }
1664
+ return {
1665
+ state,
1666
+ placement,
1667
+ containerRef,
1668
+ triggerRef,
1669
+ menuRef,
1670
+ handleTriggerClick: () => dispatch({ type: "toggle" }),
1671
+ handleTriggerKeyDown,
1672
+ handleMenuKeyDown: (event) => handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }),
1673
+ handleBlur,
1674
+ close: () => dispatch({ type: "close" }),
1675
+ open: () => dispatch({ type: "open", initialFocus: "first" })
1676
+ };
1677
+ }
1678
+
1679
+ // src/participant/ParticipantConversationMenu.tsx
1680
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1681
+ function ParticipantConversationMenu({ state, title }) {
1682
+ const menu = useParticipantFailedMenu();
1683
+ const triggerId = useId2();
1684
+ const { labels } = state;
1685
+ const listClass = `cv-p-failed-menu__list cv-p-message-menu__list cv-p-conversation-menu__list${menu.placement === "below" ? " cv-p-failed-menu__list--below" : ""}`;
1686
+ const entries = [
1687
+ { id: "select", label: labels.selectMessages ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.selectMessages, run: () => state.start() },
1688
+ {
1689
+ id: "download",
1690
+ label: labels.downloadConversation ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.downloadConversation,
1691
+ disabled: state.messages.length === 0,
1692
+ run: () => downloadTextFile(
1693
+ conversationExportFileName(title),
1694
+ conversationExportText({
1695
+ title,
1696
+ messages: state.messages,
1697
+ locale: state.locale,
1698
+ meLabel: labels.me,
1699
+ transcriptionLabel: labels.transcriptionPrefix ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.transcriptionPrefix
1700
+ })
1701
+ )
1702
+ }
1703
+ ];
1704
+ return /* @__PURE__ */ jsxs12("span", { className: "cv-p-conversation-menu", ref: menu.containerRef, onBlur: menu.handleBlur, children: [
1705
+ /* @__PURE__ */ jsx14(
1706
+ "button",
1707
+ {
1708
+ type: "button",
1709
+ id: triggerId,
1710
+ ref: menu.triggerRef,
1711
+ className: "cv-p-conversation-menu__trigger",
1712
+ "aria-haspopup": "menu",
1713
+ "aria-expanded": menu.state.isOpen,
1714
+ "aria-label": labels.conversationOptions ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.conversationOptions,
1715
+ onClick: menu.handleTriggerClick,
1716
+ onKeyDown: menu.handleTriggerKeyDown,
1717
+ children: /* @__PURE__ */ jsx14(EllipsisVertical, { size: 18, "aria-hidden": true })
1718
+ }
1719
+ ),
1720
+ menu.state.isOpen ? /* @__PURE__ */ jsx14(
1721
+ "div",
1722
+ {
1723
+ role: "menu",
1724
+ "aria-labelledby": triggerId,
1725
+ ref: menu.menuRef,
1726
+ className: listClass,
1727
+ onKeyDown: menu.handleMenuKeyDown,
1728
+ children: entries.map((entry) => /* @__PURE__ */ jsx14(
1729
+ "button",
1730
+ {
1731
+ type: "button",
1732
+ role: "menuitem",
1733
+ tabIndex: -1,
1734
+ className: "cv-p-failed-menu__item cv-p-message-menu__item",
1735
+ disabled: entry.disabled,
1736
+ onClick: () => {
1737
+ menu.close();
1738
+ entry.run();
1739
+ },
1740
+ children: entry.label
1741
+ },
1742
+ entry.id
1743
+ ))
1744
+ }
1745
+ ) : null
1746
+ ] });
1747
+ }
1748
+
1427
1749
  // src/participant/useSuppressTouchTooltips.ts
1428
- import { useEffect as useEffect6 } from "react";
1750
+ import { useEffect as useEffect7 } from "react";
1429
1751
  function useSuppressTouchTooltips(rootRef) {
1430
- useEffect6(() => {
1752
+ useEffect7(() => {
1431
1753
  const root = rootRef.current;
1432
1754
  if (!root) return void 0;
1433
1755
  let isTouching = false;
@@ -1457,7 +1779,7 @@ function useSuppressTouchTooltips(rootRef) {
1457
1779
  }
1458
1780
 
1459
1781
  // src/participant/ParticipantCopyAnnouncer.tsx
1460
- import { createContext, useCallback as useCallback4, useContext, useEffect as useEffect7, useReducer, useRef as useRef8 } from "react";
1782
+ import { createContext, useCallback as useCallback4, useContext, useEffect as useEffect8, useReducer as useReducer2, useRef as useRef9 } from "react";
1461
1783
 
1462
1784
  // src/participant/participantCopyAnnouncement.ts
1463
1785
  var INITIAL_COPY_ANNOUNCEMENT = { text: "", tick: 0 };
@@ -1477,7 +1799,7 @@ function resolveLiveText(params) {
1477
1799
  }
1478
1800
 
1479
1801
  // src/participant/ParticipantCopyAnnouncer.tsx
1480
- import { jsx as jsx14 } from "react/jsx-runtime";
1802
+ import { jsx as jsx15 } from "react/jsx-runtime";
1481
1803
  var ANNOUNCEMENT_MS = 3e3;
1482
1804
  var AnnounceContext = createContext(() => void 0);
1483
1805
  var AnnouncementContext = createContext(INITIAL_COPY_ANNOUNCEMENT);
@@ -1485,34 +1807,34 @@ function useAnnounceCopy() {
1485
1807
  return useContext(AnnounceContext);
1486
1808
  }
1487
1809
  function ParticipantCopyAnnouncer({ children }) {
1488
- const [announcement, dispatch] = useReducer(copyAnnouncementReducer, INITIAL_COPY_ANNOUNCEMENT);
1810
+ const [announcement, dispatch] = useReducer2(copyAnnouncementReducer, INITIAL_COPY_ANNOUNCEMENT);
1489
1811
  const announce = useCallback4((text) => dispatch({ type: "announce", text }), []);
1490
- useEffect7(() => {
1812
+ useEffect8(() => {
1491
1813
  if (announcement.text === "") return void 0;
1492
1814
  const timer = setTimeout(() => dispatch({ type: "expired" }), ANNOUNCEMENT_MS);
1493
1815
  return () => clearTimeout(timer);
1494
1816
  }, [announcement.tick, announcement.text]);
1495
- return /* @__PURE__ */ jsx14(AnnounceContext.Provider, { value: announce, children: /* @__PURE__ */ jsx14(AnnouncementContext.Provider, { value: announcement, children }) });
1817
+ return /* @__PURE__ */ jsx15(AnnounceContext.Provider, { value: announce, children: /* @__PURE__ */ jsx15(AnnouncementContext.Provider, { value: announcement, children }) });
1496
1818
  }
1497
1819
  function ParticipantThreadLive({ notice }) {
1498
1820
  const copy = useContext(AnnouncementContext);
1499
- const baselineNotice = useRef8(void 0);
1821
+ const baselineNotice = useRef9(void 0);
1500
1822
  const resolution = resolveLiveText({ copy, notice, baselineNotice: baselineNotice.current });
1501
1823
  baselineNotice.current = resolution.baselineNotice;
1502
- return /* @__PURE__ */ jsx14("div", { className: "cv-p-thread__live", role: "status", "aria-live": "polite", children: resolution.text });
1824
+ return /* @__PURE__ */ jsx15("div", { className: "cv-p-thread__live", role: "status", "aria-live": "polite", children: resolution.text });
1503
1825
  }
1504
1826
 
1505
1827
  // src/participant/ParticipantScrollToLatest.tsx
1506
1828
  import { ChevronDown } from "lucide-react";
1507
1829
 
1508
1830
  // src/participant/participantScrollFocus.ts
1509
- var SCROLLER_SELECTOR = ".cv-p-thread__scroll";
1831
+ var SCROLLER_SELECTOR2 = ".cv-p-thread__scroll";
1510
1832
  function focusScrollerOf(button) {
1511
- button.closest(SCROLLER_SELECTOR)?.focus({ preventScroll: true });
1833
+ button.closest(SCROLLER_SELECTOR2)?.focus({ preventScroll: true });
1512
1834
  }
1513
1835
 
1514
1836
  // src/participant/ParticipantScrollToLatest.tsx
1515
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1837
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1516
1838
  function ParticipantScrollToLatest({
1517
1839
  label,
1518
1840
  isAwayFromBottom,
@@ -1522,7 +1844,7 @@ function ParticipantScrollToLatest({
1522
1844
  }) {
1523
1845
  const { isVisible, badge } = resolveScrollToLatestView({ isAwayFromBottom, newMessagesCount, hasMessages });
1524
1846
  if (!isVisible || onClick === void 0) return null;
1525
- return /* @__PURE__ */ jsx15("div", { className: "cv-p-thread__latest", children: /* @__PURE__ */ jsxs12(
1847
+ return /* @__PURE__ */ jsx16("div", { className: "cv-p-thread__latest", children: /* @__PURE__ */ jsxs13(
1526
1848
  "button",
1527
1849
  {
1528
1850
  type: "button",
@@ -1534,8 +1856,8 @@ function ParticipantScrollToLatest({
1534
1856
  onClick();
1535
1857
  },
1536
1858
  children: [
1537
- /* @__PURE__ */ jsx15(ChevronDown, { size: 24, "aria-hidden": "true", focusable: "false" }),
1538
- badge ? /* @__PURE__ */ jsx15("span", { className: "cv-p-thread__latest-badge", "aria-hidden": "true", children: badge }) : null
1859
+ /* @__PURE__ */ jsx16(ChevronDown, { size: 24, "aria-hidden": "true", focusable: "false" }),
1860
+ badge ? /* @__PURE__ */ jsx16("span", { className: "cv-p-thread__latest-badge", "aria-hidden": "true", children: badge }) : null
1539
1861
  ]
1540
1862
  }
1541
1863
  ) });
@@ -1581,13 +1903,13 @@ function shouldShowAvatar({ previousAuthor, author, isMine }) {
1581
1903
  }
1582
1904
 
1583
1905
  // src/participant/ParticipantAuthorAvatar.tsx
1584
- import { jsx as jsx16 } from "react/jsx-runtime";
1906
+ import { jsx as jsx17 } from "react/jsx-runtime";
1585
1907
  function ParticipantAuthorAvatar({ name, render }) {
1586
1908
  const fromHost = render?.({ name });
1587
1909
  const hasHostContent = !(fromHost == null || fromHost === false || fromHost === "");
1588
1910
  const initials = authorInitials(name);
1589
- const fallback = initials ?? /* @__PURE__ */ jsx16(User, { size: 16, "aria-hidden": true });
1590
- return /* @__PURE__ */ jsx16("span", { className: "cv-p-avatar", "aria-hidden": "true", children: hasHostContent ? fromHost : fallback });
1911
+ const fallback = initials ?? /* @__PURE__ */ jsx17(User, { size: 16, "aria-hidden": true });
1912
+ return /* @__PURE__ */ jsx17("span", { className: "cv-p-avatar", "aria-hidden": "true", children: hasHostContent ? fromHost : fallback });
1591
1913
  }
1592
1914
 
1593
1915
  // src/participant/participantDay.ts
@@ -1599,27 +1921,27 @@ function toDateTimeAttribute(date) {
1599
1921
  }
1600
1922
 
1601
1923
  // src/participant/ParticipantDayDivider.tsx
1602
- import { jsx as jsx17 } from "react/jsx-runtime";
1924
+ import { jsx as jsx18 } from "react/jsx-runtime";
1603
1925
  function ParticipantDayDivider({ day, locale }) {
1604
1926
  const label = day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" });
1605
- return /* @__PURE__ */ jsx17("div", { className: "cv-p-day", role: "separator", "aria-label": label, children: /* @__PURE__ */ jsx17("time", { className: "cv-p-day__label", dateTime: toDateTimeAttribute(day), children: label }) });
1927
+ return /* @__PURE__ */ jsx18("div", { className: "cv-p-day", role: "separator", "aria-label": label, children: /* @__PURE__ */ jsx18("time", { className: "cv-p-day__label", dateTime: toDateTimeAttribute(day), children: label }) });
1606
1928
  }
1607
1929
 
1608
1930
  // src/participant/ParticipantMessageBubble.tsx
1609
- import { useId as useId3 } from "react";
1931
+ import { useId as useId5 } from "react";
1610
1932
 
1611
1933
  // src/participant/ParticipantAttachmentItem.tsx
1612
- import { useEffect as useEffect11, useState as useState7 } from "react";
1934
+ import { useEffect as useEffect12, useState as useState8 } from "react";
1613
1935
 
1614
1936
  // src/participant/ParticipantAudioTranscription.tsx
1615
- import { useState as useState5 } from "react";
1937
+ import { useState as useState6 } from "react";
1616
1938
 
1617
1939
  // src/participant/useTranscriptionOverflow.ts
1618
- import { useEffect as useEffect8, useRef as useRef9, useState as useState4 } from "react";
1940
+ import { useEffect as useEffect9, useRef as useRef10, useState as useState5 } from "react";
1619
1941
  function useTranscriptionOverflow(text, isExpanded) {
1620
- const textRef = useRef9(null);
1621
- const [isOverflowing, setIsOverflowing] = useState4(false);
1622
- useEffect8(() => {
1942
+ const textRef = useRef10(null);
1943
+ const [isOverflowing, setIsOverflowing] = useState5(false);
1944
+ useEffect9(() => {
1623
1945
  const element = textRef.current;
1624
1946
  if (!element || isExpanded) return void 0;
1625
1947
  const measure = () => setIsOverflowing(element.scrollHeight > element.clientHeight);
@@ -1633,11 +1955,11 @@ function useTranscriptionOverflow(text, isExpanded) {
1633
1955
  }
1634
1956
 
1635
1957
  // src/participant/useTranscriptionReadyAnnouncement.ts
1636
- import { useEffect as useEffect9, useRef as useRef10 } from "react";
1958
+ import { useEffect as useEffect10, useRef as useRef11 } from "react";
1637
1959
  function useTranscriptionReadyAnnouncement({ attachmentId, status, text }) {
1638
1960
  const announce = useAnnounceCopy();
1639
- const previous = useRef10({ attachmentId, status });
1640
- useEffect9(() => {
1961
+ const previous = useRef11({ attachmentId, status });
1962
+ useEffect10(() => {
1641
1963
  const before = previous.current;
1642
1964
  previous.current = { attachmentId, status };
1643
1965
  if (before.attachmentId !== attachmentId) return;
@@ -1646,7 +1968,7 @@ function useTranscriptionReadyAnnouncement({ attachmentId, status, text }) {
1646
1968
  }
1647
1969
 
1648
1970
  // src/participant/ParticipantAudioTranscription.tsx
1649
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
1971
+ import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
1650
1972
  var MILLISECONDS_PER_MINUTE = 6e4;
1651
1973
  function resolveTooLongMinutes(transcription) {
1652
1974
  if (transcription?.status !== "failed" || transcription.reason !== "too_long") return void 0;
@@ -1666,21 +1988,21 @@ function ParticipantAudioTranscription({ attachmentId, transcription: received,
1666
1988
  text: labels.transcriptionReady
1667
1989
  });
1668
1990
  if (transcription?.status === "pending") {
1669
- return /* @__PURE__ */ jsx18("p", { className: "cv-p-transcription cv-p-transcription--pending", children: labels.transcribing });
1991
+ return /* @__PURE__ */ jsx19("p", { className: "cv-p-transcription cv-p-transcription--pending", children: labels.transcribing });
1670
1992
  }
1671
- if (transcription?.status === "ready") return /* @__PURE__ */ jsx18(ReadyTranscriptionBlock, { transcription, labels });
1993
+ if (transcription?.status === "ready") return /* @__PURE__ */ jsx19(ReadyTranscriptionBlock, { transcription, labels });
1672
1994
  const minutes = resolveTooLongMinutes(transcription);
1673
1995
  if (minutes === void 0) return null;
1674
- return /* @__PURE__ */ jsx18("p", { className: "cv-p-transcription cv-p-transcription--too-long", children: labels.transcriptionTooLong.replace("{minutes}", String(minutes)) });
1996
+ return /* @__PURE__ */ jsx19("p", { className: "cv-p-transcription cv-p-transcription--too-long", children: labels.transcriptionTooLong.replace("{minutes}", String(minutes)) });
1675
1997
  }
1676
1998
  function ReadyTranscriptionBlock({ transcription, labels }) {
1677
- const [isExpanded, setIsExpanded] = useState5(false);
1999
+ const [isExpanded, setIsExpanded] = useState6(false);
1678
2000
  const { textRef, isOverflowing } = useTranscriptionOverflow(transcription.text, isExpanded);
1679
2001
  const hasText = transcription.text !== "";
1680
2002
  const showsToggle = hasText && (isOverflowing || isExpanded);
1681
- return /* @__PURE__ */ jsxs13("div", { className: "cv-p-transcription", children: [
1682
- /* @__PURE__ */ jsx18("p", { className: "cv-p-transcription__notice", children: labels.machineTranscription }),
1683
- hasText ? /* @__PURE__ */ jsx18(
2003
+ return /* @__PURE__ */ jsxs14("div", { className: "cv-p-transcription", children: [
2004
+ /* @__PURE__ */ jsx19("p", { className: "cv-p-transcription__notice", children: labels.machineTranscription }),
2005
+ hasText ? /* @__PURE__ */ jsx19(
1684
2006
  "p",
1685
2007
  {
1686
2008
  ref: textRef,
@@ -1689,8 +2011,8 @@ function ReadyTranscriptionBlock({ transcription, labels }) {
1689
2011
  "data-expanded": isExpanded ? "true" : void 0,
1690
2012
  children: transcription.text
1691
2013
  }
1692
- ) : /* @__PURE__ */ jsx18("p", { className: "cv-p-transcription__empty", children: labels.emptyTranscription }),
1693
- showsToggle ? /* @__PURE__ */ jsx18(
2014
+ ) : /* @__PURE__ */ jsx19("p", { className: "cv-p-transcription__empty", children: labels.emptyTranscription }),
2015
+ showsToggle ? /* @__PURE__ */ jsx19(
1694
2016
  "button",
1695
2017
  {
1696
2018
  type: "button",
@@ -1704,7 +2026,7 @@ function ReadyTranscriptionBlock({ transcription, labels }) {
1704
2026
  }
1705
2027
 
1706
2028
  // src/participant/useStableAttachmentUrl.ts
1707
- import { useCallback as useCallback5, useEffect as useEffect10, useRef as useRef11, useState as useState6 } from "react";
2029
+ import { useCallback as useCallback5, useEffect as useEffect11, useRef as useRef12, useState as useState7 } from "react";
1708
2030
 
1709
2031
  // src/participant/participantAttachmentUrl.ts
1710
2032
  var SAFE_ATTACHMENT_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:", "blob:"]);
@@ -1728,14 +2050,14 @@ async function resolveSafely({ attachment, resolveAttachmentUrl }) {
1728
2050
  }
1729
2051
  }
1730
2052
  function useStableAttachmentUrl(params) {
1731
- const latest = useRef11(params);
1732
- const currentId = useRef11(params.attachment.id);
1733
- const [state, setState2] = useState6(PENDING_STATE);
2053
+ const latest = useRef12(params);
2054
+ const currentId = useRef12(params.attachment.id);
2055
+ const [state, setState2] = useState7(PENDING_STATE);
1734
2056
  const { id } = params.attachment;
1735
- useEffect10(() => {
2057
+ useEffect11(() => {
1736
2058
  latest.current = params;
1737
2059
  });
1738
- useEffect10(() => {
2060
+ useEffect11(() => {
1739
2061
  currentId.current = id;
1740
2062
  setState2(PENDING_STATE);
1741
2063
  let retryTimer;
@@ -1761,12 +2083,12 @@ function useStableAttachmentUrl(params) {
1761
2083
  }
1762
2084
 
1763
2085
  // src/participant/ParticipantAudioAttachment.tsx
1764
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
2086
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
1765
2087
  function ParticipantAudioAttachment({ attachment, resolveAttachmentUrl, labels }) {
1766
2088
  const source = useStableAttachmentUrl({ attachment, resolveAttachmentUrl });
1767
2089
  const audioLabels = resolveParticipantAudioLabels(labels);
1768
- return /* @__PURE__ */ jsxs14("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
1769
- /* @__PURE__ */ jsx19(
2090
+ return /* @__PURE__ */ jsxs15("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
2091
+ /* @__PURE__ */ jsx20(
1770
2092
  ParticipantAudioPlayer,
1771
2093
  {
1772
2094
  labels: audioLabels,
@@ -1778,17 +2100,17 @@ function ParticipantAudioAttachment({ attachment, resolveAttachmentUrl, labels }
1778
2100
  onSourceError: source.resolveAgain
1779
2101
  }
1780
2102
  ),
1781
- /* @__PURE__ */ jsx19("span", { className: "cv-p-attachment__name", children: attachment.filename }),
1782
- /* @__PURE__ */ jsx19(ParticipantAudioTranscription, { attachmentId: attachment.id, transcription: attachment.transcription, labels: audioLabels })
2103
+ /* @__PURE__ */ jsx20("span", { className: "cv-p-attachment__name", children: attachment.filename }),
2104
+ /* @__PURE__ */ jsx20(ParticipantAudioTranscription, { attachmentId: attachment.id, transcription: attachment.transcription, labels: audioLabels })
1783
2105
  ] });
1784
2106
  }
1785
2107
 
1786
2108
  // src/participant/ParticipantAttachmentItem.tsx
1787
- import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
2109
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
1788
2110
  function useAttachmentUrl(attachment, resolveAttachmentUrl) {
1789
- const [url, setUrl] = useState7(void 0);
2111
+ const [url, setUrl] = useState8(void 0);
1790
2112
  const disposition = attachment.kind === "document" ? "attachment" : "inline";
1791
- useEffect11(() => {
2113
+ useEffect12(() => {
1792
2114
  let isCurrent = true;
1793
2115
  resolveAttachmentUrl(attachment, disposition).then(
1794
2116
  (resolved) => {
@@ -1805,18 +2127,18 @@ function useAttachmentUrl(attachment, resolveAttachmentUrl) {
1805
2127
  return url;
1806
2128
  }
1807
2129
  function ParticipantAttachmentItem(props) {
1808
- if (props.attachment.kind === "audio") return /* @__PURE__ */ jsx20(ParticipantAudioAttachment, { ...props });
1809
- return /* @__PURE__ */ jsx20(ParticipantFileAttachment, { ...props });
2130
+ if (props.attachment.kind === "audio") return /* @__PURE__ */ jsx21(ParticipantAudioAttachment, { ...props });
2131
+ return /* @__PURE__ */ jsx21(ParticipantFileAttachment, { ...props });
1810
2132
  }
1811
2133
  function ParticipantFileAttachment({ attachment, resolveAttachmentUrl, labels }) {
1812
2134
  const url = useAttachmentUrl(attachment, resolveAttachmentUrl);
1813
2135
  if (attachment.kind === "image") {
1814
- return /* @__PURE__ */ jsxs15("figure", { className: "cv-p-attachment cv-p-attachment--image", children: [
1815
- url ? /* @__PURE__ */ jsx20("img", { className: "cv-p-attachment__image", src: url, alt: attachment.filename }) : null,
1816
- /* @__PURE__ */ jsx20("figcaption", { className: "cv-p-attachment__name", children: attachment.filename })
2136
+ return /* @__PURE__ */ jsxs16("figure", { className: "cv-p-attachment cv-p-attachment--image", children: [
2137
+ url ? /* @__PURE__ */ jsx21("img", { className: "cv-p-attachment__image", src: url, alt: attachment.filename }) : null,
2138
+ /* @__PURE__ */ jsx21("figcaption", { className: "cv-p-attachment__name", children: attachment.filename })
1817
2139
  ] });
1818
2140
  }
1819
- return /* @__PURE__ */ jsxs15(
2141
+ return /* @__PURE__ */ jsxs16(
1820
2142
  "a",
1821
2143
  {
1822
2144
  className: "cv-p-attachment cv-p-attachment--document",
@@ -1824,15 +2146,15 @@ function ParticipantFileAttachment({ attachment, resolveAttachmentUrl, labels })
1824
2146
  download: attachment.filename,
1825
2147
  "aria-label": `${labels.downloadAttachment}: ${attachment.filename}`,
1826
2148
  children: [
1827
- /* @__PURE__ */ jsx20("span", { className: "cv-p-attachment__name", children: attachment.filename }),
1828
- /* @__PURE__ */ jsx20("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
2149
+ /* @__PURE__ */ jsx21("span", { className: "cv-p-attachment__name", children: attachment.filename }),
2150
+ /* @__PURE__ */ jsx21("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
1829
2151
  ]
1830
2152
  }
1831
2153
  );
1832
2154
  }
1833
2155
 
1834
2156
  // src/participant/ParticipantBubbleStatus.tsx
1835
- import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
2157
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
1836
2158
  function statusText(status, labels) {
1837
2159
  const byStatus = {
1838
2160
  sending: labels.statusSending,
@@ -1849,13 +2171,13 @@ function OwnStatus({ status, labels, onRetry }) {
1849
2171
  const isVisibleText = status === "sending" || status === "queued" || status === "failed";
1850
2172
  const text = status === "failed" ? labels.statusFailedShort : statusText(status, labels);
1851
2173
  const isTextVisible = isVisibleText && !(status === "failed" && onRetry);
1852
- return /* @__PURE__ */ jsxs16("span", { className: "cv-p-bubble__status", children: [
1853
- /* @__PURE__ */ jsx21(StatusTicks, { status: ticksStatus, appearance: "stylesheet", title: isTextVisible ? void 0 : text }),
1854
- /* @__PURE__ */ jsx21("span", { className: isTextVisible ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
2174
+ return /* @__PURE__ */ jsxs17("span", { className: "cv-p-bubble__status", children: [
2175
+ /* @__PURE__ */ jsx22(StatusTicks, { status: ticksStatus, appearance: "stylesheet", title: isTextVisible ? void 0 : text }),
2176
+ /* @__PURE__ */ jsx22("span", { className: isTextVisible ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
1855
2177
  ] });
1856
2178
  }
1857
2179
  function RetryButton({ labels, onRetry, describedBy }) {
1858
- return /* @__PURE__ */ jsx21(
2180
+ return /* @__PURE__ */ jsx22(
1859
2181
  "button",
1860
2182
  {
1861
2183
  type: "button",
@@ -1864,16 +2186,16 @@ function RetryButton({ labels, onRetry, describedBy }) {
1864
2186
  "aria-describedby": describedBy,
1865
2187
  "data-cv-tooltip": labels.statusFailed,
1866
2188
  onClick: onRetry,
1867
- children: /* @__PURE__ */ jsx21("span", { className: "cv-p-failed-retry__icon", children: /* @__PURE__ */ jsx21("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx21("path", { d: "M13 8a5 5 0 1 1-1.6-3.7M13 2.5v3h-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }) }) })
2189
+ children: /* @__PURE__ */ jsx22("span", { className: "cv-p-failed-retry__icon", children: /* @__PURE__ */ jsx22("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx22("path", { d: "M13 8a5 5 0 1 1-1.6-3.7M13 2.5v3h-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }) }) })
1868
2190
  }
1869
2191
  );
1870
2192
  }
1871
2193
 
1872
2194
  // src/participant/ParticipantFailedMenu.tsx
1873
- import { useId as useId2 } from "react";
2195
+ import { useId as useId3 } from "react";
1874
2196
 
1875
2197
  // src/participant/ParticipantFailedMenuList.tsx
1876
- import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
2198
+ import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
1877
2199
  function ParticipantFailedMenuList({ menu, labelledBy, labels, onEdit, onDiscard }) {
1878
2200
  function runAndClose(action) {
1879
2201
  return () => {
@@ -1882,9 +2204,9 @@ function ParticipantFailedMenuList({ menu, labelledBy, labels, onEdit, onDiscard
1882
2204
  };
1883
2205
  }
1884
2206
  const className = `cv-p-failed-menu__list${menu.placement === "below" ? " cv-p-failed-menu__list--below" : ""}`;
1885
- return /* @__PURE__ */ jsxs17("div", { role: "menu", "aria-labelledby": labelledBy, ref: menu.menuRef, className, onKeyDown: menu.handleMenuKeyDown, children: [
1886
- onEdit ? /* @__PURE__ */ jsx22("button", { type: "button", role: "menuitem", tabIndex: -1, className: "cv-p-failed-menu__item", onClick: runAndClose(onEdit), children: labels.edit }) : null,
1887
- onDiscard ? /* @__PURE__ */ jsx22(
2207
+ return /* @__PURE__ */ jsxs18("div", { role: "menu", "aria-labelledby": labelledBy, ref: menu.menuRef, className, onKeyDown: menu.handleMenuKeyDown, children: [
2208
+ onEdit ? /* @__PURE__ */ jsx23("button", { type: "button", role: "menuitem", tabIndex: -1, className: "cv-p-failed-menu__item", onClick: runAndClose(onEdit), children: labels.edit }) : null,
2209
+ onDiscard ? /* @__PURE__ */ jsx23(
1888
2210
  "button",
1889
2211
  {
1890
2212
  type: "button",
@@ -1898,137 +2220,15 @@ function ParticipantFailedMenuList({ menu, labelledBy, labels, onEdit, onDiscard
1898
2220
  ] });
1899
2221
  }
1900
2222
 
1901
- // src/participant/useParticipantFailedMenu.ts
1902
- import { useReducer as useReducer2, useRef as useRef12 } from "react";
1903
-
1904
- // src/participant/participantFailedMenuState.ts
1905
- var CLOSED_FAILED_MENU = { isOpen: false, initialFocus: "first" };
1906
- function failedMenuReducer(state, action) {
1907
- if (action.type === "open") return { isOpen: true, initialFocus: action.initialFocus };
1908
- if (action.type === "close") return state.isOpen ? CLOSED_FAILED_MENU : state;
1909
- return state.isOpen ? CLOSED_FAILED_MENU : { isOpen: true, initialFocus: "first" };
1910
- }
1911
- function resolveFailedMenuKey({ key, index, count }) {
1912
- if (key === "Escape") return { type: "close", shouldRestoreFocus: true };
1913
- if (key === "Tab") return { type: "close", shouldRestoreFocus: false };
1914
- if (key === "ArrowDown") return { type: "focus", index: (index + 1) % count };
1915
- if (key === "ArrowUp") return { type: "focus", index: (index - 1 + count) % count };
1916
- if (key === "Home") return { type: "focus", index: 0 };
1917
- if (key === "End") return { type: "focus", index: count - 1 };
1918
- return void 0;
1919
- }
1920
- function resolveFailedMenuTriggerKey(key) {
1921
- if (key === "ArrowDown") return "first";
1922
- if (key === "ArrowUp") return "last";
1923
- return void 0;
1924
- }
1925
- var MENU_GAP_PX = 8;
1926
- function resolveFailedMenuPlacement({ anchorTop, scrollerTop, menuHeight }) {
1927
- return anchorTop - scrollerTop >= menuHeight + MENU_GAP_PX ? "above" : "below";
1928
- }
1929
-
1930
- // src/participant/failedMenuKeyboard.ts
1931
- function handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }) {
1932
- const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
1933
- const index = items.findIndex((item) => item === document.activeElement);
1934
- const result = resolveFailedMenuKey({ key: event.key, index, count: items.length });
1935
- if (!result) return;
1936
- if (result.type === "focus") {
1937
- event.preventDefault();
1938
- items[result.index]?.focus();
1939
- return;
1940
- }
1941
- if (result.shouldRestoreFocus) {
1942
- event.preventDefault();
1943
- event.stopPropagation();
1944
- triggerRef.current?.focus();
1945
- }
1946
- dispatch({ type: "close" });
1947
- }
1948
-
1949
- // src/participant/useFailedMenuEffects.ts
1950
- import { useEffect as useEffect12, useLayoutEffect as useLayoutEffect2, useState as useState8 } from "react";
1951
- var SCROLLER_SELECTOR2 = ".cv-p-thread__scroll";
1952
- function useFailedMenuInitialFocus(state, menuRef) {
1953
- useEffect12(() => {
1954
- if (!state.isOpen) return;
1955
- const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
1956
- const target = state.initialFocus === "last" ? items[items.length - 1] : items[0];
1957
- target?.focus();
1958
- }, [state, menuRef]);
1959
- }
1960
- function useFailedMenuOutsideClose(isOpen, containerRef, dispatch) {
1961
- useEffect12(() => {
1962
- if (!isOpen) return;
1963
- function handlePointerDown(event) {
1964
- if (event.target instanceof Node && containerRef.current?.contains(event.target)) return;
1965
- dispatch({ type: "close" });
1966
- }
1967
- document.addEventListener("pointerdown", handlePointerDown);
1968
- return () => document.removeEventListener("pointerdown", handlePointerDown);
1969
- }, [isOpen, containerRef, dispatch]);
1970
- }
1971
- function useFailedMenuPlacement(isOpen, { triggerRef, menuRef }) {
1972
- const [placement, setPlacement] = useState8("above");
1973
- useLayoutEffect2(() => {
1974
- const trigger = triggerRef.current;
1975
- const menu = menuRef.current;
1976
- if (!isOpen || !trigger || !menu) return;
1977
- const anchor = trigger.closest(".cv-p-bubble") ?? trigger;
1978
- const scroller = trigger.closest(SCROLLER_SELECTOR2);
1979
- setPlacement(
1980
- resolveFailedMenuPlacement({
1981
- anchorTop: anchor.getBoundingClientRect().top,
1982
- scrollerTop: scroller?.getBoundingClientRect().top ?? 0,
1983
- menuHeight: menu.getBoundingClientRect().height
1984
- })
1985
- );
1986
- }, [isOpen, triggerRef, menuRef]);
1987
- return placement;
1988
- }
1989
-
1990
- // src/participant/useParticipantFailedMenu.ts
1991
- function useParticipantFailedMenu() {
1992
- const [state, dispatch] = useReducer2(failedMenuReducer, CLOSED_FAILED_MENU);
1993
- const containerRef = useRef12(null);
1994
- const triggerRef = useRef12(null);
1995
- const menuRef = useRef12(null);
1996
- useFailedMenuInitialFocus(state, menuRef);
1997
- useFailedMenuOutsideClose(state.isOpen, containerRef, dispatch);
1998
- const placement = useFailedMenuPlacement(state.isOpen, { triggerRef, menuRef });
1999
- function handleTriggerKeyDown(event) {
2000
- const initialFocus = resolveFailedMenuTriggerKey(event.key);
2001
- if (!initialFocus) return;
2002
- event.preventDefault();
2003
- dispatch({ type: "open", initialFocus });
2004
- }
2005
- function handleBlur(event) {
2006
- const next = event.relatedTarget;
2007
- if (next instanceof Node && !containerRef.current?.contains(next)) dispatch({ type: "close" });
2008
- }
2009
- return {
2010
- state,
2011
- placement,
2012
- containerRef,
2013
- triggerRef,
2014
- menuRef,
2015
- handleTriggerClick: () => dispatch({ type: "toggle" }),
2016
- handleTriggerKeyDown,
2017
- handleMenuKeyDown: (event) => handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }),
2018
- handleBlur,
2019
- close: () => dispatch({ type: "close" })
2020
- };
2021
- }
2022
-
2023
2223
  // src/participant/ParticipantFailedMenu.tsx
2024
- import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
2224
+ import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
2025
2225
  function ParticipantFailedMenu({ labels, onEdit, onDiscard }) {
2026
2226
  const menu = useParticipantFailedMenu();
2027
- const triggerId = useId2();
2227
+ const triggerId = useId3();
2028
2228
  if (!onEdit && !onDiscard) return null;
2029
2229
  const label = labels.messageOptions ?? DEFAULT_MESSAGE_OPTIONS_LABEL;
2030
- return /* @__PURE__ */ jsxs18("span", { className: "cv-p-failed-menu", ref: menu.containerRef, onBlur: menu.handleBlur, children: [
2031
- /* @__PURE__ */ jsx23(
2230
+ return /* @__PURE__ */ jsxs19("span", { className: "cv-p-failed-menu", ref: menu.containerRef, onBlur: menu.handleBlur, children: [
2231
+ /* @__PURE__ */ jsx24(
2032
2232
  "button",
2033
2233
  {
2034
2234
  type: "button",
@@ -2041,10 +2241,10 @@ function ParticipantFailedMenu({ labels, onEdit, onDiscard }) {
2041
2241
  "data-cv-tooltip": menu.state.isOpen ? void 0 : label,
2042
2242
  onClick: menu.handleTriggerClick,
2043
2243
  onKeyDown: menu.handleTriggerKeyDown,
2044
- children: /* @__PURE__ */ jsx23("span", { "aria-hidden": "true", children: "\u22EF" })
2244
+ children: /* @__PURE__ */ jsx24("span", { "aria-hidden": "true", children: "\u22EF" })
2045
2245
  }
2046
2246
  ),
2047
- menu.state.isOpen ? /* @__PURE__ */ jsx23(ParticipantFailedMenuList, { menu, labelledBy: triggerId, labels, onEdit, onDiscard }) : null
2247
+ menu.state.isOpen ? /* @__PURE__ */ jsx24(ParticipantFailedMenuList, { menu, labelledBy: triggerId, labels, onEdit, onDiscard }) : null
2048
2248
  ] });
2049
2249
  }
2050
2250
 
@@ -2094,10 +2294,10 @@ function useParticipantCopyFeedback() {
2094
2294
  }
2095
2295
 
2096
2296
  // src/participant/ParticipantCopyableToken.tsx
2097
- import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
2297
+ import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
2098
2298
  function ParticipantCopyableToken({ copyKind, value, labels }) {
2099
2299
  const { isCopied, copy } = useParticipantCopyFeedback();
2100
- return /* @__PURE__ */ jsxs19(
2300
+ return /* @__PURE__ */ jsxs20(
2101
2301
  "button",
2102
2302
  {
2103
2303
  type: "button",
@@ -2106,8 +2306,8 @@ function ParticipantCopyableToken({ copyKind, value, labels }) {
2106
2306
  onClick: () => copy(value, labels.valueCopied),
2107
2307
  children: [
2108
2308
  value,
2109
- isCopied ? /* @__PURE__ */ jsxs19("span", { className: "cv-p-copyable__done", "aria-hidden": "true", children: [
2110
- /* @__PURE__ */ jsx24(Check, { size: 12, "aria-hidden": true }),
2309
+ isCopied ? /* @__PURE__ */ jsxs20("span", { className: "cv-p-copyable__done", "aria-hidden": "true", children: [
2310
+ /* @__PURE__ */ jsx25(Check, { size: 12, "aria-hidden": true }),
2111
2311
  " ",
2112
2312
  labels.valueCopied
2113
2313
  ] }) : null
@@ -2117,7 +2317,7 @@ function ParticipantCopyableToken({ copyKind, value, labels }) {
2117
2317
  }
2118
2318
  function ParticipantLinkCopyButton({ value, labels }) {
2119
2319
  const { isCopied, copy } = useParticipantCopyFeedback();
2120
- return /* @__PURE__ */ jsx24(
2320
+ return /* @__PURE__ */ jsx25(
2121
2321
  "button",
2122
2322
  {
2123
2323
  type: "button",
@@ -2125,26 +2325,26 @@ function ParticipantLinkCopyButton({ value, labels }) {
2125
2325
  "aria-label": `${labels.copyValue} ${value}`,
2126
2326
  "data-cv-tooltip": labels.copyValue,
2127
2327
  onClick: () => copy(value, labels.valueCopied),
2128
- children: isCopied ? /* @__PURE__ */ jsx24(Check, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx24(Copy, { size: 14, "aria-hidden": true })
2328
+ children: isCopied ? /* @__PURE__ */ jsx25(Check, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx25(Copy, { size: 14, "aria-hidden": true })
2129
2329
  }
2130
2330
  );
2131
2331
  }
2132
2332
 
2133
2333
  // src/participant/ParticipantMessageNodes.tsx
2134
- import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
2334
+ import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
2135
2335
  var LINK_REL = "noopener noreferrer nofollow ugc";
2136
2336
  function renderLink(node, labels, key) {
2137
2337
  const isExternal = /^https?:/i.test(node.href);
2138
- return /* @__PURE__ */ jsxs20("span", { className: "cv-p-text__link-group", children: [
2139
- /* @__PURE__ */ jsxs20("a", { className: "cv-p-text__link", href: node.href, rel: LINK_REL, target: isExternal ? "_blank" : void 0, children: [
2338
+ return /* @__PURE__ */ jsxs21("span", { className: "cv-p-text__link-group", children: [
2339
+ /* @__PURE__ */ jsxs21("a", { className: "cv-p-text__link", href: node.href, rel: LINK_REL, target: isExternal ? "_blank" : void 0, children: [
2140
2340
  node.label,
2141
- isExternal ? /* @__PURE__ */ jsxs20("span", { className: "cv-p-sr-only", children: [
2341
+ isExternal ? /* @__PURE__ */ jsxs21("span", { className: "cv-p-sr-only", children: [
2142
2342
  " (",
2143
2343
  labels.externalLinkHint,
2144
2344
  ")"
2145
2345
  ] }) : null
2146
2346
  ] }),
2147
- /* @__PURE__ */ jsx25(ParticipantLinkCopyButton, { value: node.href, labels })
2347
+ /* @__PURE__ */ jsx26(ParticipantLinkCopyButton, { value: node.href, labels })
2148
2348
  ] }, key);
2149
2349
  }
2150
2350
  function renderInlineNodes(nodes, labels) {
@@ -2153,17 +2353,17 @@ function renderInlineNodes(nodes, labels) {
2153
2353
  case "text":
2154
2354
  return node.value;
2155
2355
  case "strong":
2156
- return /* @__PURE__ */ jsx25("strong", { children: renderInlineNodes(node.children, labels) }, key);
2356
+ return /* @__PURE__ */ jsx26("strong", { children: renderInlineNodes(node.children, labels) }, key);
2157
2357
  case "em":
2158
- return /* @__PURE__ */ jsx25("em", { children: renderInlineNodes(node.children, labels) }, key);
2358
+ return /* @__PURE__ */ jsx26("em", { children: renderInlineNodes(node.children, labels) }, key);
2159
2359
  case "del":
2160
- return /* @__PURE__ */ jsx25("del", { children: renderInlineNodes(node.children, labels) }, key);
2360
+ return /* @__PURE__ */ jsx26("del", { children: renderInlineNodes(node.children, labels) }, key);
2161
2361
  case "code":
2162
- return /* @__PURE__ */ jsx25("code", { className: "cv-p-text__code", children: node.value }, key);
2362
+ return /* @__PURE__ */ jsx26("code", { className: "cv-p-text__code", children: node.value }, key);
2163
2363
  case "link":
2164
2364
  return renderLink(node, labels, key);
2165
2365
  case "copyable":
2166
- return /* @__PURE__ */ jsx25(ParticipantCopyableToken, { copyKind: node.copyKind, value: node.value, labels }, key);
2366
+ return /* @__PURE__ */ jsx26(ParticipantCopyableToken, { copyKind: node.copyKind, value: node.value, labels }, key);
2167
2367
  }
2168
2368
  });
2169
2369
  }
@@ -2549,35 +2749,35 @@ function startsFenceLine(line) {
2549
2749
  }
2550
2750
 
2551
2751
  // src/participant/ParticipantMessageText.tsx
2552
- import { jsx as jsx26 } from "react/jsx-runtime";
2752
+ import { jsx as jsx27 } from "react/jsx-runtime";
2553
2753
  function renderBlock(block, labels, key) {
2554
- if (block.kind === "paragraph") return /* @__PURE__ */ jsx26("span", { children: renderInlineNodes(block.children, labels) }, key);
2754
+ if (block.kind === "paragraph") return /* @__PURE__ */ jsx27("span", { children: renderInlineNodes(block.children, labels) }, key);
2555
2755
  if (block.kind === "pre") {
2556
- return /* @__PURE__ */ jsx26("pre", { className: "cv-p-text__pre", children: /* @__PURE__ */ jsx26("code", { children: block.value }) }, key);
2756
+ return /* @__PURE__ */ jsx27("pre", { className: "cv-p-text__pre", children: /* @__PURE__ */ jsx27("code", { children: block.value }) }, key);
2557
2757
  }
2558
2758
  if (block.kind === "quote") {
2559
- return /* @__PURE__ */ jsx26("blockquote", { className: "cv-p-text__quote", children: renderInlineNodes(block.children, labels) }, key);
2759
+ return /* @__PURE__ */ jsx27("blockquote", { className: "cv-p-text__quote", children: renderInlineNodes(block.children, labels) }, key);
2560
2760
  }
2561
- const items = block.items.map((item, index) => /* @__PURE__ */ jsx26("li", { children: renderInlineNodes(item, labels) }, index));
2761
+ const items = block.items.map((item, index) => /* @__PURE__ */ jsx27("li", { children: renderInlineNodes(item, labels) }, index));
2562
2762
  if (block.ordered) {
2563
- return /* @__PURE__ */ jsx26("ol", { className: "cv-p-text__list", start: block.start === 1 ? void 0 : block.start, children: items }, key);
2763
+ return /* @__PURE__ */ jsx27("ol", { className: "cv-p-text__list", start: block.start === 1 ? void 0 : block.start, children: items }, key);
2564
2764
  }
2565
- return /* @__PURE__ */ jsx26("ul", { className: "cv-p-text__list", children: items }, key);
2765
+ return /* @__PURE__ */ jsx27("ul", { className: "cv-p-text__list", children: items }, key);
2566
2766
  }
2567
2767
  function ParticipantMessageText({ text, labels, id }) {
2568
2768
  const blocks = useMemo2(() => parseParticipantMessage(text), [text]);
2569
2769
  if (blocks.length === 0) return null;
2570
2770
  const [only] = blocks;
2571
2771
  const children = blocks.length === 1 && only?.kind === "paragraph" ? renderInlineNodes(only.children, labels) : blocks.map((block, key) => renderBlock(block, labels, key));
2572
- return /* @__PURE__ */ jsx26("div", { className: "cv-p-text", id, children });
2772
+ return /* @__PURE__ */ jsx27("div", { className: "cv-p-text", id, children });
2573
2773
  }
2574
2774
 
2575
2775
  // src/participant/ParticipantBubbleBody.tsx
2576
- import { Fragment as Fragment6, jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
2776
+ import { Fragment as Fragment6, jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
2577
2777
  function BubbleHeading({ content, labels }) {
2578
- if (content.isMine) return /* @__PURE__ */ jsx27("span", { className: "cv-p-sr-only", children: labels.me });
2778
+ if (content.isMine) return /* @__PURE__ */ jsx28("span", { className: "cv-p-sr-only", children: labels.me });
2579
2779
  if (!content.authorName) return null;
2580
- return /* @__PURE__ */ jsx27("span", { className: "cv-p-bubble__author", children: content.authorName });
2780
+ return /* @__PURE__ */ jsx28("span", { className: "cv-p-bubble__author", children: content.authorName });
2581
2781
  }
2582
2782
  function hostActions(content, render) {
2583
2783
  if (!render || content.messageId === void 0) return null;
@@ -2586,23 +2786,34 @@ function hostActions(content, render) {
2586
2786
  text: content.text ?? null,
2587
2787
  authorName: content.authorName ?? null,
2588
2788
  isMine: content.isMine,
2589
- attachments: content.attachments
2789
+ attachments: content.attachments,
2790
+ createdAt: content.createdAt
2590
2791
  });
2591
2792
  }
2592
2793
  function OriginalText({ content, labels }) {
2593
2794
  const original = content.originalText?.trim();
2594
2795
  if (!original || original === content.text?.trim()) return null;
2595
- return /* @__PURE__ */ jsxs21("details", { className: "cv-p-bubble__original", children: [
2596
- /* @__PURE__ */ jsx27("summary", { className: "cv-p-bubble__original-toggle", children: labels.showOriginalText ?? DEFAULT_SHOW_ORIGINAL_TEXT_LABEL }),
2597
- /* @__PURE__ */ jsx27("div", { className: "cv-p-bubble__original-text", children: original })
2796
+ return /* @__PURE__ */ jsxs22("details", { className: "cv-p-bubble__original", children: [
2797
+ /* @__PURE__ */ jsx28("summary", { className: "cv-p-bubble__original-toggle", children: labels.showOriginalText ?? DEFAULT_SHOW_ORIGINAL_TEXT_LABEL }),
2798
+ /* @__PURE__ */ jsx28("div", { className: "cv-p-bubble__original-text", children: original })
2598
2799
  ] });
2599
2800
  }
2600
- function BubbleBody({ content, labels, ownStatus, textId, resolveAttachmentUrl, onRetry, onDiscard, onEdit, renderMessageActions }) {
2801
+ function BubbleBody({
2802
+ content,
2803
+ labels,
2804
+ ownStatus,
2805
+ textId,
2806
+ resolveAttachmentUrl,
2807
+ onRetry,
2808
+ onDiscard,
2809
+ onEdit,
2810
+ renderMessageActions
2811
+ }) {
2601
2812
  const actions = hostActions(content, renderMessageActions);
2602
- return /* @__PURE__ */ jsxs21(Fragment6, { children: [
2603
- /* @__PURE__ */ jsx27(BubbleHeading, { content, labels }),
2604
- content.text ? /* @__PURE__ */ jsx27(ParticipantMessageText, { text: content.text, labels, id: textId }) : null,
2605
- content.attachments.map((attachment) => /* @__PURE__ */ jsx27(
2813
+ return /* @__PURE__ */ jsxs22(Fragment6, { children: [
2814
+ /* @__PURE__ */ jsx28(BubbleHeading, { content, labels }),
2815
+ content.text ? /* @__PURE__ */ jsx28(ParticipantMessageText, { text: content.text, labels, id: textId }) : null,
2816
+ content.attachments.map((attachment) => /* @__PURE__ */ jsx28(
2606
2817
  ParticipantAttachmentItem,
2607
2818
  {
2608
2819
  attachment,
@@ -2611,17 +2822,319 @@ function BubbleBody({ content, labels, ownStatus, textId, resolveAttachmentUrl,
2611
2822
  },
2612
2823
  attachment.id
2613
2824
  )),
2614
- content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx27("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
2615
- /* @__PURE__ */ jsxs21("span", { className: "cv-p-bubble__meta", children: [
2616
- /* @__PURE__ */ jsx27("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
2617
- ownStatus ? /* @__PURE__ */ jsx27(OwnStatus, { status: ownStatus, labels, onRetry }) : null,
2618
- ownStatus === "failed" ? /* @__PURE__ */ jsx27(ParticipantFailedMenu, { labels, onEdit, onDiscard }) : null,
2619
- actions ? /* @__PURE__ */ jsx27("span", { className: "cv-p-bubble__host-actions", children: actions }) : null
2825
+ content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx28("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
2826
+ /* @__PURE__ */ jsxs22("span", { className: "cv-p-bubble__meta", children: [
2827
+ /* @__PURE__ */ jsx28("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
2828
+ ownStatus ? /* @__PURE__ */ jsx28(OwnStatus, { status: ownStatus, labels, onRetry }) : null,
2829
+ ownStatus === "failed" ? /* @__PURE__ */ jsx28(ParticipantFailedMenu, { labels, onEdit, onDiscard }) : null,
2830
+ actions ? /* @__PURE__ */ jsx28("span", { className: "cv-p-bubble__host-actions", children: actions }) : null
2620
2831
  ] }),
2621
- /* @__PURE__ */ jsx27(OriginalText, { content, labels })
2832
+ /* @__PURE__ */ jsx28(OriginalText, { content, labels })
2622
2833
  ] });
2623
2834
  }
2624
2835
 
2836
+ // src/participant/ParticipantMenuBubble.tsx
2837
+ import { useRef as useRef14, useState as useState11 } from "react";
2838
+
2839
+ // src/participant/ParticipantMessageMenu.tsx
2840
+ import { ChevronDown as ChevronDown2 } from "lucide-react";
2841
+ import { useCallback as useCallback6, useId as useId4, useState as useState10 } from "react";
2842
+
2843
+ // src/participant/ParticipantMessageInfo.tsx
2844
+ import { X } from "lucide-react";
2845
+ import { useEffect as useEffect14, useRef as useRef13 } from "react";
2846
+
2847
+ // src/participant/useOpenUpwards.ts
2848
+ import { useLayoutEffect as useLayoutEffect3, useState as useState9 } from "react";
2849
+ var SCROLLER_SELECTOR3 = ".cv-p-thread__scroll";
2850
+ function useOpenUpwards(isOpen, panelRef) {
2851
+ const [isUpwards, setUpwards] = useState9(false);
2852
+ useLayoutEffect3(() => {
2853
+ const panel = panelRef.current;
2854
+ if (!isOpen || !panel) {
2855
+ setUpwards(false);
2856
+ return;
2857
+ }
2858
+ const scroller = panel.closest(SCROLLER_SELECTOR3);
2859
+ if (!scroller) return;
2860
+ setUpwards(panel.getBoundingClientRect().bottom > scroller.getBoundingClientRect().bottom);
2861
+ }, [isOpen, panelRef]);
2862
+ return isUpwards;
2863
+ }
2864
+
2865
+ // src/participant/ParticipantMessageInfo.tsx
2866
+ import { jsx as jsx29, jsxs as jsxs23 } from "react/jsx-runtime";
2867
+ function ParticipantMessageInfo({ state, message, onClose }) {
2868
+ const panelRef = useRef13(null);
2869
+ const closeRef = useRef13(null);
2870
+ const { labels } = state;
2871
+ const title = labels.messageInfo ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.messageInfo ?? "";
2872
+ const rows = messageInfoRows(message, labels, state.locale);
2873
+ const isUpwards = useOpenUpwards(true, panelRef);
2874
+ useEffect14(() => {
2875
+ closeRef.current?.focus();
2876
+ function handleKeyDown(event) {
2877
+ if (event.key === "Escape") onClose();
2878
+ }
2879
+ function handlePointerDown(event) {
2880
+ if (event.target instanceof Node && !panelRef.current?.contains(event.target)) onClose();
2881
+ }
2882
+ document.addEventListener("keydown", handleKeyDown);
2883
+ document.addEventListener("pointerdown", handlePointerDown);
2884
+ return () => {
2885
+ document.removeEventListener("keydown", handleKeyDown);
2886
+ document.removeEventListener("pointerdown", handlePointerDown);
2887
+ };
2888
+ }, [onClose]);
2889
+ return /* @__PURE__ */ jsxs23(
2890
+ "div",
2891
+ {
2892
+ ref: panelRef,
2893
+ role: "dialog",
2894
+ "aria-label": title,
2895
+ className: `cv-p-failed-menu__list cv-p-message-menu__list cv-p-message-info${isUpwards ? " cv-p-message-menu__list--up" : ""}`,
2896
+ children: [
2897
+ /* @__PURE__ */ jsxs23("div", { className: "cv-p-message-info__head", children: [
2898
+ /* @__PURE__ */ jsx29("span", { className: "cv-p-message-info__title", children: title }),
2899
+ /* @__PURE__ */ jsx29(
2900
+ "button",
2901
+ {
2902
+ ref: closeRef,
2903
+ type: "button",
2904
+ className: "cv-p-message-info__close",
2905
+ "aria-label": labels.closeMessageInfo ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.closeMessageInfo,
2906
+ onClick: onClose,
2907
+ children: /* @__PURE__ */ jsx29(X, { size: 14, "aria-hidden": true })
2908
+ }
2909
+ )
2910
+ ] }),
2911
+ /* @__PURE__ */ jsx29("dl", { className: "cv-p-message-info__rows", children: rows.map((row) => /* @__PURE__ */ jsxs23("div", { className: "cv-p-message-info__row", children: [
2912
+ /* @__PURE__ */ jsx29("dt", { children: row.label }),
2913
+ /* @__PURE__ */ jsx29("dd", { children: row.value })
2914
+ ] }, row.id)) })
2915
+ ]
2916
+ }
2917
+ );
2918
+ }
2919
+
2920
+ // src/participant/ParticipantMessageMenu.tsx
2921
+ import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
2922
+ function transcriptionEntries(props, copy) {
2923
+ const { state, message } = props;
2924
+ const { labels } = state;
2925
+ const ready = readyTranscriptions(message.attachments);
2926
+ const [first] = ready;
2927
+ if (!first) return [];
2928
+ const prefix = labels.transcriptionPrefix ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.transcriptionPrefix ?? "";
2929
+ const text = ready.map((entry) => entry.text).join("\n\n");
2930
+ const author = message.isMine ? labels.me : message.authorName ?? "";
2931
+ const head = `[${formatMessageMoment(message.createdAt, state.locale)}]${author === "" ? "" : ` ${author}`}`;
2932
+ return [
2933
+ {
2934
+ id: "copy-transcription",
2935
+ label: labels.copyTranscription ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.copyTranscription ?? "",
2936
+ run: () => copy(text)
2937
+ },
2938
+ {
2939
+ id: "download-transcription",
2940
+ label: labels.downloadTranscription ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.downloadTranscription ?? "",
2941
+ run: () => downloadTextFile(transcriptionFileName(first.filename, prefix), `${head}
2942
+
2943
+ ${text}
2944
+ `)
2945
+ }
2946
+ ];
2947
+ }
2948
+ function entriesFor(props, copy, showInfo) {
2949
+ const { state, message, selectedText } = props;
2950
+ const { labels } = state;
2951
+ const text = copyableText(message);
2952
+ const own = [];
2953
+ if (selectedText !== "" && selectedText !== text) {
2954
+ own.push({
2955
+ id: "copy-selection",
2956
+ label: labels.copySelection ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.copySelection ?? "",
2957
+ run: () => copy(selectedText)
2958
+ });
2959
+ }
2960
+ if (text !== null)
2961
+ own.push({ id: "copy", label: labels.copyMessage ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.copyMessage ?? "", run: () => copy(text) });
2962
+ own.push({
2963
+ id: "select",
2964
+ label: labels.selectMessages ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.selectMessages ?? "",
2965
+ run: () => state.start(message.id)
2966
+ });
2967
+ own.push(...transcriptionEntries(props, copy));
2968
+ own.push({ id: "info", label: labels.messageInfo ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.messageInfo ?? "", run: showInfo });
2969
+ return [...own, ...(state.options.items?.(message) ?? []).map((item) => ({ ...item, run: item.onSelect }))];
2970
+ }
2971
+ function ParticipantMessageMenu(props) {
2972
+ const { menu, state } = props;
2973
+ const triggerId = useId4();
2974
+ const { copy } = useParticipantCopyFeedback();
2975
+ const label = state.labels.messageOptions ?? DEFAULT_MESSAGE_OPTIONS_LABEL;
2976
+ const [isInfoOpen, setInfoOpen] = useState10(false);
2977
+ const closeInfo = useCallback6(() => setInfoOpen(false), []);
2978
+ const entries = entriesFor(
2979
+ props,
2980
+ (value) => copy(value, state.labels.valueCopied),
2981
+ () => setInfoOpen(true)
2982
+ );
2983
+ const isUpwards = useOpenUpwards(menu.state.isOpen, menu.menuRef);
2984
+ const listClass = `cv-p-failed-menu__list cv-p-message-menu__list${isUpwards ? " cv-p-message-menu__list--up" : ""}`;
2985
+ function choose(entry) {
2986
+ menu.close();
2987
+ entry.run?.();
2988
+ }
2989
+ return /* @__PURE__ */ jsxs24(
2990
+ "span",
2991
+ {
2992
+ className: "cv-p-message-menu",
2993
+ "data-open": menu.state.isOpen || isInfoOpen ? "true" : void 0,
2994
+ ref: menu.containerRef,
2995
+ onBlur: menu.handleBlur,
2996
+ children: [
2997
+ /* @__PURE__ */ jsx30(
2998
+ "button",
2999
+ {
3000
+ type: "button",
3001
+ id: triggerId,
3002
+ ref: menu.triggerRef,
3003
+ className: "cv-p-message-menu__trigger",
3004
+ "aria-haspopup": "menu",
3005
+ "aria-expanded": menu.state.isOpen,
3006
+ "aria-label": label,
3007
+ onPointerDown: props.onTriggerPointerDown,
3008
+ onClick: menu.handleTriggerClick,
3009
+ onKeyDown: menu.handleTriggerKeyDown,
3010
+ children: /* @__PURE__ */ jsx30(ChevronDown2, { size: 16, "aria-hidden": true })
3011
+ }
3012
+ ),
3013
+ menu.state.isOpen ? /* @__PURE__ */ jsx30(
3014
+ "div",
3015
+ {
3016
+ role: "menu",
3017
+ "aria-labelledby": triggerId,
3018
+ ref: menu.menuRef,
3019
+ className: listClass,
3020
+ onKeyDown: menu.handleMenuKeyDown,
3021
+ children: entries.map(
3022
+ (entry) => entry.href ? /* @__PURE__ */ jsx30(
3023
+ "a",
3024
+ {
3025
+ role: "menuitem",
3026
+ tabIndex: -1,
3027
+ className: "cv-p-failed-menu__item cv-p-message-menu__item",
3028
+ href: entry.href,
3029
+ download: entry.download,
3030
+ target: "_blank",
3031
+ rel: "noopener noreferrer",
3032
+ "aria-disabled": entry.disabled ? "true" : void 0,
3033
+ onClick: () => choose(entry),
3034
+ children: entry.label
3035
+ },
3036
+ entry.id
3037
+ ) : /* @__PURE__ */ jsx30(
3038
+ "button",
3039
+ {
3040
+ type: "button",
3041
+ role: "menuitem",
3042
+ tabIndex: -1,
3043
+ className: "cv-p-failed-menu__item cv-p-message-menu__item",
3044
+ disabled: entry.disabled,
3045
+ onClick: () => choose(entry),
3046
+ children: entry.label
3047
+ },
3048
+ entry.id
3049
+ )
3050
+ )
3051
+ }
3052
+ ) : null,
3053
+ isInfoOpen ? /* @__PURE__ */ jsx30(ParticipantMessageInfo, { state, message: props.message, onClose: closeInfo }) : null
3054
+ ]
3055
+ }
3056
+ );
3057
+ }
3058
+
3059
+ // src/participant/ParticipantMenuBubble.tsx
3060
+ import { jsx as jsx31, jsxs as jsxs25 } from "react/jsx-runtime";
3061
+ var LONG_PRESS_MS = 500;
3062
+ function ParticipantMenuBubble({ props, content, textId, state }) {
3063
+ const menu = useParticipantFailedMenu();
3064
+ const bubbleRef = useRef14(null);
3065
+ const pressTimer = useRef14(void 0);
3066
+ const [selectedText, setSelectedText] = useState11("");
3067
+ const id = content.messageId ?? "";
3068
+ const message = state.messages.find((candidate) => candidate.id === id);
3069
+ function captureSelection() {
3070
+ if (!menu.state.isOpen) setSelectedText(selectedTextWithin(bubbleRef.current));
3071
+ }
3072
+ function openFromGesture() {
3073
+ captureSelection();
3074
+ if (!menu.state.isOpen) menu.open();
3075
+ }
3076
+ function cancelPress() {
3077
+ clearTimeout(pressTimer.current);
3078
+ }
3079
+ if (state.isSelecting)
3080
+ return /* @__PURE__ */ jsx31(SelectRow, { state, id, children: renderParticipantBubble({ props, content, textId }) });
3081
+ if (!message) return renderParticipantBubble({ props, content, textId });
3082
+ return renderParticipantBubble({
3083
+ props,
3084
+ content,
3085
+ textId,
3086
+ frame: {
3087
+ ref: bubbleRef,
3088
+ className: menu.state.isOpen ? "cv-p-bubble--menu-open" : void 0,
3089
+ handlers: {
3090
+ onContextMenu: (event) => {
3091
+ event.preventDefault();
3092
+ openFromGesture();
3093
+ },
3094
+ onPointerDown: (event) => {
3095
+ if (event.pointerType !== "touch") return;
3096
+ cancelPress();
3097
+ pressTimer.current = setTimeout(openFromGesture, LONG_PRESS_MS);
3098
+ },
3099
+ onPointerUp: cancelPress,
3100
+ onPointerCancel: cancelPress,
3101
+ onPointerMove: cancelPress
3102
+ },
3103
+ corner: /* @__PURE__ */ jsx31(
3104
+ ParticipantMessageMenu,
3105
+ {
3106
+ menu,
3107
+ state,
3108
+ message,
3109
+ selectedText,
3110
+ onTriggerPointerDown: captureSelection
3111
+ }
3112
+ )
3113
+ }
3114
+ });
3115
+ }
3116
+ function SelectRow({ state, id, children }) {
3117
+ const isSelected = state.selectedIds.includes(id);
3118
+ const label = state.labels.selectMessage ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.selectMessage ?? "";
3119
+ return /* @__PURE__ */ jsxs25("div", { className: "cv-p-select-row", "data-selected": isSelected ? "true" : void 0, onClick: () => state.toggle(id), children: [
3120
+ /* @__PURE__ */ jsx31(
3121
+ "input",
3122
+ {
3123
+ type: "checkbox",
3124
+ className: "cv-p-select-row__check",
3125
+ checked: isSelected,
3126
+ "aria-label": label,
3127
+ onChange: () => state.toggle(id),
3128
+ onClick: (event) => event.stopPropagation()
3129
+ }
3130
+ ),
3131
+ children
3132
+ ] });
3133
+ }
3134
+
3135
+ // src/participant/ParticipantMessageMenuContext.tsx
3136
+ import { createContext as createContext2, useCallback as useCallback7, useContext as useContext2, useEffect as useEffect15, useMemo as useMemo3, useState as useState12 } from "react";
3137
+
2625
3138
  // src/participant/participantPerspective.ts
2626
3139
  var DEFAULT_PARTICIPANT_PERSPECTIVE = "participant";
2627
3140
  var OWN_DIRECTION = {
@@ -2683,8 +3196,74 @@ function resolveOwnMessageStatus(params) {
2683
3196
  return serverStatus;
2684
3197
  }
2685
3198
 
3199
+ // src/participant/ParticipantMessageMenuContext.tsx
3200
+ import { jsx as jsx32 } from "react/jsx-runtime";
3201
+ var MessageMenuContext = createContext2(null);
3202
+ function useParticipantMessageMenu() {
3203
+ return useContext2(MessageMenuContext);
3204
+ }
3205
+ function toMessageTargets(items, perspective) {
3206
+ return items.flatMap(
3207
+ (item) => item.kind === "server" ? [
3208
+ {
3209
+ id: item.message.id,
3210
+ text: item.message.text ?? null,
3211
+ authorName: item.message.authorName ?? null,
3212
+ isMine: isOwnMessage(item.message, perspective),
3213
+ attachments: item.message.attachments,
3214
+ createdAt: item.message.createdAt,
3215
+ status: item.message.status,
3216
+ readAt: item.message.readAt ?? null
3217
+ }
3218
+ ] : []
3219
+ );
3220
+ }
3221
+ function ParticipantMessageMenuProvider({
3222
+ options,
3223
+ labels,
3224
+ locale,
3225
+ items,
3226
+ perspective,
3227
+ children
3228
+ }) {
3229
+ const [selection, setSelection] = useState12({
3230
+ isSelecting: false,
3231
+ ids: []
3232
+ });
3233
+ const clear = useCallback7(() => setSelection({ isSelecting: false, ids: [] }), []);
3234
+ const start = useCallback7((id) => setSelection({ isSelecting: true, ids: id ? [id] : [] }), []);
3235
+ const toggle = useCallback7(
3236
+ (id) => setSelection((current) => ({ ...current, ids: toggleSelectedId(current.ids, id) })),
3237
+ []
3238
+ );
3239
+ const messages = useMemo3(() => toMessageTargets(items, perspective), [items, perspective]);
3240
+ useEffect15(() => {
3241
+ if (!selection.isSelecting) return void 0;
3242
+ function handleKeyDown(event) {
3243
+ if (event.key === "Escape") clear();
3244
+ }
3245
+ document.addEventListener("keydown", handleKeyDown);
3246
+ return () => document.removeEventListener("keydown", handleKeyDown);
3247
+ }, [selection.isSelecting, clear]);
3248
+ const value = useMemo3(
3249
+ () => options ? {
3250
+ options,
3251
+ labels,
3252
+ locale,
3253
+ messages,
3254
+ selectedIds: selection.ids,
3255
+ isSelecting: selection.isSelecting,
3256
+ start,
3257
+ toggle,
3258
+ clear
3259
+ } : null,
3260
+ [options, labels, locale, messages, selection, start, toggle, clear]
3261
+ );
3262
+ return /* @__PURE__ */ jsx32(MessageMenuContext.Provider, { value, children });
3263
+ }
3264
+
2686
3265
  // src/participant/ParticipantMessageBubble.tsx
2687
- import { jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
3266
+ import { jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
2688
3267
  function describeItem(item, perspective) {
2689
3268
  if (item.kind === "server") {
2690
3269
  const { message } = item;
@@ -2716,23 +3295,32 @@ function bubbleClassName(isMine, hasTail, hasFailed) {
2716
3295
  if (!hasTail) classes.push("cv-p-bubble--no-tail");
2717
3296
  return classes.join(" ");
2718
3297
  }
2719
- function ParticipantMessageBubble(props) {
2720
- const { item, labels, confirmsRead, onRetry, avatar, tail = true, perspective } = props;
2721
- const textId = useId3();
2722
- const content = describeItem(item, perspective);
3298
+ function renderParticipantBubble({ props, content, textId, frame }) {
3299
+ const { confirmsRead, onRetry, avatar, tail = true } = props;
2723
3300
  const ownStatus = content.isMine ? resolveOwnMessageStatus({ displayState: content.displayState, serverStatus: content.serverStatus, confirmsRead }) : void 0;
2724
3301
  const isFailed = ownStatus === "failed";
2725
3302
  const canRetry = isFailed && onRetry !== void 0;
2726
- const bubble = /* @__PURE__ */ jsxs22("div", { className: bubbleClassName(content.isMine, tail, isFailed), children: [
2727
- canRetry ? /* @__PURE__ */ jsx28(RetryButton, { labels, onRetry, describedBy: textId }) : null,
2728
- /* @__PURE__ */ jsx28(BubbleBody, { ...props, content, ownStatus, textId: canRetry ? textId : void 0 })
3303
+ const className = [bubbleClassName(content.isMine, tail, isFailed), frame?.className].filter(Boolean).join(" ");
3304
+ const bubble = /* @__PURE__ */ jsxs26("div", { className, ref: frame?.ref, ...frame?.handlers, children: [
3305
+ canRetry ? /* @__PURE__ */ jsx33(RetryButton, { labels: props.labels, onRetry, describedBy: textId }) : null,
3306
+ /* @__PURE__ */ jsx33(BubbleBody, { ...props, content, ownStatus, textId: canRetry ? textId : void 0 }),
3307
+ frame?.corner
2729
3308
  ] });
2730
3309
  if (avatar === void 0 || content.isMine) return bubble;
2731
- return /* @__PURE__ */ jsxs22("div", { className: "cv-p-bubble-row", children: [
2732
- /* @__PURE__ */ jsx28("span", { className: "cv-p-bubble-row__avatar", children: avatar }),
3310
+ return /* @__PURE__ */ jsxs26("div", { className: "cv-p-bubble-row", children: [
3311
+ /* @__PURE__ */ jsx33("span", { className: "cv-p-bubble-row__avatar", children: avatar }),
2733
3312
  bubble
2734
3313
  ] });
2735
3314
  }
3315
+ function ParticipantMessageBubble(props) {
3316
+ const textId = useId5();
3317
+ const menu = useParticipantMessageMenu();
3318
+ const content = describeItem(props.item, props.perspective);
3319
+ if (menu && content.messageId !== void 0) {
3320
+ return /* @__PURE__ */ jsx33(ParticipantMenuBubble, { props, content, textId, state: menu });
3321
+ }
3322
+ return renderParticipantBubble({ props, content, textId });
3323
+ }
2736
3324
 
2737
3325
  // src/participant/participantBubbleActions.ts
2738
3326
  function bindPendingActions(actions, clientMessageId) {
@@ -2751,7 +3339,7 @@ function resolveParticipantBubbleActions(item, handlers) {
2751
3339
  }
2752
3340
 
2753
3341
  // src/participant/ParticipantTimeline.tsx
2754
- import { jsx as jsx29 } from "react/jsx-runtime";
3342
+ import { jsx as jsx34 } from "react/jsx-runtime";
2755
3343
  function createdAtOf2(item) {
2756
3344
  return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
2757
3345
  }
@@ -2768,7 +3356,7 @@ function resolveActions(item, pendingActions) {
2768
3356
  }
2769
3357
  function resolveAvatarSlot({ isEnabled, isMine, showAvatar, author, render }) {
2770
3358
  if (!isEnabled || isMine) return void 0;
2771
- return showAvatar ? /* @__PURE__ */ jsx29(ParticipantAuthorAvatar, { name: author, render }) : null;
3359
+ return showAvatar ? /* @__PURE__ */ jsx34(ParticipantAuthorAvatar, { name: author, render }) : null;
2772
3360
  }
2773
3361
  function ParticipantTimeline(props) {
2774
3362
  const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
@@ -2780,7 +3368,7 @@ function ParticipantTimeline(props) {
2780
3368
  for (const item of items) {
2781
3369
  const day = new Date(createdAtOf2(item));
2782
3370
  if (previousDay === void 0 || !isSameDay(previousDay, day)) {
2783
- nodes.push(/* @__PURE__ */ jsx29(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
3371
+ nodes.push(/* @__PURE__ */ jsx34(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
2784
3372
  previousAuthor = void 0;
2785
3373
  }
2786
3374
  previousDay = day;
@@ -2795,7 +3383,7 @@ function ParticipantTimeline(props) {
2795
3383
  render: props.renderAuthorAvatar
2796
3384
  });
2797
3385
  nodes.push(
2798
- /* @__PURE__ */ jsx29(
3386
+ /* @__PURE__ */ jsx34(
2799
3387
  ParticipantMessageBubble,
2800
3388
  {
2801
3389
  item,
@@ -2812,19 +3400,70 @@ function ParticipantTimeline(props) {
2812
3400
  )
2813
3401
  );
2814
3402
  }
2815
- return /* @__PURE__ */ jsx29("div", { className: "cv-p-thread__messages", children: nodes });
3403
+ return /* @__PURE__ */ jsx34("div", { className: "cv-p-thread__messages", children: nodes });
3404
+ }
3405
+
3406
+ // src/participant/ParticipantSelectionBar.tsx
3407
+ import { X as X2 } from "lucide-react";
3408
+ import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
3409
+ function ParticipantSelectionBar({ state }) {
3410
+ const { labels } = state;
3411
+ const { copy } = useParticipantCopyFeedback();
3412
+ const selected = selectedInOrder(state.messages, state.selectedIds);
3413
+ const count = formatParticipantLabel(labels.selectedCount ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.selectedCount ?? "{count}", selected.length);
3414
+ const actions = state.options.selectionActions?.(selected) ?? [];
3415
+ const isEmpty = selected.length === 0;
3416
+ function copySelected() {
3417
+ copy(
3418
+ formatMessagesForCopy(selected, {
3419
+ locale: state.locale,
3420
+ meLabel: labels.me,
3421
+ transcriptionLabel: labels.transcriptionPrefix ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.transcriptionPrefix
3422
+ }),
3423
+ labels.valueCopied
3424
+ );
3425
+ state.clear();
3426
+ }
3427
+ return /* @__PURE__ */ jsxs27("div", { className: "cv-p-selection-bar", role: "toolbar", "aria-label": count, children: [
3428
+ /* @__PURE__ */ jsx35(
3429
+ "button",
3430
+ {
3431
+ type: "button",
3432
+ className: "cv-p-selection-bar__close",
3433
+ "aria-label": labels.cancelSelection ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.cancelSelection,
3434
+ onClick: state.clear,
3435
+ children: /* @__PURE__ */ jsx35(X2, { size: 18, "aria-hidden": true })
3436
+ }
3437
+ ),
3438
+ /* @__PURE__ */ jsx35("span", { className: "cv-p-selection-bar__count", "aria-live": "polite", children: count }),
3439
+ /* @__PURE__ */ jsx35("button", { type: "button", className: "cv-p-selection-bar__action", disabled: isEmpty, onClick: copySelected, children: labels.copyMessage ?? DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS.copyMessage }),
3440
+ actions.map((action) => /* @__PURE__ */ jsx35(
3441
+ "button",
3442
+ {
3443
+ type: "button",
3444
+ className: "cv-p-selection-bar__action",
3445
+ disabled: isEmpty || action.disabled,
3446
+ onClick: () => {
3447
+ action.onSelect(selected);
3448
+ state.clear();
3449
+ },
3450
+ children: action.label
3451
+ },
3452
+ action.id
3453
+ ))
3454
+ ] });
2816
3455
  }
2817
3456
 
2818
3457
  // src/participant/ParticipantConversationAvatar.tsx
2819
- import { jsx as jsx30 } from "react/jsx-runtime";
3458
+ import { jsx as jsx36 } from "react/jsx-runtime";
2820
3459
  function ParticipantConversationAvatar({ icon }) {
2821
- return /* @__PURE__ */ jsx30("span", { className: "cv-p-thread__avatar", "aria-hidden": "true", children: icon });
3460
+ return /* @__PURE__ */ jsx36("span", { className: "cv-p-thread__avatar", "aria-hidden": "true", children: icon });
2822
3461
  }
2823
3462
 
2824
3463
  // src/participant/ParticipantProtocolBadge.tsx
2825
3464
  import { Check as Check2, Copy as Copy2 } from "lucide-react";
2826
- import { useEffect as useEffect14, useReducer as useReducer4 } from "react";
2827
- import { Fragment as Fragment7, jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
3465
+ import { useEffect as useEffect16, useReducer as useReducer4 } from "react";
3466
+ import { Fragment as Fragment7, jsx as jsx37, jsxs as jsxs28 } from "react/jsx-runtime";
2828
3467
  var COPIED_ANNOUNCEMENT_MS = 3e3;
2829
3468
  function ParticipantProtocolBadgeView({
2830
3469
  protocol,
@@ -2833,16 +3472,16 @@ function ParticipantProtocolBadgeView({
2833
3472
  onCopy
2834
3473
  }) {
2835
3474
  const { isCopied, copyCount } = copyState;
2836
- return /* @__PURE__ */ jsxs23(Fragment7, { children: [
2837
- /* @__PURE__ */ jsxs23("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
2838
- /* @__PURE__ */ jsxs23("span", { className: "cv-p-protocol__code", children: [
2839
- /* @__PURE__ */ jsxs23("span", { className: "cv-p-sr-only", children: [
3475
+ return /* @__PURE__ */ jsxs28(Fragment7, { children: [
3476
+ /* @__PURE__ */ jsxs28("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
3477
+ /* @__PURE__ */ jsxs28("span", { className: "cv-p-protocol__code", children: [
3478
+ /* @__PURE__ */ jsxs28("span", { className: "cv-p-sr-only", children: [
2840
3479
  labels.protocolPrefix,
2841
3480
  " "
2842
3481
  ] }),
2843
3482
  protocol
2844
3483
  ] }),
2845
- /* @__PURE__ */ jsxs23(
3484
+ /* @__PURE__ */ jsxs28(
2846
3485
  "button",
2847
3486
  {
2848
3487
  type: "button",
@@ -2851,19 +3490,19 @@ function ParticipantProtocolBadgeView({
2851
3490
  "data-cv-tooltip": labels.copyProtocol,
2852
3491
  onClick: onCopy,
2853
3492
  children: [
2854
- isCopied ? /* @__PURE__ */ jsx31(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx31(Copy2, { size: 18, "aria-hidden": true }),
2855
- isCopied ? /* @__PURE__ */ jsx31("span", { className: "cv-p-protocol__copied", "aria-hidden": "true", children: labels.protocolCopied }) : null
3493
+ isCopied ? /* @__PURE__ */ jsx37(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx37(Copy2, { size: 18, "aria-hidden": true }),
3494
+ isCopied ? /* @__PURE__ */ jsx37("span", { className: "cv-p-protocol__copied", "aria-hidden": "true", children: labels.protocolCopied }) : null
2856
3495
  ]
2857
3496
  }
2858
3497
  )
2859
3498
  ] }),
2860
- /* @__PURE__ */ jsx31("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx31("span", { children: labels.protocolCopied }, copyCount) : null })
3499
+ /* @__PURE__ */ jsx37("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx37("span", { children: labels.protocolCopied }, copyCount) : null })
2861
3500
  ] });
2862
3501
  }
2863
3502
  function ParticipantProtocolBadge({ protocol, labels }) {
2864
3503
  const [copyState, dispatch] = useReducer4(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
2865
3504
  const { isCopied, copyCount } = copyState;
2866
- useEffect14(() => {
3505
+ useEffect16(() => {
2867
3506
  if (!isCopied) return void 0;
2868
3507
  const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_ANNOUNCEMENT_MS);
2869
3508
  return () => clearTimeout(timer);
@@ -2872,7 +3511,7 @@ function ParticipantProtocolBadge({ protocol, labels }) {
2872
3511
  const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
2873
3512
  if (await copyProtocolToClipboard(protocol, clipboard)) dispatch({ type: "copied" });
2874
3513
  }
2875
- return /* @__PURE__ */ jsx31(
3514
+ return /* @__PURE__ */ jsx37(
2876
3515
  ParticipantProtocolBadgeView,
2877
3516
  {
2878
3517
  protocol,
@@ -2884,19 +3523,19 @@ function ParticipantProtocolBadge({ protocol, labels }) {
2884
3523
  }
2885
3524
 
2886
3525
  // src/participant/ParticipantThreadHeader.tsx
2887
- import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
3526
+ import { jsx as jsx38, jsxs as jsxs29 } from "react/jsx-runtime";
2888
3527
  function Title({ conversation, labels, onOpenSubject }) {
2889
3528
  const { subjectType, subjectId, subjectLabel } = conversation;
2890
- return /* @__PURE__ */ jsx32("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx32(
3529
+ return /* @__PURE__ */ jsx38("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx38(
2891
3530
  "button",
2892
3531
  {
2893
3532
  type: "button",
2894
3533
  className: "cv-p-thread__title-button",
2895
3534
  "aria-label": `${labels.openSubject}: ${subjectLabel}`,
2896
3535
  onClick: () => onOpenSubject({ subjectType, subjectId }),
2897
- children: /* @__PURE__ */ jsx32("span", { className: "cv-p-thread__title-text", children: subjectLabel })
3536
+ children: /* @__PURE__ */ jsx38("span", { className: "cv-p-thread__title-text", children: subjectLabel })
2898
3537
  }
2899
- ) : /* @__PURE__ */ jsx32("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
3538
+ ) : /* @__PURE__ */ jsx38("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
2900
3539
  }
2901
3540
  function ParticipantThreadHeader({
2902
3541
  conversation,
@@ -2911,34 +3550,36 @@ function ParticipantThreadHeader({
2911
3550
  const group = subjectGroups?.find((candidate) => candidate.subjectType === conversation.subjectType);
2912
3551
  const icon = resolveConversationIcon({ conversation, group, renderSubjectIcon });
2913
3552
  const hasChannels = resolveParticipantChannels(conversation.channels).length > 0;
2914
- return /* @__PURE__ */ jsxs24("header", { className: "cv-p-thread__header", children: [
2915
- onBack ? /* @__PURE__ */ jsx32(ParticipantBackButton, { label: labels.back, onBack }) : null,
2916
- icon !== void 0 ? /* @__PURE__ */ jsx32(ParticipantConversationAvatar, { icon }) : null,
2917
- /* @__PURE__ */ jsxs24("div", { className: "cv-p-thread__heading", children: [
2918
- icon === void 0 && group ? /* @__PURE__ */ jsx32("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
2919
- /* @__PURE__ */ jsx32(Title, { conversation, labels, onOpenSubject }),
2920
- counterpartLabel ? /* @__PURE__ */ jsx32("p", { className: "cv-p-thread__counterpart", children: counterpartLabel }) : null,
2921
- conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs24("div", { className: "cv-p-thread__meta", children: [
2922
- conversation.protocol ? /* @__PURE__ */ jsx32(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
2923
- /* @__PURE__ */ jsx32(ParticipantChannelBadges, { channels: conversation.channels, labels, variant: "inline", iconSize: 16 })
3553
+ return /* @__PURE__ */ jsxs29("header", { className: "cv-p-thread__header", children: [
3554
+ onBack ? /* @__PURE__ */ jsx38(ParticipantBackButton, { label: labels.back, onBack }) : null,
3555
+ icon !== void 0 ? /* @__PURE__ */ jsx38(ParticipantConversationAvatar, { icon }) : null,
3556
+ /* @__PURE__ */ jsxs29("div", { className: "cv-p-thread__heading", children: [
3557
+ icon === void 0 && group ? /* @__PURE__ */ jsx38("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
3558
+ /* @__PURE__ */ jsx38(Title, { conversation, labels, onOpenSubject }),
3559
+ counterpartLabel ? /* @__PURE__ */ jsx38("p", { className: "cv-p-thread__counterpart", children: counterpartLabel }) : null,
3560
+ conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs29("div", { className: "cv-p-thread__meta", children: [
3561
+ conversation.protocol ? /* @__PURE__ */ jsx38(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
3562
+ /* @__PURE__ */ jsx38(ParticipantChannelBadges, { channels: conversation.channels, labels, variant: "inline", iconSize: 16 })
2924
3563
  ] }) : null
2925
3564
  ] }),
2926
- actions ? /* @__PURE__ */ jsx32("div", { className: "cv-p-thread__actions", children: actions }) : null
3565
+ actions ? /* @__PURE__ */ jsx38("div", { className: "cv-p-thread__actions", children: actions }) : null
2927
3566
  ] });
2928
3567
  }
2929
3568
 
2930
3569
  // src/participant/ParticipantThread.tsx
2931
- import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
3570
+ import { Fragment as Fragment8, jsx as jsx39, jsxs as jsxs30 } from "react/jsx-runtime";
2932
3571
  function Footer({ props }) {
2933
3572
  const { conversation, labels, draft } = props;
3573
+ const menu = useParticipantMessageMenu();
3574
+ if (menu?.isSelecting) return /* @__PURE__ */ jsx39(ParticipantSelectionBar, { state: menu });
2934
3575
  if (conversation.status === "closed") {
2935
- return /* @__PURE__ */ jsx33("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
3576
+ return /* @__PURE__ */ jsx39("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
2936
3577
  }
2937
3578
  const lock = props.renderComposerLock?.(conversation);
2938
3579
  if (lock !== void 0 && lock !== null) {
2939
- return /* @__PURE__ */ jsx33("div", { className: "cv-p-thread__lock", role: "region", "aria-live": "polite", children: lock });
3580
+ return /* @__PURE__ */ jsx39("div", { className: "cv-p-thread__lock", role: "region", "aria-live": "polite", children: lock });
2940
3581
  }
2941
- return /* @__PURE__ */ jsx33(
3582
+ return /* @__PURE__ */ jsx39(
2942
3583
  ParticipantComposer,
2943
3584
  {
2944
3585
  value: draft.value,
@@ -2958,7 +3599,12 @@ function Footer({ props }) {
2958
3599
  );
2959
3600
  }
2960
3601
  function Header({ props }) {
2961
- return /* @__PURE__ */ jsx33(
3602
+ const menu = useParticipantMessageMenu();
3603
+ const actions = menu ? /* @__PURE__ */ jsxs30(Fragment8, { children: [
3604
+ props.headerActions,
3605
+ /* @__PURE__ */ jsx39(ParticipantConversationMenu, { state: menu, title: props.conversation.subjectLabel })
3606
+ ] }) : props.headerActions;
3607
+ return /* @__PURE__ */ jsx39(
2962
3608
  ParticipantThreadHeader,
2963
3609
  {
2964
3610
  conversation: props.conversation,
@@ -2968,7 +3614,7 @@ function Header({ props }) {
2968
3614
  onBack: props.onBack,
2969
3615
  onOpenSubject: props.onOpenSubject,
2970
3616
  counterpartLabel: props.counterpartLabel,
2971
- actions: props.headerActions
3617
+ actions
2972
3618
  }
2973
3619
  );
2974
3620
  }
@@ -2976,17 +3622,17 @@ function ThreadBody(props) {
2976
3622
  const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
2977
3623
  const subjectCard = renderSubjectCard?.(conversation);
2978
3624
  const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
2979
- const rootRef = useRef13(null);
3625
+ const rootRef = useRef15(null);
2980
3626
  useSuppressTouchTooltips(rootRef);
2981
- return /* @__PURE__ */ jsxs25("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, ref: rootRef, children: [
2982
- /* @__PURE__ */ jsx33(Header, { props }),
2983
- subjectCard ? /* @__PURE__ */ jsx33("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
2984
- /* @__PURE__ */ jsxs25("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
2985
- loadView.isLoading ? /* @__PURE__ */ jsx33(ParticipantLoading, { labels }) : null,
2986
- loadView.hasError ? /* @__PURE__ */ jsx33(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
2987
- hasMore && onLoadOlder ? /* @__PURE__ */ jsx33("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
2988
- /* @__PURE__ */ jsx33(ParticipantTimeline, { ...props }),
2989
- /* @__PURE__ */ jsx33(
3627
+ return /* @__PURE__ */ jsxs30("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, ref: rootRef, children: [
3628
+ /* @__PURE__ */ jsx39(Header, { props }),
3629
+ subjectCard ? /* @__PURE__ */ jsx39("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
3630
+ /* @__PURE__ */ jsxs30("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
3631
+ loadView.isLoading ? /* @__PURE__ */ jsx39(ParticipantLoading, { labels }) : null,
3632
+ loadView.hasError ? /* @__PURE__ */ jsx39(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
3633
+ hasMore && onLoadOlder ? /* @__PURE__ */ jsx39("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
3634
+ /* @__PURE__ */ jsx39(ParticipantTimeline, { ...props }),
3635
+ /* @__PURE__ */ jsx39(
2990
3636
  ParticipantScrollToLatest,
2991
3637
  {
2992
3638
  label: labels.scrollToLatest,
@@ -2997,19 +3643,29 @@ function ThreadBody(props) {
2997
3643
  }
2998
3644
  )
2999
3645
  ] }),
3000
- /* @__PURE__ */ jsx33(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
3001
- /* @__PURE__ */ jsx33(Footer, { props })
3646
+ /* @__PURE__ */ jsx39(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
3647
+ /* @__PURE__ */ jsx39(Footer, { props })
3002
3648
  ] });
3003
3649
  }
3004
3650
  function ParticipantThread(props) {
3005
- return /* @__PURE__ */ jsxs25(ParticipantCopyAnnouncer, { children: [
3006
- /* @__PURE__ */ jsx33(ThreadBody, { ...props }),
3007
- /* @__PURE__ */ jsx33(TooltipLayer, {})
3651
+ return /* @__PURE__ */ jsxs30(ParticipantCopyAnnouncer, { children: [
3652
+ /* @__PURE__ */ jsx39(
3653
+ ParticipantMessageMenuProvider,
3654
+ {
3655
+ options: props.messageMenu,
3656
+ labels: props.labels,
3657
+ locale: props.locale,
3658
+ items: props.items,
3659
+ perspective: props.perspective,
3660
+ children: /* @__PURE__ */ jsx39(ThreadBody, { ...props })
3661
+ }
3662
+ ),
3663
+ /* @__PURE__ */ jsx39(TooltipLayer, {})
3008
3664
  ] });
3009
3665
  }
3010
3666
 
3011
3667
  // src/participant/useParticipantThreadController.ts
3012
- import { useMemo as useMemo5 } from "react";
3668
+ import { useMemo as useMemo6 } from "react";
3013
3669
 
3014
3670
  // src/participant/bindAttachmentUrlResolver.ts
3015
3671
  function bindAttachmentUrlResolver(api) {
@@ -3093,7 +3749,7 @@ function appendEntryToDraft(entry, current) {
3093
3749
  }
3094
3750
 
3095
3751
  // src/participant/useParticipantConversationView.ts
3096
- import { useCallback as useCallback9, useMemo as useMemo4 } from "react";
3752
+ import { useCallback as useCallback11, useMemo as useMemo5 } from "react";
3097
3753
 
3098
3754
  // src/participant/participantDrafts.ts
3099
3755
  var EMPTY_PARTICIPANT_DRAFT = { text: "", files: [] };
@@ -3137,7 +3793,7 @@ function participantSendStatesReducer(states, { subject, action }) {
3137
3793
  }
3138
3794
 
3139
3795
  // src/participant/useParticipantThread.ts
3140
- import { useCallback as useCallback8, useEffect as useEffect18, useMemo as useMemo3, useReducer as useReducer5, useRef as useRef17 } from "react";
3796
+ import { useCallback as useCallback10, useEffect as useEffect20, useMemo as useMemo4, useReducer as useReducer5, useRef as useRef19 } from "react";
3141
3797
 
3142
3798
  // src/participant/participantThreadState.ts
3143
3799
  var INITIAL_PARTICIPANT_THREAD_STATE = {
@@ -3173,7 +3829,7 @@ function participantThreadReducer(state, action) {
3173
3829
  }
3174
3830
 
3175
3831
  // src/participant/useParticipantMarkRead.ts
3176
- import { useEffect as useEffect16 } from "react";
3832
+ import { useEffect as useEffect18 } from "react";
3177
3833
 
3178
3834
  // src/participant/shouldMarkRead.ts
3179
3835
  function shouldMarkParticipantRead(params) {
@@ -3185,12 +3841,12 @@ function shouldMarkParticipantRead(params) {
3185
3841
  }
3186
3842
 
3187
3843
  // src/participant/useDocumentVisibility.ts
3188
- import { useEffect as useEffect15, useState as useState9 } from "react";
3844
+ import { useEffect as useEffect17, useState as useState13 } from "react";
3189
3845
  function useDocumentVisibility() {
3190
- const [visibilityState, setVisibilityState] = useState9(
3846
+ const [visibilityState, setVisibilityState] = useState13(
3191
3847
  () => typeof document === "undefined" ? "visible" : document.visibilityState
3192
3848
  );
3193
- useEffect15(() => {
3849
+ useEffect17(() => {
3194
3850
  function handleVisibilityChange() {
3195
3851
  setVisibilityState(document.visibilityState);
3196
3852
  }
@@ -3205,18 +3861,18 @@ function useDocumentVisibility() {
3205
3861
  function useParticipantMarkRead(params) {
3206
3862
  const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
3207
3863
  const visibilityState = useDocumentVisibility();
3208
- useEffect16(() => {
3864
+ useEffect18(() => {
3209
3865
  if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
3210
3866
  void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
3211
3867
  }, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
3212
3868
  }
3213
3869
 
3214
3870
  // src/participant/useParticipantRevalidation.ts
3215
- import { useEffect as useEffect17, useRef as useRef14 } from "react";
3871
+ import { useEffect as useEffect19, useRef as useRef16 } from "react";
3216
3872
  function useParticipantRevalidation(api, onRevalidate) {
3217
- const handlerRef = useRef14(onRevalidate);
3873
+ const handlerRef = useRef16(onRevalidate);
3218
3874
  handlerRef.current = onRevalidate;
3219
- useEffect17(() => {
3875
+ useEffect19(() => {
3220
3876
  function handleFocus() {
3221
3877
  handlerRef.current();
3222
3878
  }
@@ -3243,12 +3899,12 @@ function useParticipantRevalidation(api, onRevalidate) {
3243
3899
  }
3244
3900
 
3245
3901
  // src/participant/useParticipantSend.ts
3246
- import { useCallback as useCallback6, useRef as useRef15 } from "react";
3902
+ import { useCallback as useCallback8, useRef as useRef17 } from "react";
3247
3903
  function useParticipantSend(params) {
3248
3904
  const { apiRef, subject, dispatchSend, onSentMessage } = params;
3249
- const entriesRef = useRef15(params.entries);
3905
+ const entriesRef = useRef17(params.entries);
3250
3906
  entriesRef.current = params.entries;
3251
- const deliver = useCallback6(
3907
+ const deliver = useCallback8(
3252
3908
  async (entry) => {
3253
3909
  const { clientMessageId } = entry;
3254
3910
  try {
@@ -3272,7 +3928,7 @@ function useParticipantSend(params) {
3272
3928
  },
3273
3929
  [apiRef, dispatchSend, onSentMessage]
3274
3930
  );
3275
- const send = useCallback6(
3931
+ const send = useCallback8(
3276
3932
  async (content) => {
3277
3933
  const entry = createParticipantSendEntry({
3278
3934
  clientMessageId: crypto.randomUUID(),
@@ -3285,7 +3941,7 @@ function useParticipantSend(params) {
3285
3941
  },
3286
3942
  [deliver, dispatchSend, subject]
3287
3943
  );
3288
- const retry = useCallback6(
3944
+ const retry = useCallback8(
3289
3945
  async (clientMessageId) => {
3290
3946
  const entry = findFailedEntry(entriesRef.current, clientMessageId);
3291
3947
  if (!entry) return;
@@ -3298,13 +3954,13 @@ function useParticipantSend(params) {
3298
3954
  }
3299
3955
 
3300
3956
  // src/participant/useParticipantUnread.ts
3301
- import { useCallback as useCallback7, useRef as useRef16, useState as useState10 } from "react";
3957
+ import { useCallback as useCallback9, useRef as useRef18, useState as useState14 } from "react";
3302
3958
  function useParticipantUnread(params) {
3303
3959
  const { messages, perspective, reportedUnreadCount, onMarkedRead } = params;
3304
- const [acknowledgedCount, setAcknowledgedCount] = useState10(0);
3305
- const messagesRef = useRef16(messages);
3960
+ const [acknowledgedCount, setAcknowledgedCount] = useState14(0);
3961
+ const messagesRef = useRef18(messages);
3306
3962
  messagesRef.current = messages;
3307
- const handleMarkedRead = useCallback7(
3963
+ const handleMarkedRead = useCallback9(
3308
3964
  (subject) => {
3309
3965
  if (perspective) setAcknowledgedCount(countOtherSideMessages(messagesRef.current, perspective));
3310
3966
  onMarkedRead?.(subject);
@@ -3327,13 +3983,13 @@ function isForSubject(event, subject) {
3327
3983
  function useParticipantThread(params) {
3328
3984
  const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [], perspective } = params;
3329
3985
  const { subjectType, subjectId } = subject;
3330
- const stableSubject = useMemo3(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
3986
+ const stableSubject = useMemo4(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
3331
3987
  const [state, dispatch] = useReducer5(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
3332
- const apiRef = useRef17(api);
3988
+ const apiRef = useRef19(api);
3333
3989
  apiRef.current = api;
3334
- const activeSubjectKey = useRef17("");
3990
+ const activeSubjectKey = useRef19("");
3335
3991
  const confirmsRead = channelCapabilityFor(channel).confirmsRead;
3336
- const refresh = useCallback8(async () => {
3992
+ const refresh = useCallback10(async () => {
3337
3993
  const requestKey = `${subjectType}:${subjectId}`;
3338
3994
  dispatch({ type: "started" });
3339
3995
  try {
@@ -3346,7 +4002,7 @@ function useParticipantThread(params) {
3346
4002
  }
3347
4003
  }, [stableSubject, subjectType, subjectId]);
3348
4004
  const oldestMessageId = state.messages[0]?.id;
3349
- const loadOlder = useCallback8(async () => {
4005
+ const loadOlder = useCallback10(async () => {
3350
4006
  if (!oldestMessageId) return;
3351
4007
  const requestKey = `${subjectType}:${subjectId}`;
3352
4008
  try {
@@ -3358,9 +4014,9 @@ function useParticipantThread(params) {
3358
4014
  if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
3359
4015
  }
3360
4016
  }, [stableSubject, subjectType, subjectId, oldestMessageId]);
3361
- const handleSentMessage = useCallback8((message) => dispatch({ type: "sendConfirmed", message }), []);
4017
+ const handleSentMessage = useCallback10((message) => dispatch({ type: "sendConfirmed", message }), []);
3362
4018
  const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
3363
- useEffect18(() => {
4019
+ useEffect20(() => {
3364
4020
  activeSubjectKey.current = `${subjectType}:${subjectId}`;
3365
4021
  dispatch({ type: "reset" });
3366
4022
  void refresh();
@@ -3368,7 +4024,7 @@ function useParticipantThread(params) {
3368
4024
  useParticipantRevalidation(api, (event) => {
3369
4025
  if (isForSubject(event, stableSubject)) void refresh();
3370
4026
  });
3371
- useEffect18(() => {
4027
+ useEffect20(() => {
3372
4028
  if (sendEntries.length === 0) return;
3373
4029
  dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
3374
4030
  }, [sendEntries, state.messages, hostPending, dispatchSend]);
@@ -3380,7 +4036,7 @@ function useParticipantThread(params) {
3380
4036
  messageCount: state.messages.length,
3381
4037
  onMarkedRead: unread.handleMarkedRead
3382
4038
  });
3383
- const localPending = useMemo3(() => toLocalPending(sendEntries), [sendEntries]);
4039
+ const localPending = useMemo4(() => toLocalPending(sendEntries), [sendEntries]);
3384
4040
  return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
3385
4041
  }
3386
4042
 
@@ -3389,7 +4045,7 @@ function useParticipantConversationView(params) {
3389
4045
  const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
3390
4046
  const { subjectType, subjectId } = subject;
3391
4047
  const sendEntries = selectSendEntries(sendStates, subject);
3392
- const dispatchSend = useCallback9(
4048
+ const dispatchSend = useCallback11(
3393
4049
  (action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
3394
4050
  [dispatchSendStates, subjectType, subjectId]
3395
4051
  );
@@ -3404,7 +4060,7 @@ function useParticipantConversationView(params) {
3404
4060
  hostPending: pendingMessages,
3405
4061
  perspective: params.perspective
3406
4062
  });
3407
- const items = useMemo4(
4063
+ const items = useMemo5(
3408
4064
  () => mergeParticipantMessages({
3409
4065
  serverMessages: thread.messages,
3410
4066
  hostPending: pendingMessages ?? [],
@@ -3417,9 +4073,9 @@ function useParticipantConversationView(params) {
3417
4073
  }
3418
4074
 
3419
4075
  // src/participant/useParticipantDraft.ts
3420
- import { useState as useState11 } from "react";
4076
+ import { useState as useState15 } from "react";
3421
4077
  function useParticipantDraft(draftsRef, subject) {
3422
- const [draft, setDraftState] = useState11(() => getDraft(draftsRef.current, subject));
4078
+ const [draft, setDraftState] = useState15(() => getDraft(draftsRef.current, subject));
3423
4079
  function replaceDrafts(next) {
3424
4080
  draftsRef.current = next;
3425
4081
  setDraftState(getDraft(next, subject));
@@ -3439,12 +4095,12 @@ function useParticipantDraft(draftsRef, subject) {
3439
4095
  }
3440
4096
 
3441
4097
  // src/participant/useParticipantSendAudio.ts
3442
- import { useCallback as useCallback10, useRef as useRef18 } from "react";
4098
+ import { useCallback as useCallback12, useRef as useRef20 } from "react";
3443
4099
  function useParticipantSendAudio(send) {
3444
- const sendRef = useRef18(send);
4100
+ const sendRef = useRef20(send);
3445
4101
  sendRef.current = send;
3446
- const inFlightRef = useRef18(/* @__PURE__ */ new Set());
3447
- return useCallback10((file) => {
4102
+ const inFlightRef = useRef20(/* @__PURE__ */ new Set());
4103
+ return useCallback12((file) => {
3448
4104
  const inFlight = inFlightRef.current;
3449
4105
  if (inFlight.has(file)) return;
3450
4106
  inFlight.add(file);
@@ -3460,7 +4116,7 @@ function useParticipantSendAudio(send) {
3460
4116
  }
3461
4117
 
3462
4118
  // src/participant/useParticipantThreadScroll.ts
3463
- import { useEffect as useEffect19, useRef as useRef20 } from "react";
4119
+ import { useEffect as useEffect21, useRef as useRef22 } from "react";
3464
4120
 
3465
4121
  // src/participant/participantNewMessages.ts
3466
4122
  function collectServerMessageIds(items) {
@@ -3485,9 +4141,9 @@ function countNewIncomingMessages(previousIds, currentItems, perspective) {
3485
4141
  }
3486
4142
 
3487
4143
  // src/participant/useScrollAwayState.ts
3488
- import { useCallback as useCallback11 } from "react";
4144
+ import { useCallback as useCallback13 } from "react";
3489
4145
  function useScrollAwayState(ref, isNearBottom) {
3490
- const scrollToLatest = useCallback11(() => {
4146
+ const scrollToLatest = useCallback13(() => {
3491
4147
  const element = ref.current;
3492
4148
  if (!element) return;
3493
4149
  element.scrollTo(
@@ -3498,7 +4154,7 @@ function useScrollAwayState(ref, isNearBottom) {
3498
4154
  }
3499
4155
 
3500
4156
  // src/participant/useStickToBottom.ts
3501
- import { useCallback as useCallback12, useLayoutEffect as useLayoutEffect3, useRef as useRef19, useState as useState12 } from "react";
4157
+ import { useCallback as useCallback14, useLayoutEffect as useLayoutEffect4, useRef as useRef21, useState as useState16 } from "react";
3502
4158
 
3503
4159
  // src/participant/participantScroll.ts
3504
4160
  var NEAR_BOTTOM_THRESHOLD_PX = 80;
@@ -3520,17 +4176,17 @@ function resolveScrollAction(params) {
3520
4176
 
3521
4177
  // src/participant/useStickToBottom.ts
3522
4178
  function useStickToBottom({ firstKey, lastKey }) {
3523
- const ref = useRef19(null);
3524
- const snapshot = useRef19({
4179
+ const ref = useRef21(null);
4180
+ const snapshot = useRef21({
3525
4181
  hasPositioned: false,
3526
4182
  firstKey: void 0,
3527
4183
  lastKey: void 0,
3528
4184
  scrollTop: 0,
3529
4185
  scrollHeight: 0
3530
4186
  });
3531
- const [isNearBottom, setIsNearBottom] = useState12(true);
3532
- const isNearBottomRef = useRef19(true);
3533
- const onScroll = useCallback12((event) => {
4187
+ const [isNearBottom, setIsNearBottom] = useState16(true);
4188
+ const isNearBottomRef = useRef21(true);
4189
+ const onScroll = useCallback14((event) => {
3534
4190
  const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
3535
4191
  const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
3536
4192
  snapshot.current.scrollTop = scrollTop;
@@ -3538,7 +4194,7 @@ function useStickToBottom({ firstKey, lastKey }) {
3538
4194
  isNearBottomRef.current = isNear;
3539
4195
  setIsNearBottom(isNear);
3540
4196
  }, []);
3541
- useLayoutEffect3(() => {
4197
+ useLayoutEffect4(() => {
3542
4198
  const element = ref.current;
3543
4199
  const previous = snapshot.current;
3544
4200
  if (!element) return;
@@ -3585,8 +4241,8 @@ function useParticipantThreadScroll(items, perspective) {
3585
4241
  });
3586
4242
  const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
3587
4243
  const scroll = { ...base, isAwayFromBottom, scrollToLatest };
3588
- const seenIds = useRef20(/* @__PURE__ */ new Set());
3589
- useEffect19(() => {
4244
+ const seenIds = useRef22(/* @__PURE__ */ new Set());
4245
+ useEffect21(() => {
3590
4246
  if (isNearBottom) seenIds.current = collectServerMessageIds(items);
3591
4247
  }, [isNearBottom, items]);
3592
4248
  return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items, perspective) };
@@ -3613,7 +4269,7 @@ function useParticipantThreadController(params) {
3613
4269
  const { api, subject, conversation, draftsRef } = params;
3614
4270
  const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, subject);
3615
4271
  const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView(params);
3616
- const resolveAttachmentUrl = useMemo5(() => bindAttachmentUrlResolver(api), [api]);
4272
+ const resolveAttachmentUrl = useMemo6(() => bindAttachmentUrlResolver(api), [api]);
3617
4273
  const { scroll, newMessagesCount } = useParticipantThreadScroll(items, params.perspective);
3618
4274
  const handleSendAudio = useParticipantSendAudio(thread.send);
3619
4275
  const handleEdit = buildEditHandler({ sendEntries, dispatchSend, restoreFromEntry });
@@ -3644,12 +4300,12 @@ function useParticipantThreadController(params) {
3644
4300
  }
3645
4301
 
3646
4302
  // src/participant/ParticipantThreadScreen.tsx
3647
- import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
4303
+ import { jsx as jsx40, jsxs as jsxs31 } from "react/jsx-runtime";
3648
4304
  function useOpenedConversation(api, subject, isNeeded) {
3649
- const [opened, setOpened] = useState13({ status: "idle" });
4305
+ const [opened, setOpened] = useState17({ status: "idle" });
3650
4306
  const { subjectType, subjectId } = subject;
3651
4307
  const canOpen = api.openConversation !== void 0;
3652
- useEffect20(() => {
4308
+ useEffect22(() => {
3653
4309
  if (!isNeeded || !canOpen) return;
3654
4310
  let isCurrent = true;
3655
4311
  setOpened({ status: "loading" });
@@ -3667,9 +4323,9 @@ function NotFound({
3667
4323
  labels,
3668
4324
  onBack
3669
4325
  }) {
3670
- return /* @__PURE__ */ jsxs26("div", { className: "cv-p cv-p-thread", children: [
3671
- /* @__PURE__ */ jsx34("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx34(ParticipantBackButton, { label: labels.back, onBack }) : null }),
3672
- /* @__PURE__ */ jsx34("p", { className: "cv-p-empty", children: labels.notFound })
4326
+ return /* @__PURE__ */ jsxs31("div", { className: "cv-p cv-p-thread", children: [
4327
+ /* @__PURE__ */ jsx40("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx40(ParticipantBackButton, { label: labels.back, onBack }) : null }),
4328
+ /* @__PURE__ */ jsx40("p", { className: "cv-p-empty", children: labels.notFound })
3673
4329
  ] });
3674
4330
  }
3675
4331
  function ParticipantThreadScreen(props) {
@@ -3679,12 +4335,12 @@ function ParticipantThreadScreen(props) {
3679
4335
  );
3680
4336
  const opened = useOpenedConversation(api, selected, listed === void 0);
3681
4337
  const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
3682
- if (conversation) return /* @__PURE__ */ jsx34(LoadedThread, { ...props, conversation });
4338
+ if (conversation) return /* @__PURE__ */ jsx40(LoadedThread, { ...props, conversation });
3683
4339
  const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
3684
4340
  const isListLoading = inbox.status === "idle" || inbox.status === "loading";
3685
4341
  if (isResolving || api.openConversation === void 0 && isListLoading)
3686
- return /* @__PURE__ */ jsx34("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
3687
- return /* @__PURE__ */ jsx34(NotFound, { labels, onBack: props.onBack });
4342
+ return /* @__PURE__ */ jsx40("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
4343
+ return /* @__PURE__ */ jsx40(NotFound, { labels, onBack: props.onBack });
3688
4344
  }
3689
4345
  function LoadedThread(props) {
3690
4346
  const core = useParticipantThreadController({
@@ -3692,7 +4348,7 @@ function LoadedThread(props) {
3692
4348
  subject: props.selected,
3693
4349
  onMarkedRead: props.inbox.markSubjectRead
3694
4350
  });
3695
- return /* @__PURE__ */ jsx34(
4351
+ return /* @__PURE__ */ jsx40(
3696
4352
  ParticipantThread,
3697
4353
  {
3698
4354
  ...core,
@@ -3705,6 +4361,7 @@ function LoadedThread(props) {
3705
4361
  avatars: props.avatars,
3706
4362
  renderAuthorAvatar: props.renderAuthorAvatar,
3707
4363
  renderMessageActions: props.renderMessageActions,
4364
+ messageMenu: props.messageMenu,
3708
4365
  tail: props.tail,
3709
4366
  audioRecording: props.audioRecording,
3710
4367
  renderComposerLock: props.renderComposerLock
@@ -3833,16 +4490,16 @@ function buildParticipantThemeStyle(theme) {
3833
4490
  }
3834
4491
 
3835
4492
  // src/participant/ParticipantConversationsScreen.tsx
3836
- import { jsx as jsx35 } from "react/jsx-runtime";
4493
+ import { jsx as jsx41 } from "react/jsx-runtime";
3837
4494
  var ALL_FILTER3 = "all";
3838
4495
  function ParticipantConversationsScreen(props) {
3839
4496
  const { selected, inbox, subjectGroups, classNames } = props;
3840
- const [filter, setFilter] = useState14(ALL_FILTER3);
3841
- const [searchQuery, setSearchQuery] = useState14("");
3842
- const draftsRef = useRef21(/* @__PURE__ */ new Map());
4497
+ const [filter, setFilter] = useState18(ALL_FILTER3);
4498
+ const [searchQuery, setSearchQuery] = useState18("");
4499
+ const draftsRef = useRef23(/* @__PURE__ */ new Map());
3843
4500
  const [sendStates, dispatchSendStates] = useReducer6(participantSendStatesReducer, /* @__PURE__ */ new Map());
3844
- const labels = useMemo6(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
3845
- const view = useMemo6(
4501
+ const labels = useMemo7(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
4502
+ const view = useMemo7(
3846
4503
  () => groupParticipantConversations({
3847
4504
  conversations: inbox.conversations,
3848
4505
  subjectGroups,
@@ -3853,7 +4510,7 @@ function ParticipantConversationsScreen(props) {
3853
4510
  );
3854
4511
  const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
3855
4512
  const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
3856
- return /* @__PURE__ */ jsx35("div", { className: rootClassName, style: buildParticipantThemeStyle(props.theme), children: selected === void 0 ? /* @__PURE__ */ jsx35("div", { className: ["cv-p-root__screen", classNames?.inbox].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx35(
4513
+ return /* @__PURE__ */ jsx41("div", { className: rootClassName, style: buildParticipantThemeStyle(props.theme), children: selected === void 0 ? /* @__PURE__ */ jsx41("div", { className: ["cv-p-root__screen", classNames?.inbox].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx41(
3857
4514
  ParticipantInbox,
3858
4515
  {
3859
4516
  view,
@@ -3870,7 +4527,7 @@ function ParticipantConversationsScreen(props) {
3870
4527
  renderSubjectIcon: props.renderSubjectIcon,
3871
4528
  search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
3872
4529
  }
3873
- ) }) : /* @__PURE__ */ jsx35("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx35(
4530
+ ) }) : /* @__PURE__ */ jsx41("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx41(
3874
4531
  ParticipantThreadScreen,
3875
4532
  {
3876
4533
  ...props,
@@ -3885,7 +4542,7 @@ function ParticipantConversationsScreen(props) {
3885
4542
  }
3886
4543
 
3887
4544
  // src/participant/useParticipantInbox.ts
3888
- import { useCallback as useCallback13, useEffect as useEffect21, useReducer as useReducer7 } from "react";
4545
+ import { useCallback as useCallback15, useEffect as useEffect23, useReducer as useReducer7 } from "react";
3889
4546
 
3890
4547
  // src/participant/participantInboxState.ts
3891
4548
  var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
@@ -3936,7 +4593,7 @@ function describeError2(error) {
3936
4593
  }
3937
4594
  function useParticipantInbox(api) {
3938
4595
  const [state, dispatch] = useReducer7(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
3939
- const refresh = useCallback13(async () => {
4596
+ const refresh = useCallback15(async () => {
3940
4597
  dispatch({ type: "started" });
3941
4598
  try {
3942
4599
  const page = await api.listConversations();
@@ -3946,7 +4603,7 @@ function useParticipantInbox(api) {
3946
4603
  }
3947
4604
  }, [api]);
3948
4605
  const { nextCursor } = state;
3949
- const loadMore = useCallback13(async () => {
4606
+ const loadMore = useCallback15(async () => {
3950
4607
  if (!nextCursor) return;
3951
4608
  try {
3952
4609
  const page = await api.listConversations({ cursor: nextCursor });
@@ -3955,10 +4612,10 @@ function useParticipantInbox(api) {
3955
4612
  dispatch({ type: "failed", error: describeError2(error) });
3956
4613
  }
3957
4614
  }, [api, nextCursor]);
3958
- const markSubjectRead = useCallback13((subject) => {
4615
+ const markSubjectRead = useCallback15((subject) => {
3959
4616
  dispatch({ type: "markedRead", subject });
3960
4617
  }, []);
3961
- useEffect21(() => {
4618
+ useEffect23(() => {
3962
4619
  void refresh();
3963
4620
  }, [refresh]);
3964
4621
  useParticipantRevalidation(api, () => {
@@ -3968,11 +4625,11 @@ function useParticipantInbox(api) {
3968
4625
  }
3969
4626
 
3970
4627
  // src/participant/ParticipantConversations.tsx
3971
- import { jsx as jsx36 } from "react/jsx-runtime";
4628
+ import { jsx as jsx42 } from "react/jsx-runtime";
3972
4629
  function ParticipantConversations(props) {
3973
4630
  const inbox = useParticipantInbox(props.api);
3974
4631
  const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
3975
- return /* @__PURE__ */ jsx36(
4632
+ return /* @__PURE__ */ jsx42(
3976
4633
  ParticipantConversationsScreen,
3977
4634
  {
3978
4635
  ...props,
@@ -3982,7 +4639,7 @@ function ParticipantConversations(props) {
3982
4639
  }
3983
4640
 
3984
4641
  // src/participant/ConversationThread.tsx
3985
- import { useCallback as useCallback14, useMemo as useMemo7, useReducer as useReducer8, useRef as useRef22 } from "react";
4642
+ import { useCallback as useCallback16, useMemo as useMemo8, useReducer as useReducer8, useRef as useRef24 } from "react";
3986
4643
 
3987
4644
  // src/participant/conversationThreadApi.ts
3988
4645
  function toParticipantApi(api) {
@@ -4022,20 +4679,20 @@ function buildThreadSummary({
4022
4679
  }
4023
4680
 
4024
4681
  // src/participant/ConversationThread.tsx
4025
- import { jsx as jsx37 } from "react/jsx-runtime";
4682
+ import { jsx as jsx43 } from "react/jsx-runtime";
4026
4683
  function useConversationThreadCore(props) {
4027
4684
  const { perspective = "participant" } = props;
4028
4685
  const { subjectType, subjectId } = props.subject;
4029
- const subject = useMemo7(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
4030
- const onMarkedReadRef = useRef22(props.onMarkedRead);
4686
+ const subject = useMemo8(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
4687
+ const onMarkedReadRef = useRef24(props.onMarkedRead);
4031
4688
  onMarkedReadRef.current = props.onMarkedRead;
4032
- const handleMarkedRead = useCallback14((marked) => onMarkedReadRef.current?.(marked), []);
4033
- const api = useMemo7(() => toParticipantApi(props.api), [props.api]);
4034
- const draftsRef = useRef22(/* @__PURE__ */ new Map());
4689
+ const handleMarkedRead = useCallback16((marked) => onMarkedReadRef.current?.(marked), []);
4690
+ const api = useMemo8(() => toParticipantApi(props.api), [props.api]);
4691
+ const draftsRef = useRef24(/* @__PURE__ */ new Map());
4035
4692
  const [sendStates, dispatchSendStates] = useReducer8(participantSendStatesReducer, /* @__PURE__ */ new Map());
4036
- const labels = useMemo7(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
4693
+ const labels = useMemo8(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
4037
4694
  const { title, protocol, channels, status } = props;
4038
- const conversation = useMemo7(
4695
+ const conversation = useMemo8(
4039
4696
  () => buildThreadSummary({ subject, title, protocol, channels, status }),
4040
4697
  [subject, title, protocol, channels, status]
4041
4698
  );
@@ -4057,12 +4714,12 @@ function useConversationThreadCore(props) {
4057
4714
  }
4058
4715
  function ConversationThreadBody(props) {
4059
4716
  const core = useConversationThreadCore(props);
4060
- return /* @__PURE__ */ jsx37(
4717
+ return /* @__PURE__ */ jsx43(
4061
4718
  "div",
4062
4719
  {
4063
4720
  className: ["cv-p-root", props.className].filter(Boolean).join(" "),
4064
4721
  style: buildParticipantThemeStyle(props.theme),
4065
- children: /* @__PURE__ */ jsx37("div", { className: "cv-p-root__screen", children: /* @__PURE__ */ jsx37(
4722
+ children: /* @__PURE__ */ jsx43("div", { className: "cv-p-root__screen", children: /* @__PURE__ */ jsx43(
4066
4723
  ParticipantThread,
4067
4724
  {
4068
4725
  ...core,
@@ -4073,6 +4730,7 @@ function ConversationThreadBody(props) {
4073
4730
  avatars: props.avatars,
4074
4731
  renderAuthorAvatar: props.renderAuthorAvatar,
4075
4732
  renderMessageActions: props.renderMessageActions,
4733
+ messageMenu: props.messageMenu,
4076
4734
  tail: props.tail,
4077
4735
  counterpartLabel: props.counterpartLabel,
4078
4736
  headerActions: props.headerActions,
@@ -4084,7 +4742,7 @@ function ConversationThreadBody(props) {
4084
4742
  );
4085
4743
  }
4086
4744
  function ConversationThread(props) {
4087
- return /* @__PURE__ */ jsx37(ConversationThreadBody, { ...props }, draftKey(props.subject));
4745
+ return /* @__PURE__ */ jsx43(ConversationThreadBody, { ...props }, draftKey(props.subject));
4088
4746
  }
4089
4747
  export {
4090
4748
  ConversationThread,
@@ -4092,5 +4750,6 @@ export {
4092
4750
  DEFAULT_PARTICIPANT_AUDIO_LABELS,
4093
4751
  DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS,
4094
4752
  ParticipantConversations,
4753
+ formatMessagesForCopy,
4095
4754
  groupParticipantConversations
4096
4755
  };