@adatechnology/conversations-ui 0.12.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 (37) 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 +87 -2
  5. package/dist/participant/index.js +1073 -398
  6. package/dist/styles.css +218 -0
  7. package/package.json +1 -1
  8. package/src/conversationTranscript.ts +6 -2
  9. package/src/participant/ConversationThread.tsx +8 -1
  10. package/src/participant/ParticipantAuthorAvatar.tsx +18 -0
  11. package/src/participant/ParticipantBubbleBody.tsx +33 -3
  12. package/src/participant/ParticipantConversationMenu.tsx +89 -0
  13. package/src/participant/ParticipantConversations.tsx +6 -1
  14. package/src/participant/ParticipantMenuBubble.tsx +106 -0
  15. package/src/participant/ParticipantMessageBubble.test.tsx +51 -0
  16. package/src/participant/ParticipantMessageBubble.tsx +39 -7
  17. package/src/participant/ParticipantMessageInfo.tsx +70 -0
  18. package/src/participant/ParticipantMessageMenu.tsx +168 -0
  19. package/src/participant/ParticipantMessageMenuContext.tsx +109 -0
  20. package/src/participant/ParticipantSelectionBar.tsx +64 -0
  21. package/src/participant/ParticipantThread.tsx +30 -3
  22. package/src/participant/ParticipantThreadScreen.tsx +2 -0
  23. package/src/participant/ParticipantTimeline.tsx +7 -1
  24. package/src/participant/index.ts +11 -1
  25. package/src/participant/participantConversationMenuDom.test.tsx +166 -0
  26. package/src/participant/participantLabels.ts +40 -0
  27. package/src/participant/participantMessageInfoModel.test.ts +68 -0
  28. package/src/participant/participantMessageInfoModel.ts +80 -0
  29. package/src/participant/participantMessageMenuDom.test.tsx +111 -0
  30. package/src/participant/participantMessageMenuModel.test.ts +43 -0
  31. package/src/participant/participantMessageMenuModel.ts +91 -0
  32. package/src/participant/participantMessageMoment.ts +7 -0
  33. package/src/participant/participantTranscriptionText.test.ts +48 -0
  34. package/src/participant/participantTranscriptionText.ts +28 -0
  35. package/src/participant/useOpenUpwards.ts +22 -0
  36. package/src/participant/useParticipantFailedMenu.ts +3 -0
  37. package/src/styles.css +41 -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,49 +2749,71 @@ 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 });
2781
+ }
2782
+ function hostActions(content, render) {
2783
+ if (!render || content.messageId === void 0) return null;
2784
+ return render({
2785
+ id: content.messageId,
2786
+ text: content.text ?? null,
2787
+ authorName: content.authorName ?? null,
2788
+ isMine: content.isMine,
2789
+ attachments: content.attachments,
2790
+ createdAt: content.createdAt
2791
+ });
2581
2792
  }
