@arbidocs/blocks 0.4.110 → 0.4.111

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 (94) hide show
  1. package/dist/{NativeDocxEditor-WODZAURG.cjs → NativeDocxEditor-AQKRZBIU.cjs} +7 -7
  2. package/dist/{NativeDocxEditor-WODZAURG.cjs.map → NativeDocxEditor-AQKRZBIU.cjs.map} +1 -1
  3. package/dist/{NativeDocxEditor-4PY4YZYC.js → NativeDocxEditor-IN2HEO5I.js} +3 -3
  4. package/dist/{NativeDocxEditor-4PY4YZYC.js.map → NativeDocxEditor-IN2HEO5I.js.map} +1 -1
  5. package/dist/agents/index.cjs +207 -207
  6. package/dist/agents/index.js +2 -2
  7. package/dist/calendar/EventEditor.d.ts +1 -1
  8. package/dist/calendar/draft.d.ts +7 -1
  9. package/dist/calendar/index.cjs +58 -10
  10. package/dist/calendar/index.cjs.map +1 -1
  11. package/dist/calendar/index.d.ts +1 -1
  12. package/dist/calendar/index.js +56 -11
  13. package/dist/calendar/index.js.map +1 -1
  14. package/dist/calendar/types.d.ts +20 -0
  15. package/dist/calls/CallSidePanel.d.ts +16 -1
  16. package/dist/calls/ControlBar.d.ts +25 -3
  17. package/dist/calls/PreJoin.d.ts +6 -2
  18. package/dist/calls/cards.d.ts +6 -2
  19. package/dist/calls/index.cjs +1078 -47
  20. package/dist/calls/index.cjs.map +1 -1
  21. package/dist/calls/index.d.ts +4 -3
  22. package/dist/calls/index.js +1067 -51
  23. package/dist/calls/index.js.map +1 -1
  24. package/dist/calls/panels.d.ts +11 -0
  25. package/dist/calls/webinar/ChatPermissionSetting.d.ts +18 -0
  26. package/dist/calls/webinar/OnStageCheck.d.ts +23 -0
  27. package/dist/calls/webinar/QAPanel.d.ts +56 -0
  28. package/dist/calls/webinar/RaiseHandButton.d.ts +14 -0
  29. package/dist/calls/webinar/WebinarAudiencePanel.d.ts +23 -0
  30. package/dist/calls/webinar/WebinarViewer.d.ts +39 -0
  31. package/dist/calls/webinar/WebinarWaiting.d.ts +19 -0
  32. package/dist/calls/webinar/hooks.d.ts +13 -0
  33. package/dist/calls/webinar/index.d.ts +14 -0
  34. package/dist/calls/webinar/live.d.ts +43 -0
  35. package/dist/calls/webinar/types.d.ts +49 -0
  36. package/dist/{chunk-GKDWQLVU.js → chunk-2C7TCVQZ.js} +172 -121
  37. package/dist/chunk-2C7TCVQZ.js.map +1 -0
  38. package/dist/{chunk-54WBIVHV.cjs → chunk-2WO5L7YT.cjs} +5 -5
  39. package/dist/{chunk-54WBIVHV.cjs.map → chunk-2WO5L7YT.cjs.map} +1 -1
  40. package/dist/{chunk-A35KT2II.js → chunk-6Z7DLJU2.js} +3 -3
  41. package/dist/{chunk-A35KT2II.js.map → chunk-6Z7DLJU2.js.map} +1 -1
  42. package/dist/{chunk-VZKBI2KJ.cjs → chunk-FXUQZFAN.cjs} +3 -3
  43. package/dist/{chunk-VZKBI2KJ.cjs.map → chunk-FXUQZFAN.cjs.map} +1 -1
  44. package/dist/{chunk-SQNAJPNR.js → chunk-GFYGYME3.js} +3 -3
  45. package/dist/{chunk-SQNAJPNR.js.map → chunk-GFYGYME3.js.map} +1 -1
  46. package/dist/chunk-GICUGQNN.cjs +76 -0
  47. package/dist/chunk-GICUGQNN.cjs.map +1 -0
  48. package/dist/chunk-JJLMCV3T.js +73 -0
  49. package/dist/chunk-JJLMCV3T.js.map +1 -0
  50. package/dist/{chunk-W7ZDBPJ3.cjs → chunk-OR4CO743.cjs} +3 -3
  51. package/dist/{chunk-W7ZDBPJ3.cjs.map → chunk-OR4CO743.cjs.map} +1 -1
  52. package/dist/{chunk-PEHLUB52.js → chunk-Q6B77ZWZ.js} +3 -3
  53. package/dist/{chunk-PEHLUB52.js.map → chunk-Q6B77ZWZ.js.map} +1 -1
  54. package/dist/{chunk-ELDUWTRR.cjs → chunk-TF47ATYS.cjs} +4 -4
  55. package/dist/{chunk-ELDUWTRR.cjs.map → chunk-TF47ATYS.cjs.map} +1 -1
  56. package/dist/{chunk-JB32KYAH.js → chunk-WTM65ZNG.js} +3 -3
  57. package/dist/{chunk-JB32KYAH.js.map → chunk-WTM65ZNG.js.map} +1 -1
  58. package/dist/{chunk-2NIL2ASF.cjs → chunk-XIGWUDOD.cjs} +170 -119
  59. package/dist/chunk-XIGWUDOD.cjs.map +1 -0
  60. package/dist/collaboration/index.cjs +1 -1
  61. package/dist/collaboration/index.js +1 -1
  62. package/dist/connect/docx.cjs +6 -6
  63. package/dist/connect/docx.js +4 -4
  64. package/dist/connect/index.cjs +31 -31
  65. package/dist/connect/index.js +7 -7
  66. package/dist/docx/AgentUndo.d.ts +4 -0
  67. package/dist/docx/index.cjs +46 -46
  68. package/dist/docx/index.js +3 -3
  69. package/dist/files/index.cjs +1 -1
  70. package/dist/files/index.js +1 -1
  71. package/dist/index.cjs +332 -332
  72. package/dist/index.js +6 -6
  73. package/dist/settings/index.cjs +48 -102
  74. package/dist/settings/index.cjs.map +1 -1
  75. package/dist/settings/index.js +10 -70
  76. package/dist/settings/index.js.map +1 -1
  77. package/dist/workflow-canvas/index.cjs +9 -9
  78. package/dist/workflow-canvas/index.js +2 -2
  79. package/node_modules/@arbidocs/coedit/dist/commands.cjs +179 -9
  80. package/node_modules/@arbidocs/coedit/dist/commands.cjs.map +1 -1
  81. package/node_modules/@arbidocs/coedit/dist/commands.d.cts +81 -1
  82. package/node_modules/@arbidocs/coedit/dist/commands.d.ts +81 -1
  83. package/node_modules/@arbidocs/coedit/dist/commands.js +173 -11
  84. package/node_modules/@arbidocs/coedit/dist/commands.js.map +1 -1
  85. package/node_modules/@arbidocs/coedit/dist/server.cjs +58 -2
  86. package/node_modules/@arbidocs/coedit/dist/server.cjs.map +1 -1
  87. package/node_modules/@arbidocs/coedit/dist/server.d.cts +29 -1
  88. package/node_modules/@arbidocs/coedit/dist/server.d.ts +29 -1
  89. package/node_modules/@arbidocs/coedit/dist/server.js +58 -3
  90. package/node_modules/@arbidocs/coedit/dist/server.js.map +1 -1
  91. package/node_modules/@arbidocs/coedit/package.json +1 -1
  92. package/package.json +4 -4
  93. package/dist/chunk-2NIL2ASF.cjs.map +0 -1
  94. package/dist/chunk-GKDWQLVU.js.map +0 -1
@@ -1,14 +1,17 @@
1
1
  "use client";
2
+ import { ConfirmDialog } from '../chunk-JJLMCV3T.js';
2
3
  import { TabHeader } from '../chunk-4WWM3GQ5.js';
4
+ import '../chunk-TX2ZICS7.js';
3
5
  import { NOTICE_MS, gridSlots, gridLayout, spotlightBox, CAPTION_TTL_MS, CAPTION_LINES, speakerAccent, formatElapsed, initialsOf } from '../chunk-Z3AZ7Q5X.js';
4
6
  export { CAPTION_LINES, CAPTION_TTL_MS, INITIAL_SPOTLIGHT, MAX_GRID_TILES, NOTICE_MS, SPEAKER_ACCENTS, SPEAKING_LEVEL, TILE_ASPECT, formatElapsed, gridLayout, gridSlots, initialsOf, nearestCorner, nextSpotlight, pickSpotlight, rowCounts, speakerAccent, speakerBorder, spotlightBox, stageContentHeight } from '../chunk-Z3AZ7Q5X.js';
5
7
  import '../chunk-NPLYHR2E.js';
6
8
  import '../chunk-7F6CBP3Z.js';
9
+ import { fromNow } from '../chunk-OXF2IFXG.js';
7
10
  import '../chunk-VUNV25KB.js';
8
- import { forwardRef, useState, useRef, useMemo, useEffect } from 'react';
9
- import { Button, cn, Tooltip, TooltipTrigger, TooltipContent, Separator, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuCheckboxItem, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@arbidocs/react/ui';
11
+ import { forwardRef, useState, useRef, useMemo, useEffect, useCallback } from 'react';
12
+ import { Button, cn, Tooltip, TooltipTrigger, TooltipContent, Separator, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuCheckboxItem, TabsList, TabsTrigger, Tabs, TabsContent, Input, Textarea, Dialog, DialogContent, DialogTitle, DialogDescription, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@arbidocs/react/ui';
10
13
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
- import { MonitorUp, MicOff, AudioLines, SignalLow, Users, Mic, Captions, Lock, LockOpen, Video, VideoOff, ChevronUp, Loader2, Circle, PhoneOff, MoreHorizontal, LayoutGrid, UserRound, PictureInPicture2, Phone, X, Maximize2, BellOff, Bell, UserX } from 'lucide-react';
14
+ import { MonitorUp, MicOff, AudioLines, SignalLow, Users, Mic, Captions, Lock, LockOpen, Video, VideoOff, ChevronUp, Loader2, Circle, PhoneOff, MoreHorizontal, LayoutGrid, UserRound, PictureInPicture2, Phone, X, Maximize2, BellOff, Bell, CalendarCheck, Radio, SquareX, EyeOff, Hand, Search, Send, MessageCircleQuestion, UserX, CheckCheck, RotateCcw, Trash2, MessageSquareText } from 'lucide-react';
12
15
 
13
16
  var TONE = {
14
17
  neutral: "bg-muted text-foreground hover:bg-accent",
@@ -680,9 +683,11 @@ function LeaveButton({
680
683
  onLeave,
681
684
  compact = false,
682
685
  label = "Leave",
683
- title = "Leave call"
686
+ title = "Leave call",
687
+ options
684
688
  }) {
685
- return /* @__PURE__ */ jsxs(
689
+ const menu = !!options?.length;
690
+ const button = /* @__PURE__ */ jsxs(
686
691
  Button,
687
692
  {
688
693
  type: "button",
@@ -691,7 +696,7 @@ function LeaveButton({
691
696
  className: cn("shrink-0", !compact && "px-4"),
692
697
  "aria-label": title,
693
698
  title,
694
- onClick: onLeave,
699
+ onClick: menu ? void 0 : onLeave,
695
700
  "data-testid": "call-leave",
696
701
  children: [
697
702
  /* @__PURE__ */ jsx(PhoneOff, { className: "size-4", "aria-hidden": true }),
@@ -699,13 +704,34 @@ function LeaveButton({
699
704
  ]
700
705
  }
701
706
  );
707
+ if (!menu) return button;
708
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
709
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: button }),
710
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
711
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onLeave, "data-testid": "call-leave-only", children: [
712
+ /* @__PURE__ */ jsx(PhoneOff, { "aria-hidden": true }),
713
+ label
714
+ ] }),
715
+ options.map((o) => /* @__PURE__ */ jsx(
716
+ DropdownMenuItem,
717
+ {
718
+ onSelect: o.onSelect,
719
+ className: "text-red-text focus:text-red-text",
720
+ "data-testid": o.testId,
721
+ children: o.label
722
+ },
723
+ o.key
724
+ ))
725
+ ] })
726
+ ] });
702
727
  }
728
+ var isSection = (g) => !Array.isArray(g);
703
729
  function MoreMenu({
704
730
  details,
705
731
  groups,
706
732
  label = "More options"
707
733
  }) {
708
- const shown = groups.filter((g) => g.length > 0);
734
+ const shown = groups.filter((g) => isSection(g) || g.length > 0);
709
735
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
710
736
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(CallControlButton, { label, tooltip: false, "data-testid": "call-more", children: /* @__PURE__ */ jsx(MoreHorizontal, { "aria-hidden": true }) }) }),
711
737
  /* @__PURE__ */ jsxs(DropdownMenuContent, { side: "top", align: "end", className: "w-72", "data-testid": "call-more-menu", children: [
@@ -715,7 +741,7 @@ function MoreMenu({
715
741
  ] }),
716
742
  shown.map((group, i) => /* @__PURE__ */ jsxs("div", { role: "none", children: [
717
743
  i > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
718
- group.map((item) => /* @__PURE__ */ jsxs(
744
+ isSection(group) ? group.content : group.map((item) => /* @__PURE__ */ jsxs(
719
745
  DropdownMenuItem,
720
746
  {
721
747
  onSelect: item.onSelect,
@@ -729,7 +755,7 @@ function MoreMenu({
729
755
  },
730
756
  item.key
731
757
  ))
732
- ] }, group[0].key))
758
+ ] }, isSection(group) ? group.key : group[0].key))
733
759
  ] })
734
760
  ] });
735
761
  }
@@ -907,40 +933,69 @@ function CallSidePanel({
907
933
  onClose,
908
934
  closeLabel,
909
935
  closeTestId,
936
+ tabs,
937
+ activeTab,
938
+ onTabChange,
910
939
  children,
911
940
  className,
912
941
  ...rest
913
942
  }) {
914
- return /* @__PURE__ */ jsxs(
943
+ const tabbed = !!tabs?.length;
944
+ const current = activeTab ?? tabs?.[0]?.id;
945
+ const header = /* @__PURE__ */ jsxs("div", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border pl-3 pr-1.5", children: [
946
+ tabbed ? /* @__PURE__ */ jsx(TabsList, { className: "h-8 min-w-0 flex-1 justify-start overflow-x-auto bg-transparent p-0", children: tabs.map((t) => /* @__PURE__ */ jsxs(
947
+ TabsTrigger,
948
+ {
949
+ value: t.id,
950
+ className: "h-8 gap-1.5 px-2.5 data-[state=active]:bg-muted data-[state=active]:text-primary data-[state=active]:shadow-none",
951
+ "data-testid": t.testId,
952
+ children: [
953
+ t.label,
954
+ t.count !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-xs tabular-nums text-muted-foreground", children: t.count })
955
+ ]
956
+ },
957
+ t.id
958
+ )) }) : /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-base font-semibold text-primary", children: [
959
+ icon,
960
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: title })
961
+ ] }),
962
+ actions,
963
+ /* @__PURE__ */ jsx(
964
+ Button,
965
+ {
966
+ type: "button",
967
+ variant: "ghost",
968
+ size: "icon",
969
+ className: "size-8 text-muted-foreground hover:text-foreground",
970
+ "aria-label": closeLabel,
971
+ title: closeLabel,
972
+ onClick: onClose,
973
+ "data-testid": closeTestId,
974
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
975
+ }
976
+ )
977
+ ] });
978
+ return /* @__PURE__ */ jsx(
915
979
  "aside",
916
980
  {
917
981
  className: cn("flex min-h-0 flex-col bg-background text-foreground", className),
918
982
  "aria-label": title,
919
983
  ...rest,
920
- children: [
921
- /* @__PURE__ */ jsxs("div", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border pl-3 pr-1.5", children: [
922
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-base font-semibold text-primary", children: [
923
- icon,
924
- /* @__PURE__ */ jsx("span", { className: "truncate", children: title })
925
- ] }),
926
- actions,
927
- /* @__PURE__ */ jsx(
928
- Button,
929
- {
930
- type: "button",
931
- variant: "ghost",
932
- size: "icon",
933
- className: "size-8 text-muted-foreground hover:text-foreground",
934
- "aria-label": closeLabel,
935
- title: closeLabel,
936
- onClick: onClose,
937
- "data-testid": closeTestId,
938
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
939
- }
940
- )
941
- ] }),
984
+ children: tabbed ? /* @__PURE__ */ jsxs(
985
+ Tabs,
986
+ {
987
+ value: current,
988
+ onValueChange: (id) => onTabChange?.(id),
989
+ className: "flex min-h-0 flex-1 flex-col",
990
+ children: [
991
+ header,
992
+ current && /* @__PURE__ */ jsx(TabsContent, { value: current, className: "mt-0 flex min-h-0 flex-1 flex-col", children })
993
+ ]
994
+ }
995
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
996
+ header,
942
997
  children
943
- ]
998
+ ] })
944
999
  }