2582
2793
  function OriginalText({ content, labels }) {
2583
2794
  const original = content.originalText?.trim();
2584
2795
  if (!original || original === content.text?.trim()) return null;
2585
- return /* @__PURE__ */ jsxs21("details", { className: "cv-p-bubble__original", children: [
2586
- /* @__PURE__ */ jsx27("summary", { className: "cv-p-bubble__original-toggle", children: labels.showOriginalText ?? DEFAULT_SHOW_ORIGINAL_TEXT_LABEL }),
2587
- /* @__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 })
2588
2799
  ] });
2589
2800
  }
2590
- function BubbleBody({ content, labels, ownStatus, textId, resolveAttachmentUrl, onRetry, onDiscard, onEdit }) {
2591
- return /* @__PURE__ */ jsxs21(Fragment6, { children: [
2592
- /* @__PURE__ */ jsx27(BubbleHeading, { content, labels }),
2593
- content.text ? /* @__PURE__ */ jsx27(ParticipantMessageText, { text: content.text, labels, id: textId }) : null,
2594
- content.attachments.map((attachment) => /* @__PURE__ */ jsx27(
2801
+ function BubbleBody({
2802
+ content,
2803
+ labels,
2804
+ ownStatus,
2805
+ textId,
2806
+ resolveAttachmentUrl,
2807
+ onRetry,
2808
+ onDiscard,
2809
+ onEdit,
2810
+ renderMessageActions
2811
+ }) {
2812
+ const actions = hostActions(content, renderMessageActions);
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(
2595
2817
  ParticipantAttachmentItem,
2596
2818
  {
2597
2819
  attachment,
@@ -2600,16 +2822,319 @@ function BubbleBody({ content, labels, ownStatus, textId, resolveAttachmentUrl,
2600
2822
  },
2601
2823
  attachment.id
2602
2824
  )),
2603
- content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx27("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
2604
- /* @__PURE__ */ jsxs21("span", { className: "cv-p-bubble__meta", children: [
2605
- /* @__PURE__ */ jsx27("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
2606
- ownStatus ? /* @__PURE__ */ jsx27(OwnStatus, { status: ownStatus, labels, onRetry }) : null,
2607
- ownStatus === "failed" ? /* @__PURE__ */ jsx27(ParticipantFailedMenu, { labels, onEdit, onDiscard }) : 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
2608
2831
  ] }),
2609
- /* @__PURE__ */ jsx27(OriginalText, { content, labels })
2832
+ /* @__PURE__ */ jsx28(OriginalText, { content, labels })
2833
+ ] });
2834
+ }
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
2610
3132
  ] });
2611
3133
  }
2612
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
+
2613
3138
  // src/participant/participantPerspective.ts
2614
3139
  var DEFAULT_PARTICIPANT_PERSPECTIVE = "participant";
2615
3140
  var OWN_DIRECTION = {
@@ -2671,8 +3196,74 @@ function resolveOwnMessageStatus(params) {
2671
3196
  return serverStatus;
2672
3197
  }
2673
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
+
2674
3265
  // src/participant/ParticipantMessageBubble.tsx
2675
- import { jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
3266
+ import { jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
2676
3267
  function describeItem(item, perspective) {
2677
3268
  if (item.kind === "server") {
2678
3269
  const { message } = item;
@@ -2684,7 +3275,8 @@ function describeItem(item, perspective) {
2684
3275
  createdAt: message.createdAt,
2685
3276
  attachments: message.attachments,
2686
3277
  pendingFilenames: [],
2687
- serverStatus: message.status
3278
+ serverStatus: message.status,
3279
+ messageId: message.id
2688
3280
  };
2689
3281
  }
2690
3282
  return {
@@ -2703,23 +3295,32 @@ function bubbleClassName(isMine, hasTail, hasFailed) {
2703
3295
  if (!hasTail) classes.push("cv-p-bubble--no-tail");
2704
3296
  return classes.join(" ");
2705
3297
  }
2706
- function ParticipantMessageBubble(props) {
2707
- const { item, labels, confirmsRead, onRetry, avatar, tail = true, perspective } = props;
2708
- const textId = useId3();
2709
- const content = describeItem(item, perspective);
3298
+ function renderParticipantBubble({ props, content, textId, frame }) {
3299
+ const { confirmsRead, onRetry, avatar, tail = true } = props;
2710
3300
  const ownStatus = content.isMine ? resolveOwnMessageStatus({ displayState: content.displayState, serverStatus: content.serverStatus, confirmsRead }) : void 0;
2711
3301
  const isFailed = ownStatus === "failed";
2712
3302
  const canRetry = isFailed && onRetry !== void 0;
2713
- const bubble = /* @__PURE__ */ jsxs22("div", { className: bubbleClassName(content.isMine, tail, isFailed), children: [
2714
- canRetry ? /* @__PURE__ */ jsx28(RetryButton, { labels, onRetry, describedBy: textId }) : null,
2715
- /* @__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
2716
3308
  ] });
2717
3309
  if (avatar === void 0 || content.isMine) return bubble;
2718
- return /* @__PURE__ */ jsxs22("div", { className: "cv-p-bubble-row", children: [
2719
- /* @__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 }),
2720
3312
  bubble
2721
3313
  ] });
2722
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
+ }
2723
3324
 
2724
3325
  // src/participant/participantBubbleActions.ts
2725
3326
  function bindPendingActions(actions, clientMessageId) {
@@ -2738,7 +3339,7 @@ function resolveParticipantBubbleActions(item, handlers) {
2738
3339
  }
2739
3340
 
2740
3341
  // src/participant/ParticipantTimeline.tsx
2741
- import { jsx as jsx29 } from "react/jsx-runtime";
3342
+ import { jsx as jsx34 } from "react/jsx-runtime";
2742
3343
  function createdAtOf2(item) {
2743
3344
  return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
2744
3345
  }
@@ -2755,7 +3356,7 @@ function resolveActions(item, pendingActions) {
2755
3356
  }
2756
3357
  function resolveAvatarSlot({ isEnabled, isMine, showAvatar, author, render }) {
2757
3358
  if (!isEnabled || isMine) return void 0;
2758
- return showAvatar ? /* @__PURE__ */ jsx29(ParticipantAuthorAvatar, { name: author, render }) : null;
3359
+ return showAvatar ? /* @__PURE__ */ jsx34(ParticipantAuthorAvatar, { name: author, render }) : null;
2759
3360
  }
2760
3361
  function ParticipantTimeline(props) {
2761
3362
  const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
@@ -2767,7 +3368,7 @@ function ParticipantTimeline(props) {
2767
3368
  for (const item of items) {
2768
3369
  const day = new Date(createdAtOf2(item));
2769
3370
  if (previousDay === void 0 || !isSameDay(previousDay, day)) {
2770
- nodes.push(/* @__PURE__ */ jsx29(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
3371
+ nodes.push(/* @__PURE__ */ jsx34(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
2771
3372
  previousAuthor = void 0;
2772
3373
  }
2773
3374
  previousDay = day;
@@ -2782,7 +3383,7 @@ function ParticipantTimeline(props) {
2782
3383
  render: props.renderAuthorAvatar
2783
3384
  });
2784
3385
  nodes.push(
2785
- /* @__PURE__ */ jsx29(
3386
+ /* @__PURE__ */ jsx34(
2786
3387
  ParticipantMessageBubble,
2787
3388
  {
2788
3389
  item,
@@ -2792,25 +3393,77 @@ function ParticipantTimeline(props) {
2792
3393
  tail: props.tail,
2793
3394
  perspective: props.perspective,
2794
3395
  avatar,
3396
+ renderMessageActions: props.renderMessageActions,
2795
3397
  ...resolveActions(item, pendingActions)
2796
3398
  },
2797
3399
  keyOf(item)
2798
3400
  )
2799
3401
  );
2800
3402
  }
2801
- 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
+ ] });
2802
3455
  }
2803
3456
 
2804
3457
  // src/participant/ParticipantConversationAvatar.tsx
2805
- import { jsx as jsx30 } from "react/jsx-runtime";
3458
+ import { jsx as jsx36 } from "react/jsx-runtime";
2806
3459
  function ParticipantConversationAvatar({ icon }) {
2807
- 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 });
2808
3461
  }
2809
3462
 
2810
3463
  // src/participant/ParticipantProtocolBadge.tsx
2811
3464
  import { Check as Check2, Copy as Copy2 } from "lucide-react";
2812
- import { useEffect as useEffect14, useReducer as useReducer4 } from "react";
2813
- 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";
2814
3467
  var COPIED_ANNOUNCEMENT_MS = 3e3;
2815
3468
  function ParticipantProtocolBadgeView({
2816
3469
  protocol,
@@ -2819,16 +3472,16 @@ function ParticipantProtocolBadgeView({
2819
3472
  onCopy
2820
3473
  }) {
2821
3474
  const { isCopied, copyCount } = copyState;
2822
- return /* @__PURE__ */ jsxs23(Fragment7, { children: [
2823
- /* @__PURE__ */ jsxs23("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
2824
- /* @__PURE__ */ jsxs23("span", { className: "cv-p-protocol__code", children: [
2825
- /* @__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: [
2826
3479
  labels.protocolPrefix,
2827
3480
  " "
2828
3481
  ] }),
2829
3482
  protocol
2830
3483
  ] }),
2831
- /* @__PURE__ */ jsxs23(
3484
+ /* @__PURE__ */ jsxs28(
2832
3485
  "button",
2833
3486
  {
2834
3487
  type: "button",
@@ -2837,19 +3490,19 @@ function ParticipantProtocolBadgeView({
2837
3490
  "data-cv-tooltip": labels.copyProtocol,
2838
3491
  onClick: onCopy,
2839
3492
  children: [
2840
- isCopied ? /* @__PURE__ */ jsx31(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx31(Copy2, { size: 18, "aria-hidden": true }),
2841
- 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
2842
3495
  ]
2843
3496
  }
2844
3497
  )
2845
3498
  ] }),
2846
- /* @__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 })
2847
3500
  ] });
2848
3501
  }
2849
3502
  function ParticipantProtocolBadge({ protocol, labels }) {
2850
3503
  const [copyState, dispatch] = useReducer4(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
2851
3504
  const { isCopied, copyCount } = copyState;
2852
- useEffect14(() => {
3505
+ useEffect16(() => {
2853
3506
  if (!isCopied) return void 0;
2854
3507
  const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_ANNOUNCEMENT_MS);
2855
3508
  return () => clearTimeout(timer);
@@ -2858,7 +3511,7 @@ function ParticipantProtocolBadge({ protocol, labels }) {
2858
3511
  const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
2859
3512
  if (await copyProtocolToClipboard(protocol, clipboard)) dispatch({ type: "copied" });
2860
3513
  }
2861
- return /* @__PURE__ */ jsx31(
3514
+ return /* @__PURE__ */ jsx37(
2862
3515
  ParticipantProtocolBadgeView,
2863
3516
  {
2864
3517
  protocol,
@@ -2870,19 +3523,19 @@ function ParticipantProtocolBadge({ protocol, labels }) {
2870
3523
  }
2871
3524
 
2872
3525
  // src/participant/ParticipantThreadHeader.tsx
2873
- import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
3526
+ import { jsx as jsx38, jsxs as jsxs29 } from "react/jsx-runtime";
2874
3527
  function Title({ conversation, labels, onOpenSubject }) {
2875
3528
  const { subjectType, subjectId, subjectLabel } = conversation;
2876
- 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(
2877
3530
  "button",
2878
3531
  {
2879
3532
  type: "button",
2880
3533
  className: "cv-p-thread__title-button",
2881
3534
  "aria-label": `${labels.openSubject}: ${subjectLabel}`,
2882
3535
  onClick: () => onOpenSubject({ subjectType, subjectId }),
2883
- 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 })
2884
3537
  }
2885
- ) : /* @__PURE__ */ jsx32("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
3538
+ ) : /* @__PURE__ */ jsx38("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
2886
3539
  }
2887
3540
  function ParticipantThreadHeader({
2888
3541
  conversation,
@@ -2897,34 +3550,36 @@ function ParticipantThreadHeader({
2897
3550
  const group = subjectGroups?.find((candidate) => candidate.subjectType === conversation.subjectType);
2898
3551
  const icon = resolveConversationIcon({ conversation, group, renderSubjectIcon });
2899
3552
  const hasChannels = resolveParticipantChannels(conversation.channels).length > 0;
2900
- return /* @__PURE__ */ jsxs24("header", { className: "cv-p-thread__header", children: [
2901
- onBack ? /* @__PURE__ */ jsx32(ParticipantBackButton, { label: labels.back, onBack }) : null,
2902
- icon !== void 0 ? /* @__PURE__ */ jsx32(ParticipantConversationAvatar, { icon }) : null,
2903
- /* @__PURE__ */ jsxs24("div", { className: "cv-p-thread__heading", children: [
2904
- icon === void 0 && group ? /* @__PURE__ */ jsx32("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
2905
- /* @__PURE__ */ jsx32(Title, { conversation, labels, onOpenSubject }),
2906
- counterpartLabel ? /* @__PURE__ */ jsx32("p", { className: "cv-p-thread__counterpart", children: counterpartLabel }) : null,
2907
- conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs24("div", { className: "cv-p-thread__meta", children: [
2908
- conversation.protocol ? /* @__PURE__ */ jsx32(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
2909
- /* @__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 })
2910
3563
  ] }) : null
2911
3564
  ] }),
2912
- 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
2913
3566
  ] });
2914
3567
  }
2915
3568
 
2916
3569
  // src/participant/ParticipantThread.tsx
2917
- 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";
2918
3571
  function Footer({ props }) {
2919
3572
  const { conversation, labels, draft } = props;
3573
+ const menu = useParticipantMessageMenu();
3574
+ if (menu?.isSelecting) return /* @__PURE__ */ jsx39(ParticipantSelectionBar, { state: menu });
2920
3575
  if (conversation.status === "closed") {
2921
- 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 });
2922
3577
  }
2923
3578
  const lock = props.renderComposerLock?.(conversation);
2924
3579
  if (lock !== void 0 && lock !== null) {
2925
- 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 });
2926
3581
  }
2927
- return /* @__PURE__ */ jsx33(
3582
+ return /* @__PURE__ */ jsx39(
2928
3583
  ParticipantComposer,
2929
3584
  {
2930
3585
  value: draft.value,
@@ -2944,7 +3599,12 @@ function Footer({ props }) {
2944
3599
  );
2945
3600
  }
2946
3601
  function Header({ props }) {
2947
- 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(
2948
3608
  ParticipantThreadHeader,
2949
3609
  {
2950
3610
  conversation: props.conversation,
@@ -2954,7 +3614,7 @@ function Header({ props }) {
2954
3614
  onBack: props.onBack,
2955
3615
  onOpenSubject: props.onOpenSubject,
2956
3616
  counterpartLabel: props.counterpartLabel,
2957
- actions: props.headerActions
3617
+ actions
2958
3618
  }
2959
3619
  );
2960
3620
  }
@@ -2962,17 +3622,17 @@ function ThreadBody(props) {
2962
3622
  const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
2963
3623
  const subjectCard = renderSubjectCard?.(conversation);
2964
3624
  const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
2965
- const rootRef = useRef13(null);
3625
+ const rootRef = useRef15(null);
2966
3626
  useSuppressTouchTooltips(rootRef);
2967
- return /* @__PURE__ */ jsxs25("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, ref: rootRef, children: [
2968
- /* @__PURE__ */ jsx33(Header, { props }),
2969
- subjectCard ? /* @__PURE__ */ jsx33("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
2970
- /* @__PURE__ */ jsxs25("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
2971
- loadView.isLoading ? /* @__PURE__ */ jsx33(ParticipantLoading, { labels }) : null,
2972
- loadView.hasError ? /* @__PURE__ */ jsx33(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
2973
- hasMore && onLoadOlder ? /* @__PURE__ */ jsx33("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
2974
- /* @__PURE__ */ jsx33(ParticipantTimeline, { ...props }),
2975
- /* @__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(
2976
3636
  ParticipantScrollToLatest,
2977
3637
  {
2978
3638
  label: labels.scrollToLatest,
@@ -2983,19 +3643,29 @@ function ThreadBody(props) {
2983
3643
  }
2984
3644
  )
2985
3645
  ] }),
2986
- /* @__PURE__ */ jsx33(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
2987
- /* @__PURE__ */ jsx33(Footer, { props })
3646
+ /* @__PURE__ */ jsx39(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
3647
+ /* @__PURE__ */ jsx39(Footer, { props })
2988
3648
  ] });
2989
3649
  }
2990
3650
  function ParticipantThread(props) {
2991
- return /* @__PURE__ */ jsxs25(ParticipantCopyAnnouncer, { children: [
2992
- /* @__PURE__ */ jsx33(ThreadBody, { ...props }),
2993
- /* @__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, {})
2994
3664
  ] });
2995
3665
  }
2996
3666
 
2997
3667
  // src/participant/useParticipantThreadController.ts
2998
- import { useMemo as useMemo5 } from "react";
3668
+ import { useMemo as useMemo6 } from "react";
2999
3669
 
3000
3670
  // src/participant/bindAttachmentUrlResolver.ts
3001
3671
  function bindAttachmentUrlResolver(api) {
@@ -3079,7 +3749,7 @@ function appendEntryToDraft(entry, current) {
3079
3749
  }
3080
3750
 
3081
3751
  // src/participant/useParticipantConversationView.ts
3082
- import { useCallback as useCallback9, useMemo as useMemo4 } from "react";
3752
+ import { useCallback as useCallback11, useMemo as useMemo5 } from "react";
3083
3753
 
3084
3754
  // src/participant/participantDrafts.ts
3085
3755
  var EMPTY_PARTICIPANT_DRAFT = { text: "", files: [] };
@@ -3123,7 +3793,7 @@ function participantSendStatesReducer(states, { subject, action }) {
3123
3793
  }
3124
3794
 
3125
3795
  // src/participant/useParticipantThread.ts
3126
- 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";
3127
3797
 
3128
3798
  // src/participant/participantThreadState.ts
3129
3799
  var INITIAL_PARTICIPANT_THREAD_STATE = {
@@ -3159,7 +3829,7 @@ function participantThreadReducer(state, action) {
3159
3829
  }
3160
3830
 
3161
3831
  // src/participant/useParticipantMarkRead.ts
3162
- import { useEffect as useEffect16 } from "react";
3832
+ import { useEffect as useEffect18 } from "react";
3163
3833
 
3164
3834
  // src/participant/shouldMarkRead.ts
3165
3835
  function shouldMarkParticipantRead(params) {
@@ -3171,12 +3841,12 @@ function shouldMarkParticipantRead(params) {
3171
3841
  }
3172
3842
 
3173
3843
  // src/participant/useDocumentVisibility.ts
3174
- import { useEffect as useEffect15, useState as useState9 } from "react";
3844
+ import { useEffect as useEffect17, useState as useState13 } from "react";
3175
3845
  function useDocumentVisibility() {
3176
- const [visibilityState, setVisibilityState] = useState9(
3846
+ const [visibilityState, setVisibilityState] = useState13(
3177
3847
  () => typeof document === "undefined" ? "visible" : document.visibilityState
3178
3848
  );
3179
- useEffect15(() => {
3849
+ useEffect17(() => {
3180
3850
  function handleVisibilityChange() {
3181
3851
  setVisibilityState(document.visibilityState);
3182
3852
  }
@@ -3191,18 +3861,18 @@ function useDocumentVisibility() {
3191
3861
  function useParticipantMarkRead(params) {
3192
3862
  const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
3193
3863
  const visibilityState = useDocumentVisibility();
3194
- useEffect16(() => {
3864
+ useEffect18(() => {
3195
3865
  if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
3196
3866
  void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
3197
3867
  }, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
3198
3868
  }
3199
3869
 
3200
3870
  // src/participant/useParticipantRevalidation.ts
3201
- import { useEffect as useEffect17, useRef as useRef14 } from "react";
3871
+ import { useEffect as useEffect19, useRef as useRef16 } from "react";
3202
3872
  function useParticipantRevalidation(api, onRevalidate) {
3203
- const handlerRef = useRef14(onRevalidate);
3873
+ const handlerRef = useRef16(onRevalidate);
3204
3874
  handlerRef.current = onRevalidate;
3205
- useEffect17(() => {
3875
+ useEffect19(() => {
3206
3876
  function handleFocus() {
3207
3877
  handlerRef.current();
3208
3878
  }
@@ -3229,12 +3899,12 @@ function useParticipantRevalidation(api, onRevalidate) {
3229
3899
  }
3230
3900
 
3231
3901
  // src/participant/useParticipantSend.ts
3232
- import { useCallback as useCallback6, useRef as useRef15 } from "react";
3902
+ import { useCallback as useCallback8, useRef as useRef17 } from "react";
3233
3903
  function useParticipantSend(params) {
3234
3904
  const { apiRef, subject, dispatchSend, onSentMessage } = params;
3235
- const entriesRef = useRef15(params.entries);
3905
+ const entriesRef = useRef17(params.entries);
3236
3906
  entriesRef.current = params.entries;
3237
- const deliver = useCallback6(
3907
+ const deliver = useCallback8(
3238
3908
  async (entry) => {
3239
3909
  const { clientMessageId } = entry;
3240
3910
  try {
@@ -3258,7 +3928,7 @@ function useParticipantSend(params) {
3258
3928
  },
3259
3929
  [apiRef, dispatchSend, onSentMessage]
3260
3930
  );
3261
- const send = useCallback6(
3931
+ const send = useCallback8(
3262
3932
  async (content) => {
3263
3933
  const entry = createParticipantSendEntry({
3264
3934
  clientMessageId: crypto.randomUUID(),
@@ -3271,7 +3941,7 @@ function useParticipantSend(params) {
3271
3941
  },
3272
3942
  [deliver, dispatchSend, subject]
3273
3943
  );
3274
- const retry = useCallback6(
3944
+ const retry = useCallback8(
3275
3945
  async (clientMessageId) => {
3276
3946
  const entry = findFailedEntry(entriesRef.current, clientMessageId);
3277
3947
  if (!entry) return;
@@ -3284,13 +3954,13 @@ function useParticipantSend(params) {
3284
3954
  }
3285
3955
 
3286
3956
  // src/participant/useParticipantUnread.ts
3287
- 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";
3288
3958
  function useParticipantUnread(params) {
3289
3959
  const { messages, perspective, reportedUnreadCount, onMarkedRead } = params;
3290
- const [acknowledgedCount, setAcknowledgedCount] = useState10(0);
3291
- const messagesRef = useRef16(messages);
3960
+ const [acknowledgedCount, setAcknowledgedCount] = useState14(0);
3961
+ const messagesRef = useRef18(messages);
3292
3962
  messagesRef.current = messages;
3293
- const handleMarkedRead = useCallback7(
3963
+ const handleMarkedRead = useCallback9(
3294
3964
  (subject) => {
3295
3965
  if (perspective) setAcknowledgedCount(countOtherSideMessages(messagesRef.current, perspective));
3296
3966
  onMarkedRead?.(subject);
@@ -3313,13 +3983,13 @@ function isForSubject(event, subject) {
3313
3983
  function useParticipantThread(params) {
3314
3984
  const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [], perspective } = params;
3315
3985
  const { subjectType, subjectId } = subject;
3316
- const stableSubject = useMemo3(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
3986
+ const stableSubject = useMemo4(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
3317
3987
  const [state, dispatch] = useReducer5(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
3318
- const apiRef = useRef17(api);
3988
+ const apiRef = useRef19(api);
3319
3989
  apiRef.current = api;
3320
- const activeSubjectKey = useRef17("");
3990
+ const activeSubjectKey = useRef19("");
3321
3991
  const confirmsRead = channelCapabilityFor(channel).confirmsRead;
3322
- const refresh = useCallback8(async () => {
3992
+ const refresh = useCallback10(async () => {
3323
3993
  const requestKey = `${subjectType}:${subjectId}`;
3324
3994
  dispatch({ type: "started" });
3325
3995
  try {
@@ -3332,7 +4002,7 @@ function useParticipantThread(params) {
3332
4002
  }
3333
4003
  }, [stableSubject, subjectType, subjectId]);
3334
4004
  const oldestMessageId = state.messages[0]?.id;
3335
- const loadOlder = useCallback8(async () => {
4005
+ const loadOlder = useCallback10(async () => {
3336
4006
  if (!oldestMessageId) return;
3337
4007
  const requestKey = `${subjectType}:${subjectId}`;
3338
4008
  try {
@@ -3344,9 +4014,9 @@ function useParticipantThread(params) {
3344
4014
  if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
3345
4015
  }
3346
4016
  }, [stableSubject, subjectType, subjectId, oldestMessageId]);
3347
- const handleSentMessage = useCallback8((message) => dispatch({ type: "sendConfirmed", message }), []);
4017
+ const handleSentMessage = useCallback10((message) => dispatch({ type: "sendConfirmed", message }), []);
3348
4018
  const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
3349
- useEffect18(() => {
4019
+ useEffect20(() => {
3350
4020
  activeSubjectKey.current = `${subjectType}:${subjectId}`;
3351
4021
  dispatch({ type: "reset" });
3352
4022
  void refresh();
@@ -3354,7 +4024,7 @@ function useParticipantThread(params) {
3354
4024
  useParticipantRevalidation(api, (event) => {
3355
4025
  if (isForSubject(event, stableSubject)) void refresh();
3356
4026
  });
3357
- useEffect18(() => {
4027
+ useEffect20(() => {
3358
4028
  if (sendEntries.length === 0) return;
3359
4029
  dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
3360
4030
  }, [sendEntries, state.messages, hostPending, dispatchSend]);
@@ -3366,7 +4036,7 @@ function useParticipantThread(params) {
3366
4036
  messageCount: state.messages.length,
3367
4037
  onMarkedRead: unread.handleMarkedRead
3368
4038
  });
3369
- const localPending = useMemo3(() => toLocalPending(sendEntries), [sendEntries]);
4039
+ const localPending = useMemo4(() => toLocalPending(sendEntries), [sendEntries]);
3370
4040
  return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
3371
4041
  }
3372
4042
 
@@ -3375,7 +4045,7 @@ function useParticipantConversationView(params) {
3375
4045
  const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
3376
4046
  const { subjectType, subjectId } = subject;
3377
4047
  const sendEntries = selectSendEntries(sendStates, subject);
3378
- const dispatchSend = useCallback9(
4048
+ const dispatchSend = useCallback11(
3379
4049
  (action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
3380
4050
  [dispatchSendStates, subjectType, subjectId]
3381
4051
  );
@@ -3390,7 +4060,7 @@ function useParticipantConversationView(params) {
3390
4060
  hostPending: pendingMessages,
3391
4061
  perspective: params.perspective
3392
4062
  });
3393
- const items = useMemo4(
4063
+ const items = useMemo5(
3394
4064
  () => mergeParticipantMessages({
3395
4065
  serverMessages: thread.messages,
3396
4066
  hostPending: pendingMessages ?? [],
@@ -3403,9 +4073,9 @@ function useParticipantConversationView(params) {
3403
4073
  }
3404
4074
 
3405
4075
  // src/participant/useParticipantDraft.ts
3406
- import { useState as useState11 } from "react";
4076
+ import { useState as useState15 } from "react";
3407
4077
  function useParticipantDraft(draftsRef, subject) {
3408
- const [draft, setDraftState] = useState11(() => getDraft(draftsRef.current, subject));
4078
+ const [draft, setDraftState] = useState15(() => getDraft(draftsRef.current, subject));
3409
4079
  function replaceDrafts(next) {
3410
4080
  draftsRef.current = next;
3411
4081
  setDraftState(getDraft(next, subject));
@@ -3425,12 +4095,12 @@ function useParticipantDraft(draftsRef, subject) {
3425
4095
  }
3426
4096
 
3427
4097
  // src/participant/useParticipantSendAudio.ts
3428
- import { useCallback as useCallback10, useRef as useRef18 } from "react";
4098
+ import { useCallback as useCallback12, useRef as useRef20 } from "react";
3429
4099
  function useParticipantSendAudio(send) {
3430
- const sendRef = useRef18(send);
4100
+ const sendRef = useRef20(send);
3431
4101
  sendRef.current = send;
3432
- const inFlightRef = useRef18(/* @__PURE__ */ new Set());
3433
- return useCallback10((file) => {
4102
+ const inFlightRef = useRef20(/* @__PURE__ */ new Set());
4103
+ return useCallback12((file) => {
3434
4104
  const inFlight = inFlightRef.current;
3435
4105
  if (inFlight.has(file)) return;
3436
4106
  inFlight.add(file);
@@ -3446,7 +4116,7 @@ function useParticipantSendAudio(send) {
3446
4116
  }
3447
4117
 
3448
4118
  // src/participant/useParticipantThreadScroll.ts
3449
- import { useEffect as useEffect19, useRef as useRef20 } from "react";
4119
+ import { useEffect as useEffect21, useRef as useRef22 } from "react";
3450
4120
 
3451
4121
  // src/participant/participantNewMessages.ts
3452
4122
  function collectServerMessageIds(items) {
@@ -3471,9 +4141,9 @@ function countNewIncomingMessages(previousIds, currentItems, perspective) {
3471
4141
  }
3472
4142
 
3473
4143
  // src/participant/useScrollAwayState.ts
3474
- import { useCallback as useCallback11 } from "react";
4144
+ import { useCallback as useCallback13 } from "react";
3475
4145
  function useScrollAwayState(ref, isNearBottom) {
3476
- const scrollToLatest = useCallback11(() => {
4146
+ const scrollToLatest = useCallback13(() => {
3477
4147
  const element = ref.current;
3478
4148
  if (!element) return;
3479
4149
  element.scrollTo(
@@ -3484,7 +4154,7 @@ function useScrollAwayState(ref, isNearBottom) {
3484
4154
  }
3485
4155
 
3486
4156
  // src/participant/useStickToBottom.ts
3487
- 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";
3488
4158
 
3489
4159
  // src/participant/participantScroll.ts
3490
4160
  var NEAR_BOTTOM_THRESHOLD_PX = 80;
@@ -3506,17 +4176,17 @@ function resolveScrollAction(params) {
3506
4176
 
3507
4177
  // src/participant/useStickToBottom.ts
3508
4178
  function useStickToBottom({ firstKey, lastKey }) {
3509
- const ref = useRef19(null);
3510
- const snapshot = useRef19({
4179
+ const ref = useRef21(null);
4180
+ const snapshot = useRef21({
3511
4181
  hasPositioned: false,
3512
4182
  firstKey: void 0,
3513
4183
  lastKey: void 0,
3514
4184
  scrollTop: 0,
3515
4185
  scrollHeight: 0
3516
4186
  });
3517
- const [isNearBottom, setIsNearBottom] = useState12(true);
3518
- const isNearBottomRef = useRef19(true);
3519
- const onScroll = useCallback12((event) => {
4187
+ const [isNearBottom, setIsNearBottom] = useState16(true);
4188
+ const isNearBottomRef = useRef21(true);
4189
+ const onScroll = useCallback14((event) => {
3520
4190
  const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
3521
4191
  const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
3522
4192
  snapshot.current.scrollTop = scrollTop;
@@ -3524,7 +4194,7 @@ function useStickToBottom({ firstKey, lastKey }) {
3524
4194
  isNearBottomRef.current = isNear;
3525
4195
  setIsNearBottom(isNear);
3526
4196
  }, []);
3527
- useLayoutEffect3(() => {
4197
+ useLayoutEffect4(() => {
3528
4198
  const element = ref.current;
3529
4199
  const previous = snapshot.current;
3530
4200
  if (!element) return;
@@ -3571,8 +4241,8 @@ function useParticipantThreadScroll(items, perspective) {
3571
4241
  });
3572
4242
  const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
3573
4243
  const scroll = { ...base, isAwayFromBottom, scrollToLatest };
3574
- const seenIds = useRef20(/* @__PURE__ */ new Set());
3575
- useEffect19(() => {
4244
+ const seenIds = useRef22(/* @__PURE__ */ new Set());
4245
+ useEffect21(() => {
3576
4246
  if (isNearBottom) seenIds.current = collectServerMessageIds(items);
3577
4247
  }, [isNearBottom, items]);
3578
4248
  return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items, perspective) };
@@ -3599,7 +4269,7 @@ function useParticipantThreadController(params) {
3599
4269
  const { api, subject, conversation, draftsRef } = params;
3600
4270
  const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, subject);
3601
4271
  const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView(params);
3602
- const resolveAttachmentUrl = useMemo5(() => bindAttachmentUrlResolver(api), [api]);
4272
+ const resolveAttachmentUrl = useMemo6(() => bindAttachmentUrlResolver(api), [api]);
3603
4273
  const { scroll, newMessagesCount } = useParticipantThreadScroll(items, params.perspective);
3604
4274
  const handleSendAudio = useParticipantSendAudio(thread.send);
3605
4275
  const handleEdit = buildEditHandler({ sendEntries, dispatchSend, restoreFromEntry });
@@ -3630,12 +4300,12 @@ function useParticipantThreadController(params) {
3630
4300
  }
3631
4301
 
3632
4302
  // src/participant/ParticipantThreadScreen.tsx
3633
- import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
4303
+ import { jsx as jsx40, jsxs as jsxs31 } from "react/jsx-runtime";
3634
4304
  function useOpenedConversation(api, subject, isNeeded) {
3635
- const [opened, setOpened] = useState13({ status: "idle" });
4305
+ const [opened, setOpened] = useState17({ status: "idle" });
3636
4306
  const { subjectType, subjectId } = subject;
3637
4307
  const canOpen = api.openConversation !== void 0;
3638
- useEffect20(() => {
4308
+ useEffect22(() => {
3639
4309
  if (!isNeeded || !canOpen) return;
3640
4310
  let isCurrent = true;
3641
4311
  setOpened({ status: "loading" });
@@ -3653,9 +4323,9 @@ function NotFound({
3653
4323
  labels,
3654
4324
  onBack
3655
4325
  }) {
3656
- return /* @__PURE__ */ jsxs26("div", { className: "cv-p cv-p-thread", children: [
3657
- /* @__PURE__ */ jsx34("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx34(ParticipantBackButton, { label: labels.back, onBack }) : null }),
3658
- /* @__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 })
3659
4329
  ] });
3660
4330
  }
3661
4331
  function ParticipantThreadScreen(props) {
@@ -3665,12 +4335,12 @@ function ParticipantThreadScreen(props) {
3665
4335
  );
3666
4336
  const opened = useOpenedConversation(api, selected, listed === void 0);
3667
4337
  const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
3668
- if (conversation) return /* @__PURE__ */ jsx34(LoadedThread, { ...props, conversation });
4338
+ if (conversation) return /* @__PURE__ */ jsx40(LoadedThread, { ...props, conversation });
3669
4339
  const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
3670
4340
  const isListLoading = inbox.status === "idle" || inbox.status === "loading";
3671
4341
  if (isResolving || api.openConversation === void 0 && isListLoading)
3672
- return /* @__PURE__ */ jsx34("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
3673
- 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 });
3674
4344
  }
3675
4345
  function LoadedThread(props) {
3676
4346
  const core = useParticipantThreadController({
@@ -3678,7 +4348,7 @@ function LoadedThread(props) {
3678
4348
  subject: props.selected,
3679
4349
  onMarkedRead: props.inbox.markSubjectRead
3680
4350
  });
3681
- return /* @__PURE__ */ jsx34(
4351
+ return /* @__PURE__ */ jsx40(
3682
4352
  ParticipantThread,
3683
4353
  {
3684
4354
  ...core,
@@ -3690,6 +4360,8 @@ function LoadedThread(props) {
3690
4360
  renderSubjectIcon: props.renderSubjectIcon,
3691
4361
  avatars: props.avatars,
3692
4362
  renderAuthorAvatar: props.renderAuthorAvatar,
4363
+ renderMessageActions: props.renderMessageActions,
4364
+ messageMenu: props.messageMenu,
3693
4365
  tail: props.tail,
3694
4366
  audioRecording: props.audioRecording,
3695
4367
  renderComposerLock: props.renderComposerLock
@@ -3818,16 +4490,16 @@ function buildParticipantThemeStyle(theme) {
3818
4490
  }
3819
4491
 
3820
4492
  // src/participant/ParticipantConversationsScreen.tsx
3821
- import { jsx as jsx35 } from "react/jsx-runtime";
4493
+ import { jsx as jsx41 } from "react/jsx-runtime";
3822
4494
  var ALL_FILTER3 = "all";
3823
4495
  function ParticipantConversationsScreen(props) {
3824
4496
  const { selected, inbox, subjectGroups, classNames } = props;
3825
- const [filter, setFilter] = useState14(ALL_FILTER3);
3826
- const [searchQuery, setSearchQuery] = useState14("");
3827
- 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());
3828
4500
  const [sendStates, dispatchSendStates] = useReducer6(participantSendStatesReducer, /* @__PURE__ */ new Map());
3829
- const labels = useMemo6(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
3830
- const view = useMemo6(
4501
+ const labels = useMemo7(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
4502
+ const view = useMemo7(
3831
4503
  () => groupParticipantConversations({
3832
4504
  conversations: inbox.conversations,
3833
4505
  subjectGroups,
@@ -3838,7 +4510,7 @@ function ParticipantConversationsScreen(props) {
3838
4510
  );
3839
4511
  const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
3840
4512
  const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
3841
- 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(
3842
4514
  ParticipantInbox,
3843
4515
  {
3844
4516
  view,
@@ -3855,7 +4527,7 @@ function ParticipantConversationsScreen(props) {
3855
4527
  renderSubjectIcon: props.renderSubjectIcon,
3856
4528
  search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
3857
4529
  }
3858
- ) }) : /* @__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(
3859
4531
  ParticipantThreadScreen,
3860
4532
  {
3861
4533
  ...props,
@@ -3870,7 +4542,7 @@ function ParticipantConversationsScreen(props) {
3870
4542
  }
3871
4543
 
3872
4544
  // src/participant/useParticipantInbox.ts
3873
- 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";
3874
4546
 
3875
4547
  // src/participant/participantInboxState.ts
3876
4548
  var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
@@ -3921,7 +4593,7 @@ function describeError2(error) {
3921
4593
  }
3922
4594
  function useParticipantInbox(api) {
3923
4595
  const [state, dispatch] = useReducer7(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
3924
- const refresh = useCallback13(async () => {
4596
+ const refresh = useCallback15(async () => {
3925
4597
  dispatch({ type: "started" });
3926
4598
  try {
3927
4599
  const page = await api.listConversations();
@@ -3931,7 +4603,7 @@ function useParticipantInbox(api) {
3931
4603
  }
3932
4604
  }, [api]);
3933
4605
  const { nextCursor } = state;
3934
- const loadMore = useCallback13(async () => {
4606
+ const loadMore = useCallback15(async () => {
3935
4607
  if (!nextCursor) return;
3936
4608
  try {
3937
4609
  const page = await api.listConversations({ cursor: nextCursor });
@@ -3940,10 +4612,10 @@ function useParticipantInbox(api) {
3940
4612
  dispatch({ type: "failed", error: describeError2(error) });
3941
4613
  }
3942
4614
  }, [api, nextCursor]);
3943
- const markSubjectRead = useCallback13((subject) => {
4615
+ const markSubjectRead = useCallback15((subject) => {
3944
4616
  dispatch({ type: "markedRead", subject });
3945
4617
  }, []);
3946
- useEffect21(() => {
4618
+ useEffect23(() => {
3947
4619
  void refresh();
3948
4620
  }, [refresh]);
3949
4621
  useParticipantRevalidation(api, () => {
@@ -3953,11 +4625,11 @@ function useParticipantInbox(api) {
3953
4625
  }
3954
4626
 
3955
4627
  // src/participant/ParticipantConversations.tsx
3956
- import { jsx as jsx36 } from "react/jsx-runtime";
4628
+ import { jsx as jsx42 } from "react/jsx-runtime";
3957
4629
  function ParticipantConversations(props) {
3958
4630
  const inbox = useParticipantInbox(props.api);
3959
4631
  const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
3960
- return /* @__PURE__ */ jsx36(
4632
+ return /* @__PURE__ */ jsx42(
3961
4633
  ParticipantConversationsScreen,
3962
4634
  {
3963
4635
  ...props,
@@ -3967,7 +4639,7 @@ function ParticipantConversations(props) {
3967
4639
  }
3968
4640
 
3969
4641
  // src/participant/ConversationThread.tsx
3970
- 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";
3971
4643
 
3972
4644
  // src/participant/conversationThreadApi.ts
3973
4645
  function toParticipantApi(api) {
@@ -4007,20 +4679,20 @@ function buildThreadSummary({
4007
4679
  }
4008
4680
 
4009
4681
  // src/participant/ConversationThread.tsx
4010
- import { jsx as jsx37 } from "react/jsx-runtime";
4682
+ import { jsx as jsx43 } from "react/jsx-runtime";
4011
4683
  function useConversationThreadCore(props) {
4012
4684
  const { perspective = "participant" } = props;
4013
4685
  const { subjectType, subjectId } = props.subject;
4014
- const subject = useMemo7(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
4015
- const onMarkedReadRef = useRef22(props.onMarkedRead);
4686
+ const subject = useMemo8(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
4687
+ const onMarkedReadRef = useRef24(props.onMarkedRead);
4016
4688
  onMarkedReadRef.current = props.onMarkedRead;
4017
- const handleMarkedRead = useCallback14((marked) => onMarkedReadRef.current?.(marked), []);
4018
- const api = useMemo7(() => toParticipantApi(props.api), [props.api]);
4019
- 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());
4020
4692
  const [sendStates, dispatchSendStates] = useReducer8(participantSendStatesReducer, /* @__PURE__ */ new Map());
4021
- const labels = useMemo7(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
4693
+ const labels = useMemo8(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
4022
4694
  const { title, protocol, channels, status } = props;
4023
- const conversation = useMemo7(
4695
+ const conversation = useMemo8(
4024
4696
  () => buildThreadSummary({ subject, title, protocol, channels, status }),
4025
4697
  [subject, title, protocol, channels, status]
4026
4698
  );
@@ -4042,12 +4714,12 @@ function useConversationThreadCore(props) {
4042
4714
  }
4043
4715
  function ConversationThreadBody(props) {
4044
4716
  const core = useConversationThreadCore(props);
4045
- return /* @__PURE__ */ jsx37(
4717
+ return /* @__PURE__ */ jsx43(
4046
4718
  "div",
4047
4719
  {
4048
4720
  className: ["cv-p-root", props.className].filter(Boolean).join(" "),
4049
4721
  style: buildParticipantThemeStyle(props.theme),
4050
- 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(
4051
4723
  ParticipantThread,
4052
4724
  {
4053
4725
  ...core,
@@ -4057,6 +4729,8 @@ function ConversationThreadBody(props) {
4057
4729
  renderSubjectIcon: props.renderSubjectIcon,
4058
4730
  avatars: props.avatars,
4059
4731
  renderAuthorAvatar: props.renderAuthorAvatar,
4732
+ renderMessageActions: props.renderMessageActions,
4733
+ messageMenu: props.messageMenu,
4060
4734
  tail: props.tail,
4061
4735
  counterpartLabel: props.counterpartLabel,
4062
4736
  headerActions: props.headerActions,
@@ -4068,7 +4742,7 @@ function ConversationThreadBody(props) {
4068
4742
  );
4069
4743
  }
4070
4744
  function ConversationThread(props) {
4071
- return /* @__PURE__ */ jsx37(ConversationThreadBody, { ...props }, draftKey(props.subject));
4745
+ return /* @__PURE__ */ jsx43(ConversationThreadBody, { ...props }, draftKey(props.subject));
4072
4746
  }
4073
4747
  export {
4074
4748
  ConversationThread,
@@ -4076,5 +4750,6 @@ export {
4076
4750
  DEFAULT_PARTICIPANT_AUDIO_LABELS,
4077
4751
  DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS,
4078
4752
  ParticipantConversations,
4753
+ formatMessagesForCopy,
4079
4754
  groupParticipantConversations
4080
4755
  };