945
1000
  );
946
1001
  }
@@ -973,6 +1028,31 @@ function VoiceMeter({
973
1028
  }
974
1029
  );
975
1030
  }
1031
+ function CallPersonLabel({
1032
+ name,
1033
+ entity,
1034
+ isLocal = false,
1035
+ youLabel = "(me)"
1036
+ }) {
1037
+ if (entity) return /* @__PURE__ */ jsx(Fragment, { children: entity });
1038
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1039
+ /* @__PURE__ */ jsx(
1040
+ "span",
1041
+ {
1042
+ className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-xs font-semibold text-primary",
1043
+ "aria-hidden": true,
1044
+ children: initialsOf(name)
1045
+ }
1046
+ ),
1047
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate", children: [
1048
+ name,
1049
+ isLocal && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1050
+ " ",
1051
+ youLabel
1052
+ ] })
1053
+ ] })
1054
+ ] });
1055
+ }
976
1056
  function PersonRow({
977
1057
  p,
978
1058
  canRemove,
@@ -1002,20 +1082,7 @@ function PersonRow({
1002
1082
  children: p.entity
1003
1083
  }
1004
1084
  )
1005
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1006
- /* @__PURE__ */ jsx(
1007
- "span",
1008
- {
1009
- className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-xs font-semibold text-primary",
1010
- "aria-hidden": true,
1011
- children: initialsOf(p.name)
1012
- }
1013
- ),
1014
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate", children: [
1015
- p.name,
1016
- p.isLocal && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: " (me)" })
1017
- ] })
1018
- ] }),
1085
+ ) : /* @__PURE__ */ jsx(CallPersonLabel, { name: p.name, isLocal: p.isLocal }),
1019
1086
  p.speaking && /* @__PURE__ */ jsx("span", { className: "sr-only", children: " \u2014 speaking" }),
1020
1087
  p.note && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: p.note })
1021
1088
  ] }),
@@ -1427,18 +1494,22 @@ function CallStateMessage({
1427
1494
  title,
1428
1495
  description,
1429
1496
  spinner = true,
1497
+ icon,
1430
1498
  action,
1499
+ children,
1431
1500
  testId
1432
1501
  }) {
1433
1502
  return /* @__PURE__ */ jsxs(
1434
1503
  "div",
1435
1504
  {
1436
1505
  className: "flex size-full flex-col items-center justify-center gap-3 p-6 text-center",
1506
+ role: "status",
1437
1507
  "data-testid": testId,
1438
1508
  children: [
1439
- spinner && /* @__PURE__ */ jsx(Loader2, { className: "size-6 animate-spin text-muted-foreground", "aria-hidden": true }),
1509
+ icon ?? (spinner && /* @__PURE__ */ jsx(Loader2, { className: "size-6 animate-spin text-muted-foreground", "aria-hidden": true })),
1440
1510
  /* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: title }),
1441
1511
  description && /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: description }),
1512
+ children,
1442
1513
  action
1443
1514
  ]
1444
1515
  }
@@ -1580,8 +1651,10 @@ function DeviceSelect({
1580
1651
  function PreJoin({
1581
1652
  title,
1582
1653
  heading = "Ready to join?",
1654
+ headingAs: Heading = "h2",
1583
1655
  children,
1584
1656
  joinLabel = "Join",
1657
+ cancelLabel = "Cancel",
1585
1658
  busy = false,
1586
1659
  error,
1587
1660
  onJoin,
@@ -1652,7 +1725,7 @@ function PreJoin({
1652
1725
  ] }),
1653
1726
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [
1654
1727
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1655
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold", children: heading }),
1728
+ /* @__PURE__ */ jsx(Heading, { className: "text-xl font-semibold", children: heading }),
1656
1729
  title && /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-muted-foreground", children: title })
1657
1730
  ] }),
1658
1731
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
@@ -1723,15 +1796,958 @@ function PreJoin({
1723
1796
  type: "button",
1724
1797
  variant: "outline",
1725
1798
  onClick: onCancel,
1799
+ disabled: busy,
1726
1800
  "data-testid": "call-prejoin-cancel",
1727
- children: "Cancel"
1801
+ children: cancelLabel
1728
1802
  }
1729
1803
  )
1730
1804
  ] })
1731
1805
  ] })
1732
1806
  ] }) });
1733
1807
  }
1808
+ function WebinarWaiting({
1809
+ variant,
1810
+ title,
1811
+ when,
1812
+ host,
1813
+ onLeave,
1814
+ leaveLabel = "Leave"
1815
+ }) {
1816
+ const waiting = variant === "waiting";
1817
+ return /* @__PURE__ */ jsx(
1818
+ CallStateMessage,
1819
+ {
1820
+ title: waiting ? "Starting soon" : "This webinar has ended",
1821
+ description: waiting ? "The host hasn't gone live yet. You'll join automatically when the webinar starts." : "Thanks for attending.",
1822
+ spinner: waiting,
1823
+ icon: waiting ? void 0 : /* @__PURE__ */ jsx(CalendarCheck, { className: "size-6 text-muted-foreground", "aria-hidden": true }),
1824
+ testId: waiting ? "webinar-waiting" : "webinar-ended",
1825
+ action: onLeave && /* @__PURE__ */ jsx(
1826
+ Button,
1827
+ {
1828
+ type: "button",
1829
+ variant: "outline",
1830
+ onClick: onLeave,
1831
+ "data-testid": "webinar-waiting-leave",
1832
+ children: leaveLabel
1833
+ }
1834
+ ),
1835
+ children: (title || when || host) && /* @__PURE__ */ jsxs("div", { className: "flex max-w-sm flex-col items-center gap-1 text-sm", children: [
1836
+ title && /* @__PURE__ */ jsx("p", { className: "font-medium", "data-testid": "webinar-waiting-title", children: title }),
1837
+ when && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", "data-testid": "webinar-waiting-when", children: when }),
1838
+ host && /* @__PURE__ */ jsxs(
1839
+ "p",
1840
+ {
1841
+ className: "flex flex-wrap items-center justify-center gap-1 text-muted-foreground",
1842
+ "data-testid": "webinar-waiting-host",
1843
+ children: [
1844
+ /* @__PURE__ */ jsx("span", { children: "Hosted by" }),
1845
+ host
1846
+ ]
1847
+ }
1848
+ )
1849
+ ] })
1850
+ }
1851
+ );
1852
+ }
1853
+ function usePending() {
1854
+ const [keys, setKeys] = useState(() => /* @__PURE__ */ new Set());
1855
+ const mounted = useRef(true);
1856
+ useEffect(() => {
1857
+ mounted.current = true;
1858
+ return () => {
1859
+ mounted.current = false;
1860
+ };
1861
+ }, []);
1862
+ const set = useCallback((key, on) => {
1863
+ if (!mounted.current) return;
1864
+ setKeys((prev) => {
1865
+ if (prev.has(key) === on) return prev;
1866
+ const next = new Set(prev);
1867
+ if (on) next.add(key);
1868
+ else next.delete(key);
1869
+ return next;
1870
+ });
1871
+ }, []);
1872
+ const run = useCallback(
1873
+ async (key, fn) => {
1874
+ let result;
1875
+ try {
1876
+ result = fn();
1877
+ } catch {
1878
+ return false;
1879
+ }
1880
+ if (!result || typeof result.then !== "function") return true;
1881
+ set(key, true);
1882
+ try {
1883
+ await result;
1884
+ return true;
1885
+ } catch {
1886
+ return false;
1887
+ } finally {
1888
+ set(key, false);
1889
+ }
1890
+ },
1891
+ [set]
1892
+ );
1893
+ const isPending = useCallback((key) => keys.has(key), [keys]);
1894
+ return { isPending, run };
1895
+ }
1896
+ function useNow(ms = 3e4, fixed) {
1897
+ const [now, setNow] = useState(() => /* @__PURE__ */ new Date());
1898
+ const ticking = !fixed;
1899
+ useEffect(() => {
1900
+ if (!ticking) return;
1901
+ const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), ms);
1902
+ return () => clearInterval(id);
1903
+ }, [ms, ticking]);
1904
+ return fixed ?? now;
1905
+ }
1906
+ function WebinarLiveChip({
1907
+ compact = false,
1908
+ className
1909
+ }) {
1910
+ return /* @__PURE__ */ jsx(
1911
+ CallStatusChip,
1912
+ {
1913
+ tone: "red",
1914
+ icon: /* @__PURE__ */ jsx(Radio, { className: "size-3.5 shrink-0", "aria-hidden": true }),
1915
+ label: "Live",
1916
+ compact,
1917
+ title: "Live: attendees see and hear the stage",
1918
+ role: "status",
1919
+ className,
1920
+ testId: "webinar-live-chip"
1921
+ }
1922
+ );
1923
+ }
1924
+ function WebinarLiveControl({
1925
+ live,
1926
+ onGoLive,
1927
+ onEnd,
1928
+ compact = false,
1929
+ className
1930
+ }) {
1931
+ const { isPending, run } = usePending();
1932
+ const [confirming, setConfirming] = useState(false);
1933
+ if (!live) {
1934
+ if (!onGoLive) return null;
1935
+ const busy = isPending("live");
1936
+ return /* @__PURE__ */ jsxs(
1937
+ Button,
1938
+ {
1939
+ type: "button",
1940
+ size: compact ? "icon" : "sm",
1941
+ className: cn("h-8 shrink-0", !compact && "px-3", className),
1942
+ disabled: busy,
1943
+ "aria-label": "Go live",
1944
+ title: "Go live: let attendees see and hear the stage",
1945
+ onClick: () => void run("live", onGoLive),
1946
+ "data-testid": "webinar-go-live",
1947
+ children: [
1948
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Radio, { className: "size-4", "aria-hidden": true }),
1949
+ !compact && "Go live"
1950
+ ]
1951
+ }
1952
+ );
1953
+ }
1954
+ if (!onEnd) return null;
1955
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1956
+ /* @__PURE__ */ jsxs(
1957
+ Button,
1958
+ {
1959
+ type: "button",
1960
+ variant: "outline",
1961
+ size: compact ? "icon" : "default",
1962
+ className: cn(
1963
+ "shrink-0 border-red-border text-red-text hover:bg-red-bg hover:text-red-text",
1964
+ !compact && "px-4",
1965
+ className
1966
+ ),
1967
+ "aria-label": "End webinar",
1968
+ title: "End webinar for everyone",
1969
+ onClick: () => setConfirming(true),
1970
+ "data-testid": "webinar-end",
1971
+ children: [
1972
+ /* @__PURE__ */ jsx(SquareX, { className: "size-4", "aria-hidden": true }),
1973
+ !compact && "End webinar"
1974
+ ]
1975
+ }
1976
+ ),
1977
+ /* @__PURE__ */ jsx(WebinarEndConfirm, { open: confirming, onOpenChange: setConfirming, onEnd })
1978
+ ] });
1979
+ }
1980
+ function WebinarEndConfirm({
1981
+ open,
1982
+ onOpenChange,
1983
+ onEnd
1984
+ }) {
1985
+ const { isPending, run } = usePending();
1986
+ const ending = isPending("end");
1987
+ return /* @__PURE__ */ jsx(
1988
+ ConfirmDialog,
1989
+ {
1990
+ isOpen: open,
1991
+ onClose: () => onOpenChange(false),
1992
+ onConfirm: () => void run("end", onEnd).then((ok) => {
1993
+ if (ok) onOpenChange(false);
1994
+ }),
1995
+ title: "End the webinar?",
1996
+ description: "Everyone leaves, attendees and panelists alike. The stage closes and cannot be reopened in this session.",
1997
+ confirmText: "End webinar",
1998
+ isLoading: ending,
1999
+ testId: "webinar-end-confirm",
2000
+ testIds: { cancel: "webinar-end-cancel", confirm: "webinar-end-confirm-button" }
2001
+ }
2002
+ );
2003
+ }
2004
+ function BackstageBanner({
2005
+ onGoLive,
2006
+ className
2007
+ }) {
2008
+ return /* @__PURE__ */ jsxs(
2009
+ "div",
2010
+ {
2011
+ className: cn(
2012
+ "flex min-h-10 min-w-0 items-center gap-2 rounded-md border border-yellow-border bg-yellow-bg py-1 pl-3 pr-1 text-sm text-yellow-text",
2013
+ className
2014
+ ),
2015
+ role: "status",
2016
+ "data-testid": "webinar-backstage",
2017
+ children: [
2018
+ /* @__PURE__ */ jsx(EyeOff, { className: "size-4 shrink-0", "aria-hidden": true }),
2019
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
2020
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Backstage" }),
2021
+ " \u2014 attendees can't see or hear you yet",
2022
+ !onGoLive && /* @__PURE__ */ jsx("span", { className: "text-yellow-text/80", children: ". The host goes live when ready." })
2023
+ ] }),
2024
+ onGoLive && /* @__PURE__ */ jsx(WebinarLiveControl, { live: false, onGoLive })
2025
+ ]
2026
+ }
2027
+ );
2028
+ }
2029
+ function RaiseHandButton({
2030
+ raised,
2031
+ onToggle,
2032
+ pending = false,
2033
+ shortcut = "H",
2034
+ className
2035
+ }) {
2036
+ const { isPending, run } = usePending();
2037
+ const busy = pending || isPending("hand");
2038
+ return /* @__PURE__ */ jsx(
2039
+ CallControlButton,
2040
+ {
2041
+ label: raised ? "Lower hand" : "Raise hand",
2042
+ shortcut,
2043
+ tone: raised ? "active" : "neutral",
2044
+ "aria-pressed": raised,
2045
+ disabled: busy,
2046
+ "aria-busy": busy || void 0,
2047
+ onClick: () => void run("hand", () => onToggle(!raised)),
2048
+ className,
2049
+ "data-testid": "webinar-raise-hand",
2050
+ "data-raised": raised,
2051
+ "data-call-shortcut": shortcut.toLowerCase(),
2052
+ children: /* @__PURE__ */ jsx(Hand, { "aria-hidden": true })
2053
+ }
2054
+ );
2055
+ }
2056
+ var ROLE_NOTE = {
2057
+ host: "Host",
2058
+ cohost: "Co-host",
2059
+ panelist: "Panelist",
2060
+ attendee: null
2061
+ };
2062
+ function SectionTitle({ children }) {
2063
+ return /* @__PURE__ */ jsx("h3", { className: "flex items-center gap-1.5 px-2 pb-1 pt-3 text-xs font-medium text-muted-foreground", children });
2064
+ }
2065
+ function Row({ testId, children }) {
2066
+ return /* @__PURE__ */ jsx(
2067
+ "li",
2068
+ {
2069
+ className: "flex min-h-11 items-center gap-3 rounded-md px-2 py-1 hover:bg-muted",
2070
+ "data-testid": testId,
2071
+ children
2072
+ }
2073
+ );
2074
+ }
2075
+ function WebinarAudiencePanel({
2076
+ hands,
2077
+ attendees,
2078
+ onPromote,
2079
+ onDemote,
2080
+ searchThreshold = 8,
2081
+ now: fixedNow,
2082
+ className
2083
+ }) {
2084
+ const now = useNow(3e4, fixedNow);
2085
+ const { isPending, run } = usePending();
2086
+ const [query, setQuery] = useState("");
2087
+ const queue = useMemo(
2088
+ () => [...hands].sort((a, b) => Date.parse(a.raisedAt) - Date.parse(b.raisedAt)),
2089
+ [hands]
2090
+ );
2091
+ const searchable = attendees.length > searchThreshold;
2092
+ const q = searchable ? query.trim().toLowerCase() : "";
2093
+ const shown = q ? attendees.filter((a) => a.name.toLowerCase().includes(q)) : attendees;
2094
+ const action = (id, name, promote, where) => {
2095
+ const key = `${promote ? "promote" : "demote"}:${id}`;
2096
+ const busy = isPending(key);
2097
+ return /* @__PURE__ */ jsxs(
2098
+ Button,
2099
+ {
2100
+ type: "button",
2101
+ variant: "outline",
2102
+ size: "sm",
2103
+ className: "h-8 shrink-0",
2104
+ disabled: busy,
2105
+ "aria-label": promote ? `Promote ${name} to the stage` : `Move ${name} to the audience`,
2106
+ onClick: () => void run(key, () => promote ? onPromote(id) : onDemote(id)),
2107
+ "data-testid": `webinar-${where === "hand" ? "hand-" : ""}${promote ? "promote" : "demote"}-${id}`,
2108
+ children: [
2109
+ busy && /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
2110
+ promote ? "Promote" : "Move to audience"
2111
+ ]
2112
+ }
2113
+ );
2114
+ };
2115
+ return /* @__PURE__ */ jsxs(
2116
+ "div",
2117
+ {
2118
+ className: cn("min-h-0 flex-1 overflow-y-auto px-2 pb-2", className),
2119
+ "data-testid": "webinar-audience",
2120
+ children: [
2121
+ /* @__PURE__ */ jsxs("section", { "aria-label": "Raised hands", children: [
2122
+ /* @__PURE__ */ jsxs(SectionTitle, { children: [
2123
+ /* @__PURE__ */ jsx(Hand, { className: "size-3.5", "aria-hidden": true }),
2124
+ "Raised hands (",
2125
+ queue.length,
2126
+ ")"
2127
+ ] }),
2128
+ queue.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-2 text-sm text-muted-foreground", "data-testid": "webinar-hands-empty", children: "No hands raised." }) : /* @__PURE__ */ jsx("ol", { children: queue.map((h) => /* @__PURE__ */ jsxs(Row, { testId: `webinar-hand-${h.id}`, children: [
2129
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2130
+ /* @__PURE__ */ jsx("span", { className: "flex min-w-0 items-center gap-1.5 text-sm", children: /* @__PURE__ */ jsx(CallPersonLabel, { name: h.name, entity: h.entity }) }),
2131
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
2132
+ "Raised ",
2133
+ fromNow(h.raisedAt, now)
2134
+ ] })
2135
+ ] }),
2136
+ action(h.id, h.name, true, "hand")
2137
+ ] }, h.id)) })
2138
+ ] }),
2139
+ /* @__PURE__ */ jsxs("section", { "aria-label": "Attendees", children: [
2140
+ /* @__PURE__ */ jsxs(SectionTitle, { children: [
2141
+ "Attendees (",
2142
+ attendees.length,
2143
+ ")"
2144
+ ] }),
2145
+ searchable && /* @__PURE__ */ jsxs("div", { className: "relative px-2 pb-1", children: [
2146
+ /* @__PURE__ */ jsx(
2147
+ Search,
2148
+ {
2149
+ className: "pointer-events-none absolute left-4.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground",
2150
+ "aria-hidden": true
2151
+ }
2152
+ ),
2153
+ /* @__PURE__ */ jsx(
2154
+ Input,
2155
+ {
2156
+ type: "search",
2157
+ value: query,
2158
+ onChange: (e) => setQuery(e.target.value),
2159
+ placeholder: "Find someone",
2160
+ "aria-label": "Find an attendee",
2161
+ className: "h-8 pl-8 text-sm",
2162
+ "data-testid": "webinar-attendee-search"
2163
+ }
2164
+ )
2165
+ ] }),
2166
+ shown.length === 0 ? /* @__PURE__ */ jsx(
2167
+ "p",
2168
+ {
2169
+ className: "px-2 py-2 text-sm text-muted-foreground",
2170
+ "data-testid": "webinar-attendees-empty",
2171
+ children: q ? `No one matches \u201C${query.trim()}\u201D.` : "No attendees yet."
2172
+ }
2173
+ ) : /* @__PURE__ */ jsx("ul", { children: shown.map((a) => {
2174
+ const note = ROLE_NOTE[a.role];
2175
+ return /* @__PURE__ */ jsxs(Row, { testId: `webinar-attendee-${a.id}`, children: [
2176
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: [
2177
+ /* @__PURE__ */ jsx(CallPersonLabel, { name: a.name, entity: a.entity }),
2178
+ (note || a.onStage) && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: [note, a.onStage ? "On stage" : null].filter(Boolean).join(" \xB7 ") })
2179
+ ] }),
2180
+ a.handRaised && /* @__PURE__ */ jsx(
2181
+ Hand,
2182
+ {
2183
+ className: "size-4 shrink-0 text-primary",
2184
+ "aria-label": "Hand raised",
2185
+ "data-testid": `webinar-attendee-hand-${a.id}`
2186
+ }
2187
+ ),
2188
+ a.onStage ? a.role !== "host" && action(a.id, a.name, false, "list") : !a.handRaised && action(a.id, a.name, true, "list")
2189
+ ] }, a.id);
2190
+ }) })
2191
+ ] })
2192
+ ]
2193
+ }
2194
+ );
2195
+ }
2196
+ var DEFAULT_MAX = 500;
2197
+ function sortQuestions(questions, filter) {
2198
+ const match = (s) => filter === "open" ? s === "open" || s === "answering" : s === filter;
2199
+ return questions.filter((q) => match(q.status)).sort(
2200
+ (a, b) => Number(b.status === "answering") - Number(a.status === "answering") || b.voteCount - a.voteCount || Date.parse(a.createdAt) - Date.parse(b.createdAt)
2201
+ );
2202
+ }
2203
+ function PanelComposer({
2204
+ placeholder,
2205
+ ariaLabel,
2206
+ sendLabel,
2207
+ maxLength,
2208
+ busy,
2209
+ onSend,
2210
+ onCancel,
2211
+ autoFocus,
2212
+ testId
2213
+ }) {
2214
+ const [text, setText] = useState("");
2215
+ const body = text.trim();
2216
+ const canSend = !!body && body.length <= maxLength && !busy;
2217
+ const send = () => {
2218
+ if (!canSend) return;
2219
+ void onSend(body).then((ok) => {
2220
+ if (ok) setText("");
2221
+ });
2222
+ };
2223
+ const onKeyDown = (e) => {
2224
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
2225
+ e.preventDefault();
2226
+ send();
2227
+ } else if (e.key === "Escape" && onCancel) {
2228
+ e.preventDefault();
2229
+ onCancel();
2230
+ }
2231
+ };
2232
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", "data-testid": testId, children: [
2233
+ /* @__PURE__ */ jsx(
2234
+ Textarea,
2235
+ {
2236
+ value: text,
2237
+ onChange: (e) => setText(e.target.value),
2238
+ onKeyDown,
2239
+ placeholder,
2240
+ "aria-label": ariaLabel,
2241
+ maxLength,
2242
+ rows: 2,
2243
+ disabled: busy,
2244
+ autoFocus,
2245
+ className: "min-h-[60px] resize-none",
2246
+ "data-testid": `${testId}-input`
2247
+ }
2248
+ ),
2249
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2250
+ /* @__PURE__ */ jsxs(
2251
+ "span",
2252
+ {
2253
+ className: cn(
2254
+ "flex-1 text-xs tabular-nums text-muted-foreground",
2255
+ text.length >= maxLength && "text-red-text"
2256
+ ),
2257
+ "aria-live": "polite",
2258
+ "data-testid": `${testId}-count`,
2259
+ children: [
2260
+ text.length,
2261
+ "/",
2262
+ maxLength
2263
+ ]
2264
+ }
2265
+ ),
2266
+ onCancel && /* @__PURE__ */ jsx(
2267
+ Button,
2268
+ {
2269
+ type: "button",
2270
+ variant: "ghost",
2271
+ size: "sm",
2272
+ className: "h-8",
2273
+ onClick: onCancel,
2274
+ disabled: busy,
2275
+ "data-testid": `${testId}-cancel`,
2276
+ children: "Cancel"
2277
+ }
2278
+ ),
2279
+ /* @__PURE__ */ jsxs(
2280
+ Button,
2281
+ {
2282
+ type: "button",
2283
+ size: "sm",
2284
+ className: "h-8",
2285
+ onClick: send,
2286
+ disabled: !canSend,
2287
+ "data-testid": `${testId}-send`,
2288
+ children: [
2289
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Send, { className: "size-4", "aria-hidden": true }),
2290
+ sendLabel
2291
+ ]
2292
+ }
2293
+ )
2294
+ ] })
2295
+ ] });
2296
+ }
2297
+ function QuestionItem({
2298
+ q,
2299
+ team,
2300
+ now,
2301
+ maxLength,
2302
+ pending,
2303
+ run,
2304
+ onVote,
2305
+ onStatusChange,
2306
+ onAnswer,
2307
+ onDelete
2308
+ }) {
2309
+ const [answering, setAnswering] = useState(false);
2310
+ const voteKey = `vote:${q.id}`;
2311
+ const statusKey = `status:${q.id}`;
2312
+ const answerKey = `answer:${q.id}`;
2313
+ const busy = pending(statusKey) || pending(answerKey) || pending(`delete:${q.id}`);
2314
+ const setStatus = (status) => () => onStatusChange ? onStatusChange(q, status) : void 0;
2315
+ const can = team && !!onStatusChange;
2316
+ const primary = !can ? null : q.status === "open" ? { key: "live", label: "Answer live", icon: /* @__PURE__ */ jsx(Radio, {}), run: setStatus("answering") } : q.status === "answering" ? {
2317
+ key: "answered",
2318
+ label: "Mark answered",
2319
+ icon: /* @__PURE__ */ jsx(CheckCheck, {}),
2320
+ run: setStatus("answered")
2321
+ } : { key: "reopen", label: "Reopen", icon: /* @__PURE__ */ jsx(RotateCcw, {}), run: setStatus("open") };
2322
+ const more = [
2323
+ ...can && q.status === "open" ? [
2324
+ {
2325
+ key: "answered",
2326
+ label: "Mark answered",
2327
+ icon: /* @__PURE__ */ jsx(CheckCheck, {}),
2328
+ run: setStatus("answered")
2329
+ }
2330
+ ] : [],
2331
+ ...can && q.status === "answering" ? [{ key: "reopen", label: "Reopen", icon: /* @__PURE__ */ jsx(RotateCcw, {}), run: setStatus("open") }] : [],
2332
+ ...can && q.status !== "dismissed" ? [{ key: "dismiss", label: "Dismiss", icon: /* @__PURE__ */ jsx(X, {}), run: setStatus("dismissed") }] : [],
2333
+ ...q.mine && q.status === "open" && onDelete ? [
2334
+ {
2335
+ key: "delete",
2336
+ label: "Delete question",
2337
+ icon: /* @__PURE__ */ jsx(Trash2, {}),
2338
+ run: () => onDelete(q),
2339
+ destructive: true
2340
+ }
2341
+ ] : []
2342
+ ];
2343
+ const canAnswerInText = team && !!onAnswer && !q.answer && q.status !== "dismissed";
2344
+ return /* @__PURE__ */ jsxs(
2345
+ "li",
2346
+ {
2347
+ className: "flex gap-2 rounded-md border border-border p-2",
2348
+ "data-testid": `webinar-question-${q.id}`,
2349
+ "data-status": q.status,
2350
+ children: [
2351
+ /* @__PURE__ */ jsxs(
2352
+ Button,
2353
+ {
2354
+ type: "button",
2355
+ variant: "ghost",
2356
+ className: cn(
2357
+ "h-auto w-10 shrink-0 flex-col gap-0 self-start px-0 py-1 text-xs tabular-nums",
2358
+ q.voted ? "bg-primary/10 text-primary hover:bg-primary/15" : "text-muted-foreground"
2359
+ ),
2360
+ "aria-pressed": q.voted,
2361
+ "aria-label": q.mine ? `${q.voteCount} upvotes (your question)` : q.voted ? `Remove upvote (${q.voteCount})` : `Upvote (${q.voteCount})`,
2362
+ title: q.mine ? "You can't upvote your own question" : void 0,
2363
+ disabled: q.mine || pending(voteKey),
2364
+ onClick: () => void run(voteKey, () => onVote(q, !q.voted)),
2365
+ "data-testid": `webinar-question-vote-${q.id}`,
2366
+ children: [
2367
+ /* @__PURE__ */ jsx(ChevronUp, { className: "size-4", "aria-hidden": true }),
2368
+ /* @__PURE__ */ jsx("span", { "data-testid": `webinar-question-votes-${q.id}`, children: q.voteCount })
2369
+ ]
2370
+ }
2371
+ ),
2372
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
2373
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground", children: [
2374
+ /* @__PURE__ */ jsx(
2375
+ "span",
2376
+ {
2377
+ className: "flex min-w-0 items-center gap-1.5 text-sm text-foreground",
2378
+ "data-testid": `webinar-question-asker-${q.id}`,
2379
+ children: q.askerName === null && !q.asker ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Attendee" }) : /* @__PURE__ */ jsx(CallPersonLabel, { name: q.askerName ?? "", entity: q.asker, isLocal: q.mine })
2380
+ }
2381
+ ),
2382
+ /* @__PURE__ */ jsx("time", { dateTime: q.createdAt, children: fromNow(q.createdAt, now) }),
2383
+ q.status === "answering" && /* @__PURE__ */ jsx(
2384
+ CallStatusChip,
2385
+ {
2386
+ tone: "red",
2387
+ icon: /* @__PURE__ */ jsx(Radio, { className: "size-3.5 shrink-0", "aria-hidden": true }),
2388
+ label: "Answering live",
2389
+ testId: `webinar-question-live-${q.id}`
2390
+ }
2391
+ ),
2392
+ q.status === "answered" && /* @__PURE__ */ jsx(
2393
+ CallStatusChip,
2394
+ {
2395
+ tone: "green",
2396
+ icon: /* @__PURE__ */ jsx(CheckCheck, { className: "size-3.5 shrink-0", "aria-hidden": true }),
2397
+ label: "Answered",
2398
+ testId: `webinar-question-answered-${q.id}`
2399
+ }
2400
+ ),
2401
+ q.status === "dismissed" && /* @__PURE__ */ jsx(CallStatusChip, { tone: "neutral", label: "Dismissed" })
2402
+ ] }),
2403
+ /* @__PURE__ */ jsx(
2404
+ "p",
2405
+ {
2406
+ className: "whitespace-pre-wrap break-words text-sm",
2407
+ "data-testid": `webinar-question-body-${q.id}`,
2408
+ children: q.body
2409
+ }
2410
+ ),
2411
+ q.answer && /* @__PURE__ */ jsxs(
2412
+ "div",
2413
+ {
2414
+ className: "flex flex-col gap-1 rounded-md bg-muted px-2.5 py-2 text-sm",
2415
+ "data-testid": `webinar-question-answer-${q.id}`,
2416
+ children: [
2417
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
2418
+ "Answer",
2419
+ q.answeredBy
2420
+ ] }),
2421
+ /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words", children: q.answer })
2422
+ ]
2423
+ }
2424
+ ),
2425
+ answering && onAnswer && /* @__PURE__ */ jsx(
2426
+ PanelComposer,
2427
+ {
2428
+ placeholder: "Write an answer",
2429
+ ariaLabel: "Answer in text",
2430
+ sendLabel: "Send answer",
2431
+ maxLength,
2432
+ busy: pending(answerKey),
2433
+ autoFocus: true,
2434
+ onSend: (text) => run(answerKey, () => onAnswer(q, text)).then((ok) => {
2435
+ if (ok) setAnswering(false);
2436
+ return ok;
2437
+ }),
2438
+ onCancel: () => setAnswering(false),
2439
+ testId: `webinar-answer-${q.id}`
2440
+ }
2441
+ ),
2442
+ (primary || canAnswerInText || more.length > 0) && !answering && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
2443
+ primary && /* @__PURE__ */ jsxs(
2444
+ Button,
2445
+ {
2446
+ type: "button",
2447
+ variant: "outline",
2448
+ size: "sm",
2449
+ className: "h-7 gap-1.5 px-2 text-xs [&_svg]:size-3.5",
2450
+ disabled: busy,
2451
+ onClick: () => void run(statusKey, primary.run),
2452
+ "data-testid": `webinar-question-${primary.key}-${q.id}`,
2453
+ children: [
2454
+ pending(statusKey) ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin", "aria-hidden": true }) : primary.icon,
2455
+ primary.label
2456
+ ]
2457
+ }
2458
+ ),
2459
+ canAnswerInText && /* @__PURE__ */ jsxs(
2460
+ Button,
2461
+ {
2462
+ type: "button",
2463
+ variant: "ghost",
2464
+ size: "sm",
2465
+ className: "h-7 gap-1.5 px-2 text-xs [&_svg]:size-3.5",
2466
+ disabled: busy,
2467
+ onClick: () => setAnswering(true),
2468
+ "data-testid": `webinar-question-answer-text-${q.id}`,
2469
+ children: [
2470
+ /* @__PURE__ */ jsx(MessageSquareText, { "aria-hidden": true }),
2471
+ "Answer in text"
2472
+ ]
2473
+ }
2474
+ ),
2475
+ more.length > 0 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2476
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
2477
+ Button,
2478
+ {
2479
+ type: "button",
2480
+ variant: "ghost",
2481
+ size: "icon",
2482
+ className: "ml-auto size-7 text-muted-foreground hover:text-foreground",
2483
+ "aria-label": "More question actions",
2484
+ disabled: busy,
2485
+ "data-testid": `webinar-question-menu-${q.id}`,
2486
+ children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" })
2487
+ }
2488
+ ) }),
2489
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", className: "w-48", children: more.map((a) => /* @__PURE__ */ jsxs(
2490
+ DropdownMenuItem,
2491
+ {
2492
+ onSelect: () => void run(a.key === "delete" ? `delete:${q.id}` : statusKey, a.run),
2493
+ className: cn(a.destructive && "text-destructive focus:text-destructive"),
2494
+ "data-testid": `webinar-question-${a.key}-${q.id}`,
2495
+ children: [
2496
+ a.icon,
2497
+ a.label
2498
+ ]
2499
+ },
2500
+ a.key
2501
+ )) })
2502
+ ] })
2503
+ ] })
2504
+ ] })
2505
+ ]
2506
+ }
2507
+ );
2508
+ }
2509
+ var EMPTY = {
2510
+ open: {
2511
+ team: "Questions from the audience appear here.",
2512
+ audience: "No questions yet. Ask the first one."
2513
+ },
2514
+ answered: { team: "No answered questions yet.", audience: "No answered questions yet." },
2515
+ dismissed: { team: "Nothing dismissed.", audience: "Nothing dismissed." }
2516
+ };
2517
+ function QAPanel({
2518
+ questions,
2519
+ team = false,
2520
+ maxLength = DEFAULT_MAX,
2521
+ canAsk = true,
2522
+ onAsk,
2523
+ onVote,
2524
+ onStatusChange,
2525
+ onAnswer,
2526
+ onDelete,
2527
+ defaultFilter = "open",
2528
+ now: fixedNow,
2529
+ className
2530
+ }) {
2531
+ const now = useNow(3e4, fixedNow);
2532
+ const { isPending, run } = usePending();
2533
+ const [filter, setFilter] = useState(
2534
+ defaultFilter === "dismissed" && !team ? "open" : defaultFilter
2535
+ );
2536
+ const filters = [
2537
+ { id: "open", label: "Open" },
2538
+ { id: "answered", label: "Answered" },
2539
+ ...team ? [{ id: "dismissed", label: "Dismissed" }] : []
2540
+ ];
2541
+ const lists = useMemo(
2542
+ () => ({
2543
+ open: sortQuestions(questions, "open"),
2544
+ answered: sortQuestions(questions, "answered"),
2545
+ // Dismissed questions are the team's business only.
2546
+ dismissed: team ? sortQuestions(questions, "dismissed") : []
2547
+ }),
2548
+ [questions, team]
2549
+ );
2550
+ const shown = lists[filter];
2551
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col", className), "data-testid": "webinar-qa", children: [
2552
+ /* @__PURE__ */ jsxs(
2553
+ Tabs,
2554
+ {
2555
+ value: filter,
2556
+ onValueChange: (v) => setFilter(v),
2557
+ className: "flex min-h-0 flex-1 flex-col",
2558
+ children: [
2559
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 px-3 pt-3", children: /* @__PURE__ */ jsx(TabsList, { className: "h-8 w-full", children: filters.map((f) => /* @__PURE__ */ jsxs(
2560
+ TabsTrigger,
2561
+ {
2562
+ value: f.id,
2563
+ className: "h-6 flex-1 gap-1.5 px-2 text-xs",
2564
+ "data-testid": `webinar-qa-filter-${f.id}`,
2565
+ children: [
2566
+ f.label,
2567
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: lists[f.id].length })
2568
+ ]
2569
+ },
2570
+ f.id
2571
+ )) }) }),
2572
+ /* @__PURE__ */ jsx(TabsContent, { value: filter, className: "mt-0 min-h-0 flex-1 overflow-y-auto p-3", children: shown.length === 0 ? /* @__PURE__ */ jsx(
2573
+ "p",
2574
+ {
2575
+ className: "py-6 text-center text-sm text-muted-foreground",
2576
+ "data-testid": "webinar-qa-empty",
2577
+ children: filter === "open" && questions.length > 0 ? "No open questions right now." : EMPTY[filter][team ? "team" : "audience"]
2578
+ }
2579
+ ) : /* @__PURE__ */ jsx("ol", { className: "flex flex-col gap-2", children: shown.map((q) => /* @__PURE__ */ jsx(
2580
+ QuestionItem,
2581
+ {
2582
+ q,
2583
+ team,
2584
+ now,
2585
+ maxLength,
2586
+ pending: isPending,
2587
+ run,
2588
+ onVote,
2589
+ onStatusChange,
2590
+ onAnswer,
2591
+ onDelete
2592
+ },
2593
+ q.id
2594
+ )) }) })
2595
+ ]
2596
+ }
2597
+ ),
2598
+ canAsk && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-border p-3", children: /* @__PURE__ */ jsx(
2599
+ PanelComposer,
2600
+ {
2601
+ placeholder: "Ask a question",
2602
+ ariaLabel: "Ask a question",
2603
+ sendLabel: "Ask",
2604
+ maxLength,
2605
+ busy: isPending("ask"),
2606
+ onSend: (text) => run("ask", () => onAsk(text)),
2607
+ testId: "webinar-ask"
2608
+ }
2609
+ ) })
2610
+ ] });
2611
+ }
2612
+ var OPTIONS = [
2613
+ { value: "panelists", label: "Panelists only" },
2614
+ { value: "everyone", label: "Everyone" }
2615
+ ];
2616
+ function ChatPermissionSetting({
2617
+ value,
2618
+ onChange,
2619
+ pending = false
2620
+ }) {
2621
+ const [choosing, setChoosing] = useState(null);
2622
+ const busy = pending || choosing !== null;
2623
+ const select = (next) => {
2624
+ const v = next;
2625
+ if (v === value || busy) return;
2626
+ const result = onChange(v);
2627
+ if (result && typeof result.then === "function") {
2628
+ setChoosing(v);
2629
+ void result.then(
2630
+ () => setChoosing(null),
2631
+ () => setChoosing(null)
2632
+ );
2633
+ }
2634
+ };
2635
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Who can chat", "data-testid": "webinar-chat-permission", children: [
2636
+ /* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
2637
+ "Who can chat",
2638
+ busy && /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin", "aria-label": "Saving" })
2639
+ ] }),
2640
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: choosing ?? value, onValueChange: select, children: OPTIONS.map((o) => /* @__PURE__ */ jsx(
2641
+ DropdownMenuRadioItem,
2642
+ {
2643
+ value: o.value,
2644
+ disabled: busy,
2645
+ "data-testid": `webinar-chat-${o.value}`,
2646
+ children: o.label
2647
+ },
2648
+ o.value
2649
+ )) })
2650
+ ] });
2651
+ }
2652
+ function chatPermissionSection(props) {
2653
+ return { key: "webinar-chat", content: /* @__PURE__ */ jsx(ChatPermissionSetting, { ...props }) };
2654
+ }
2655
+ function OnStageCheck({
2656
+ open,
2657
+ onJoinStage,
2658
+ onStayInAudience,
2659
+ busy = false,
2660
+ error,
2661
+ mirror = true,
2662
+ describeError = describeMediaError
2663
+ }) {
2664
+ const { isPending, run } = usePending();
2665
+ const working = busy || isPending("join") || isPending("stay");
2666
+ const stay = () => {
2667
+ if (!working) void run("stay", onStayInAudience);
2668
+ };
2669
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => !next && stay(), children: /* @__PURE__ */ jsx(DialogContent, { presentation: "sheet", className: "max-w-3xl", "data-testid": "webinar-on-stage", children: /* @__PURE__ */ jsx(
2670
+ PreJoin,
2671
+ {
2672
+ heading: "You're on stage",
2673
+ headingAs: DialogTitle,
2674
+ joinLabel: "Join stage",
2675
+ cancelLabel: "Stay in audience",
2676
+ busy: working,
2677
+ error,
2678
+ mirror,
2679
+ describeError,
2680
+ onJoin: (choices) => void run("join", () => onJoinStage(choices)),
2681
+ onCancel: stay,
2682
+ children: /* @__PURE__ */ jsx(DialogDescription, { children: "A host invited you to speak. Check your microphone and camera: once you join, everyone sees and hears you." })
2683
+ }
2684
+ ) }) });
2685
+ }
2686
+ function WebinarViewer({
2687
+ header,
2688
+ stage,
2689
+ stageRef,
2690
+ stageOverlay,
2691
+ captions,
2692
+ side,
2693
+ sideToggle,
2694
+ hand,
2695
+ onLeave,
2696
+ left,
2697
+ controls,
2698
+ shortcuts = true,
2699
+ className
2700
+ }) {
2701
+ return /* @__PURE__ */ jsxs(
2702
+ "div",
2703
+ {
2704
+ className: cn(
2705
+ "@container flex size-full min-h-0 flex-col bg-background text-foreground",
2706
+ className
2707
+ ),
2708
+ "data-testid": "webinar-viewer",
2709
+ children: [
2710
+ header,
2711
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1", children: [
2712
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col gap-2 px-3 pb-3 pt-1", children: [
2713
+ /* @__PURE__ */ jsxs("div", { ref: stageRef, className: "relative min-h-0 flex-1", "data-testid": "call-stage", children: [
2714
+ stage,
2715
+ stageOverlay
2716
+ ] }),
2717
+ captions
2718
+ ] }),
2719
+ side && /* @__PURE__ */ jsx(
2720
+ "div",
2721
+ {
2722
+ className: "absolute inset-0 z-20 flex min-h-0 flex-col border-t border-border motion-safe:animate-in motion-safe:fade-in-0 @3xl:static @3xl:z-auto @3xl:w-80 @3xl:shrink-0 @3xl:border-l @3xl:border-t-0",
2723
+ "data-testid": "webinar-viewer-side",
2724
+ children: side
2725
+ }
2726
+ )
2727
+ ] }),
2728
+ /* @__PURE__ */ jsx(CallControlBar, { shortcuts, left, right: /* @__PURE__ */ jsx(LeaveButton, { onLeave }), children: /* @__PURE__ */ jsxs(ControlGroup, { children: [
2729
+ /* @__PURE__ */ jsx(RaiseHandButton, { ...hand }),
2730
+ sideToggle && /* @__PURE__ */ jsxs(
2731
+ CallControlButton,
2732
+ {
2733
+ label: sideToggle.open ? `Close ${sideToggle.label}` : sideToggle.label,
2734
+ tone: sideToggle.open ? "active" : "neutral",
2735
+ "aria-pressed": sideToggle.open,
2736
+ onClick: sideToggle.onToggle,
2737
+ "data-testid": "webinar-side-toggle",
2738
+ children: [
2739
+ /* @__PURE__ */ jsx(MessageCircleQuestion, { "aria-hidden": true }),
2740
+ !!sideToggle.count && /* @__PURE__ */ jsx(ControlBadge, { count: sideToggle.count, tone: "quiet" })
2741
+ ]
2742
+ }
2743
+ ),
2744
+ controls
2745
+ ] }) })
2746
+ ]
2747
+ }
2748
+ );
2749
+ }
1734
2750
 
1735
- export { CallControlBar, CallControlButton, CallFocus, CallGrid, CallHeader, CallNotice, CallParticipants, CallSidePanel, CallStageFrame, CallStateMessage, CallStatusChip, CallTile, CaptionsStrip, ConnectionOverlay, ControlBadge, ControlDivider, ControlGroup, DEFAULT_JOIN_CHOICES, ElapsedTime, EncryptionStatusChip, IncomingCallCard, LeaveButton, LobbyRequests, MediaControl, MinimisedCall, MoreMenu, PreJoin, RecordControl, RecordingStatusChip, SpeakingNow, TranscriptionStatusChip, VoiceMeter, describeMediaError, useBriefNotice, useElementSize, useShortcutKeys };
2751
+ export { BackstageBanner, CallControlBar, CallControlButton, CallFocus, CallGrid, CallHeader, CallNotice, CallParticipants, CallPersonLabel, CallSidePanel, CallStageFrame, CallStateMessage, CallStatusChip, CallTile, CaptionsStrip, ChatPermissionSetting, ConnectionOverlay, ControlBadge, ControlDivider, ControlGroup, DEFAULT_JOIN_CHOICES, ElapsedTime, EncryptionStatusChip, IncomingCallCard, LeaveButton, LobbyRequests, MediaControl, MinimisedCall, MoreMenu, OnStageCheck, PanelComposer, PreJoin, QAPanel, RaiseHandButton, RecordControl, RecordingStatusChip, SpeakingNow, TranscriptionStatusChip, VoiceMeter, WebinarAudiencePanel, WebinarEndConfirm, WebinarLiveChip, WebinarLiveControl, WebinarViewer, WebinarWaiting, chatPermissionSection, describeMediaError, sortQuestions, useBriefNotice, useElementSize, useShortcutKeys };
1736
2752
  //# sourceMappingURL=index.js.map
1737
2753
  //# sourceMappingURL=index.js.map