@arbidocs/blocks 0.4.109 → 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 (167) 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 +286 -286
  6. package/dist/agents/index.js +10 -10
  7. package/dist/agents/tasks/ConversationListView.d.ts +5 -1
  8. package/dist/agents/tasks/TaskActivityTimeline.d.ts +5 -1
  9. package/dist/calendar/EventDetails.d.ts +1 -1
  10. package/dist/calendar/EventEditor.d.ts +1 -1
  11. package/dist/calendar/draft.d.ts +7 -1
  12. package/dist/calendar/index.cjs +71 -10
  13. package/dist/calendar/index.cjs.map +1 -1
  14. package/dist/calendar/index.d.ts +1 -1
  15. package/dist/calendar/index.js +69 -11
  16. package/dist/calendar/index.js.map +1 -1
  17. package/dist/calendar/types.d.ts +23 -0
  18. package/dist/calls/CallSidePanel.d.ts +32 -0
  19. package/dist/calls/CallStage.d.ts +57 -0
  20. package/dist/calls/ControlBar.d.ts +113 -0
  21. package/dist/calls/ControlButton.d.ts +44 -0
  22. package/dist/calls/PreJoin.d.ts +35 -0
  23. package/dist/calls/cards.d.ts +58 -0
  24. package/dist/calls/hooks.d.ts +20 -0
  25. package/dist/calls/index.cjs +2884 -0
  26. package/dist/calls/index.cjs.map +1 -0
  27. package/dist/calls/index.d.ts +21 -0
  28. package/dist/calls/index.js +2753 -0
  29. package/dist/calls/index.js.map +1 -0
  30. package/dist/calls/join-choices.d.ts +11 -0
  31. package/dist/calls/layout.d.ts +106 -0
  32. package/dist/calls/panels.d.ts +66 -0
  33. package/dist/calls/status.d.ts +69 -0
  34. package/dist/calls/tiles.d.ts +59 -0
  35. package/dist/calls/webinar/ChatPermissionSetting.d.ts +18 -0
  36. package/dist/calls/webinar/OnStageCheck.d.ts +23 -0
  37. package/dist/calls/webinar/QAPanel.d.ts +56 -0
  38. package/dist/calls/webinar/RaiseHandButton.d.ts +14 -0
  39. package/dist/calls/webinar/WebinarAudiencePanel.d.ts +23 -0
  40. package/dist/calls/webinar/WebinarViewer.d.ts +39 -0
  41. package/dist/calls/webinar/WebinarWaiting.d.ts +19 -0
  42. package/dist/calls/webinar/hooks.d.ts +13 -0
  43. package/dist/calls/webinar/index.d.ts +14 -0
  44. package/dist/calls/webinar/live.d.ts +43 -0
  45. package/dist/calls/webinar/types.d.ts +49 -0
  46. package/dist/chat/Message.d.ts +17 -2
  47. package/dist/chat/TranscriptLine.d.ts +50 -0
  48. package/dist/chat/index.cjs +147 -126
  49. package/dist/chat/index.d.ts +3 -1
  50. package/dist/chat/index.js +5 -4
  51. package/dist/chat/transcript-runs.d.ts +6 -0
  52. package/dist/{chunk-GKDWQLVU.js → chunk-2C7TCVQZ.js} +172 -121
  53. package/dist/chunk-2C7TCVQZ.js.map +1 -0
  54. package/dist/{chunk-QKWQMOHN.cjs → chunk-2UAJVK3C.cjs} +4 -4
  55. package/dist/{chunk-QKWQMOHN.cjs.map → chunk-2UAJVK3C.cjs.map} +1 -1
  56. package/dist/{chunk-54WBIVHV.cjs → chunk-2WO5L7YT.cjs} +5 -5
  57. package/dist/{chunk-54WBIVHV.cjs.map → chunk-2WO5L7YT.cjs.map} +1 -1
  58. package/dist/{chunk-7C35WXYB.js → chunk-5HFPRI3W.js} +5 -5
  59. package/dist/chunk-5HFPRI3W.js.map +1 -0
  60. package/dist/{chunk-ZSV2NQQP.js → chunk-6RHHMBZ5.js} +158 -5
  61. package/dist/chunk-6RHHMBZ5.js.map +1 -0
  62. package/dist/{chunk-FQX6N5G6.js → chunk-6Z7DLJU2.js} +35 -18
  63. package/dist/chunk-6Z7DLJU2.js.map +1 -0
  64. package/dist/{chunk-PGDL26TQ.js → chunk-AY32GGKU.js} +10 -4
  65. package/dist/{chunk-PGDL26TQ.js.map → chunk-AY32GGKU.js.map} +1 -1
  66. package/dist/{chunk-S55VDTX3.js → chunk-CECWAIHL.js} +3 -3
  67. package/dist/{chunk-S55VDTX3.js.map → chunk-CECWAIHL.js.map} +1 -1
  68. package/dist/{chunk-UABPHZWZ.js → chunk-CXZ7FPTQ.js} +3 -3
  69. package/dist/{chunk-UABPHZWZ.js.map → chunk-CXZ7FPTQ.js.map} +1 -1
  70. package/dist/{chunk-C2HRZSAT.cjs → chunk-DJM7DLCT.cjs} +5 -5
  71. package/dist/chunk-DJM7DLCT.cjs.map +1 -0
  72. package/dist/{chunk-GBYY3PIC.cjs → chunk-FXUQZFAN.cjs} +79 -62
  73. package/dist/chunk-FXUQZFAN.cjs.map +1 -0
  74. package/dist/{chunk-W7NZRZ54.js → chunk-GFYGYME3.js} +3 -3
  75. package/dist/{chunk-W7NZRZ54.js.map → chunk-GFYGYME3.js.map} +1 -1
  76. package/dist/chunk-GICUGQNN.cjs +76 -0
  77. package/dist/chunk-GICUGQNN.cjs.map +1 -0
  78. package/dist/{chunk-I4XQCE6U.cjs → chunk-HBBJYRGI.cjs} +162 -5
  79. package/dist/{chunk-ZSV2NQQP.js.map → chunk-HBBJYRGI.cjs.map} +1 -1
  80. package/dist/chunk-JJLMCV3T.js +73 -0
  81. package/dist/chunk-JJLMCV3T.js.map +1 -0
  82. package/dist/chunk-MWDW5UBW.cjs +199 -0
  83. package/dist/chunk-MWDW5UBW.cjs.map +1 -0
  84. package/dist/{chunk-NUTLXVPX.js → chunk-NRUPUDWB.js} +16 -7
  85. package/dist/chunk-NRUPUDWB.js.map +1 -0
  86. package/dist/{chunk-5YXG342J.cjs → chunk-OR4CO743.cjs} +3 -3
  87. package/dist/{chunk-5YXG342J.cjs.map → chunk-OR4CO743.cjs.map} +1 -1
  88. package/dist/{chunk-DDF5GPIJ.js → chunk-P5ST47VY.js} +3 -3
  89. package/dist/{chunk-DDF5GPIJ.js.map → chunk-P5ST47VY.js.map} +1 -1
  90. package/dist/{chunk-PEHLUB52.js → chunk-Q6B77ZWZ.js} +3 -3
  91. package/dist/{chunk-PEHLUB52.js.map → chunk-Q6B77ZWZ.js.map} +1 -1
  92. package/dist/{chunk-ELDUWTRR.cjs → chunk-TF47ATYS.cjs} +4 -4
  93. package/dist/{chunk-ELDUWTRR.cjs.map → chunk-TF47ATYS.cjs.map} +1 -1
  94. package/dist/{chunk-PDRYGPZB.cjs → chunk-VP4KJ4LK.cjs} +16 -6
  95. package/dist/chunk-VP4KJ4LK.cjs.map +1 -0
  96. package/dist/{chunk-SKSE4OT6.cjs → chunk-WPVIOGDG.cjs} +5 -5
  97. package/dist/{chunk-SKSE4OT6.cjs.map → chunk-WPVIOGDG.cjs.map} +1 -1
  98. package/dist/{chunk-JB32KYAH.js → chunk-WTM65ZNG.js} +3 -3
  99. package/dist/{chunk-JB32KYAH.js.map → chunk-WTM65ZNG.js.map} +1 -1
  100. package/dist/{chunk-2NIL2ASF.cjs → chunk-XIGWUDOD.cjs} +170 -119
  101. package/dist/chunk-XIGWUDOD.cjs.map +1 -0
  102. package/dist/{chunk-3US5OIDQ.cjs → chunk-XSUOEJB3.cjs} +9 -3
  103. package/dist/chunk-XSUOEJB3.cjs.map +1 -0
  104. package/dist/chunk-Z3AZ7Q5X.js +177 -0
  105. package/dist/chunk-Z3AZ7Q5X.js.map +1 -0
  106. package/dist/{chunk-Z5VUZ6YD.cjs → chunk-ZV3BKLFY.cjs} +3 -3
  107. package/dist/{chunk-Z5VUZ6YD.cjs.map → chunk-ZV3BKLFY.cjs.map} +1 -1
  108. package/dist/collaboration/index.cjs +42 -42
  109. package/dist/collaboration/index.js +2 -2
  110. package/dist/connect/docx.cjs +6 -6
  111. package/dist/connect/docx.js +4 -4
  112. package/dist/connect/index.cjs +72 -72
  113. package/dist/connect/index.js +18 -18
  114. package/dist/docx/AgentUndo.d.ts +4 -0
  115. package/dist/docx/index.cjs +46 -46
  116. package/dist/docx/index.js +3 -3
  117. package/dist/facts/index.cjs +4 -4
  118. package/dist/facts/index.js +4 -4
  119. package/dist/files/index.cjs +6 -6
  120. package/dist/files/index.js +6 -6
  121. package/dist/home/index.cjs +17 -17
  122. package/dist/home/index.js +3 -3
  123. package/dist/index.cjs +958 -937
  124. package/dist/index.cjs.map +1 -1
  125. package/dist/index.js +24 -23
  126. package/dist/index.js.map +1 -1
  127. package/dist/retrieval/index.cjs +1 -1
  128. package/dist/retrieval/index.js +1 -1
  129. package/dist/settings/index.cjs +50 -104
  130. package/dist/settings/index.cjs.map +1 -1
  131. package/dist/settings/index.js +12 -72
  132. package/dist/settings/index.js.map +1 -1
  133. package/dist/shell/index.cjs +12 -12
  134. package/dist/shell/index.js +4 -4
  135. package/dist/site/index.cjs +5 -5
  136. package/dist/site/index.js +1 -1
  137. package/dist/studio/index.cjs +5 -5
  138. package/dist/studio/index.js +4 -4
  139. package/dist/workflow-canvas/index.cjs +53 -53
  140. package/dist/workflow-canvas/index.js +10 -10
  141. package/dist/workspaces/index.cjs +2 -2
  142. package/dist/workspaces/index.js +2 -2
  143. package/node_modules/@arbidocs/coedit/dist/commands.cjs +179 -9
  144. package/node_modules/@arbidocs/coedit/dist/commands.cjs.map +1 -1
  145. package/node_modules/@arbidocs/coedit/dist/commands.d.cts +81 -1
  146. package/node_modules/@arbidocs/coedit/dist/commands.d.ts +81 -1
  147. package/node_modules/@arbidocs/coedit/dist/commands.js +173 -11
  148. package/node_modules/@arbidocs/coedit/dist/commands.js.map +1 -1
  149. package/node_modules/@arbidocs/coedit/dist/server.cjs +58 -2
  150. package/node_modules/@arbidocs/coedit/dist/server.cjs.map +1 -1
  151. package/node_modules/@arbidocs/coedit/dist/server.d.cts +29 -1
  152. package/node_modules/@arbidocs/coedit/dist/server.d.ts +29 -1
  153. package/node_modules/@arbidocs/coedit/dist/server.js +58 -3
  154. package/node_modules/@arbidocs/coedit/dist/server.js.map +1 -1
  155. package/node_modules/@arbidocs/coedit/package.json +1 -1
  156. package/package.json +15 -5
  157. package/src/calls/calls.css +157 -0
  158. package/dist/chunk-2NIL2ASF.cjs.map +0 -1
  159. package/dist/chunk-3US5OIDQ.cjs.map +0 -1
  160. package/dist/chunk-7C35WXYB.js.map +0 -1
  161. package/dist/chunk-C2HRZSAT.cjs.map +0 -1
  162. package/dist/chunk-FQX6N5G6.js.map +0 -1
  163. package/dist/chunk-GBYY3PIC.cjs.map +0 -1
  164. package/dist/chunk-GKDWQLVU.js.map +0 -1
  165. package/dist/chunk-I4XQCE6U.cjs.map +0 -1
  166. package/dist/chunk-NUTLXVPX.js.map +0 -1
  167. package/dist/chunk-PDRYGPZB.cjs.map +0 -1
@@ -0,0 +1,2884 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunkGICUGQNN_cjs = require('../chunk-GICUGQNN.cjs');
5
+ var chunk7RGLTF76_cjs = require('../chunk-7RGLTF76.cjs');
6
+ require('../chunk-ULWRNGGE.cjs');
7
+ var chunkMWDW5UBW_cjs = require('../chunk-MWDW5UBW.cjs');
8
+ require('../chunk-PPCATBOY.cjs');
9
+ require('../chunk-MHVNT4J3.cjs');
10
+ var chunkKDG6FER6_cjs = require('../chunk-KDG6FER6.cjs');
11
+ require('../chunk-DES3K4SD.cjs');
12
+ var react = require('react');
13
+ var ui = require('@arbidocs/react/ui');
14
+ var jsxRuntime = require('react/jsx-runtime');
15
+ var lucideReact = require('lucide-react');
16
+
17
+ var TONE = {
18
+ neutral: "bg-muted text-foreground hover:bg-accent",
19
+ // Solid: a control whose thing is on reads as on at a glance (CC on, chat open, sharing).
20
+ active: "bg-primary text-primary-foreground hover:bg-primary/90",
21
+ off: "bg-red-bg text-red-text hover:bg-red-bg/80",
22
+ danger: "bg-destructive text-destructive-foreground hover:bg-destructive/90"
23
+ };
24
+ var CallControlButton = react.forwardRef(
25
+ function CallControlButton2({
26
+ label,
27
+ shortcut,
28
+ tone = "neutral",
29
+ side = "top",
30
+ tooltip = true,
31
+ className,
32
+ children,
33
+ ...props
34
+ }, ref) {
35
+ const button = /* @__PURE__ */ jsxRuntime.jsx(
36
+ ui.Button,
37
+ {
38
+ ref,
39
+ type: "button",
40
+ variant: "ghost",
41
+ size: "icon",
42
+ "aria-label": label,
43
+ className: ui.cn("relative shrink-0 [&_svg]:size-[18px]", TONE[tone], className),
44
+ ...props,
45
+ children
46
+ }
47
+ );
48
+ if (!tooltip) return button;
49
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Tooltip, { children: [
50
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: button }) }),
51
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TooltipContent, { side, children: [
52
+ label,
53
+ shortcut && /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "ml-2 rounded-sm border border-border px-1 font-sans text-[11px] text-muted-foreground", children: shortcut })
54
+ ] })
55
+ ] });
56
+ }
57
+ );
58
+ function ControlBadge({
59
+ count,
60
+ tone = "alert",
61
+ testId
62
+ }) {
63
+ return /* @__PURE__ */ jsxRuntime.jsx(
64
+ "span",
65
+ {
66
+ className: ui.cn(
67
+ "pointer-events-none absolute -right-1 min-w-[1.125rem] rounded-full px-1 text-center text-[10px] font-semibold leading-[1.125rem]",
68
+ tone === "alert" ? "-top-1 bg-destructive text-destructive-foreground ring-2 ring-background" : "-bottom-1 bg-background text-muted-foreground ring-1 ring-border"
69
+ ),
70
+ "data-testid": testId,
71
+ children: count > 99 ? "99+" : count
72
+ }
73
+ );
74
+ }
75
+ function CallStatusChip({
76
+ tone,
77
+ icon,
78
+ label,
79
+ compact = false,
80
+ pulse = false,
81
+ title,
82
+ children,
83
+ className,
84
+ testId,
85
+ ...rest
86
+ }) {
87
+ const tones = {
88
+ red: "border-red-border bg-red-bg text-red-text",
89
+ green: "border-green-border bg-green-bg text-green-text",
90
+ yellow: "border-yellow-border bg-yellow-bg text-yellow-text",
91
+ neutral: "border-border bg-muted text-muted-foreground"
92
+ };
93
+ return /* @__PURE__ */ jsxRuntime.jsxs(
94
+ "span",
95
+ {
96
+ className: ui.cn(
97
+ "inline-flex h-6 shrink-0 items-center gap-1.5 rounded-md border px-2 text-xs font-medium",
98
+ tones[tone],
99
+ className
100
+ ),
101
+ title,
102
+ "data-testid": testId,
103
+ ...rest,
104
+ children: [
105
+ pulse && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex size-2 shrink-0", "aria-hidden": true, children: [
106
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex size-full animate-ping rounded-full bg-red-solid opacity-60 motion-reduce:hidden" }),
107
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex size-2 rounded-full bg-red-solid" })
108
+ ] }),
109
+ icon,
110
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("min-w-0 truncate", compact && "sr-only"), children: label }),
111
+ children
112
+ ]
113
+ }
114
+ );
115
+ }
116
+ function useElementSize() {
117
+ const [size, setSize] = react.useState({ width: 0, height: 0 });
118
+ const observer = react.useRef(null);
119
+ const ref = react.useMemo(
120
+ () => (el) => {
121
+ observer.current?.disconnect();
122
+ observer.current = null;
123
+ if (!el || typeof ResizeObserver === "undefined") return;
124
+ const ro = new ResizeObserver((entries) => {
125
+ const r = entries[0]?.contentRect;
126
+ if (!r) return;
127
+ setSize(
128
+ (s) => s.width === r.width && s.height === r.height ? s : { width: r.width, height: r.height }
129
+ );
130
+ });
131
+ ro.observe(el);
132
+ observer.current = ro;
133
+ },
134
+ []
135
+ );
136
+ react.useEffect(() => () => observer.current?.disconnect(), []);
137
+ return [ref, size];
138
+ }
139
+ function useBriefNotice(announce = true, onFolded, ms = chunkMWDW5UBW_cjs.NOTICE_MS) {
140
+ const [expanded, setExpanded] = react.useState(announce);
141
+ const folded = react.useRef(onFolded);
142
+ folded.current = onFolded;
143
+ react.useEffect(() => {
144
+ if (!expanded) return;
145
+ const id = setTimeout(() => {
146
+ setExpanded(false);
147
+ folded.current?.();
148
+ }, ms);
149
+ return () => clearTimeout(id);
150
+ }, [expanded, ms]);
151
+ return expanded;
152
+ }
153
+ function typing(e) {
154
+ const el = e.target;
155
+ return !!el?.closest?.('input, textarea, select, [contenteditable="true"], [role="textbox"]');
156
+ }
157
+ function useShortcutKeys(ref, enabled) {
158
+ react.useEffect(() => {
159
+ if (!enabled) return;
160
+ const onKey = (e) => {
161
+ if (e.repeat || e.ctrlKey || e.metaKey || e.altKey || typing(e)) return;
162
+ const key = e.key.toLowerCase();
163
+ if (!/^[a-z]$/.test(key)) return;
164
+ const button = ref.current?.querySelector(
165
+ `[data-call-shortcut="${key}"]:not(:disabled)`
166
+ );
167
+ if (!button) return;
168
+ e.preventDefault();
169
+ button.click();
170
+ };
171
+ window.addEventListener("keydown", onKey);
172
+ return () => window.removeEventListener("keydown", onKey);
173
+ }, [ref, enabled]);
174
+ }
175
+ var CallTile = react.forwardRef(function CallTile2({
176
+ name,
177
+ initials,
178
+ video,
179
+ source = "camera",
180
+ local = false,
181
+ mirror = false,
182
+ speaking = false,
183
+ level = 0,
184
+ micMuted = false,
185
+ poorConnection = false,
186
+ size = "normal",
187
+ youLabel,
188
+ className,
189
+ style,
190
+ ...rest
191
+ }, ref) {
192
+ const screen = source === "screen";
193
+ const ring = speaking && !screen ? Math.min(1, Math.max(0, level)) : 0;
194
+ return /* @__PURE__ */ jsxRuntime.jsxs(
195
+ "div",
196
+ {
197
+ ref,
198
+ className: ui.cn("call-tile", className),
199
+ "data-call-tile": "",
200
+ "data-source": source,
201
+ "data-local": local,
202
+ "data-mirror": mirror && source === "camera" && !!video,
203
+ "data-speaking": speaking && !screen,
204
+ style: { "--call-level": ring.toFixed(2), ...style },
205
+ ...rest,
206
+ children: [
207
+ video ?? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "call-tile-placeholder", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("call-tile-avatar", size === "small" && "call-tile-avatar-sm"), children: initials }) }),
208
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "call-tile-name", children: [
209
+ typeof name === "string" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "call-tile-chip min-w-0", children: [
210
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: name }),
211
+ local && !screen && youLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 opacity-80", children: youLabel })
212
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "call-tile-chip min-w-0 overflow-hidden py-0.5 pl-0.5", children: name }),
213
+ (screen || micMuted || speaking) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "call-tile-chip shrink-0", children: [
214
+ screen && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, { className: "size-3.5", "aria-label": "Screen" }),
215
+ !screen && micMuted && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { className: "size-3.5", "aria-label": "Microphone off" }),
216
+ speaking && !screen && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AudioLines, { className: "call-tile-speaking size-3.5", "aria-label": "Speaking" })
217
+ ] })
218
+ ] }),
219
+ poorConnection && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "call-tile-signal", title: "Poor connection", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SignalLow, { className: "size-3.5", "aria-label": "Poor connection" }) })
220
+ ]
221
+ }
222
+ );
223
+ });
224
+ var GRID_GAP = 8;
225
+ function CallGrid({
226
+ items,
227
+ getKey,
228
+ renderTile,
229
+ moreLabel = (n) => `${n} more`,
230
+ className
231
+ }) {
232
+ const [ref, size] = useElementSize();
233
+ const { shown, hidden } = chunkMWDW5UBW_cjs.gridSlots(items);
234
+ const count = shown.length + (hidden ? 1 : 0);
235
+ const layout = chunkMWDW5UBW_cjs.gridLayout(count, size.width, size.height, GRID_GAP);
236
+ const box = { width: layout.tileWidth, height: layout.tileHeight };
237
+ const rows = [];
238
+ let next = 0;
239
+ for (const n of layout.rowCounts) rows.push(Array.from({ length: n }, () => next++));
240
+ return /* @__PURE__ */ jsxRuntime.jsx(
241
+ "div",
242
+ {
243
+ ref,
244
+ className: ui.cn("flex size-full min-h-0 flex-col items-center", className),
245
+ style: { gap: GRID_GAP },
246
+ "data-testid": "call-grid",
247
+ "data-cols": layout.cols,
248
+ children: layout.tileWidth > 0 && rows.map((row, r) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center", style: { gap: GRID_GAP }, children: row.map(
249
+ (i) => i < shown.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "call-tile-box", style: box, children: renderTile(shown[i]) }, getKey(shown[i])) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "call-tile call-tile-more", style: box, children: [
250
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "size-5", "aria-hidden": true }),
251
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: moreLabel(hidden) })
252
+ ] }, "more")
253
+ ) }, r))
254
+ }
255
+ );
256
+ }
257
+ function CallFocus({
258
+ focus,
259
+ others,
260
+ getKey,
261
+ renderTile,
262
+ className,
263
+ testId = "call-focus"
264
+ }) {
265
+ const [ref, size] = useElementSize();
266
+ const narrow = size.width > 0 && size.width < 640;
267
+ const box = chunkMWDW5UBW_cjs.spotlightBox(size.width, size.height, others.length, narrow);
268
+ return /* @__PURE__ */ jsxRuntime.jsxs(
269
+ "div",
270
+ {
271
+ ref,
272
+ className: ui.cn(
273
+ "flex size-full min-h-0 gap-2",
274
+ narrow ? "flex-col items-center" : "flex-row items-start justify-center",
275
+ className
276
+ ),
277
+ "data-testid": testId,
278
+ children: [
279
+ /* @__PURE__ */ jsxRuntime.jsx(
280
+ "div",
281
+ {
282
+ className: "call-tile-box shrink-0",
283
+ style: { width: box.width, height: box.height },
284
+ "data-testid": "call-spotlight",
285
+ children: box.width > 0 && renderTile(focus, "normal")
286
+ },
287
+ getKey(focus)
288
+ ),
289
+ others.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
290
+ "div",
291
+ {
292
+ className: ui.cn(
293
+ "grid shrink-0 gap-2",
294
+ narrow ? "w-full grid-cols-3 content-start" : "w-60 grid-cols-1 overflow-y-auto"
295
+ ),
296
+ style: narrow ? { width: box.width } : void 0,
297
+ "data-testid": "call-filmstrip",
298
+ children: others.map((t) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "call-tile-box aspect-video w-full min-w-0", children: renderTile(t, "small") }, getKey(t)))
299
+ }
300
+ )
301
+ ]
302
+ }
303
+ );
304
+ }
305
+ function FoldingLabel({
306
+ sentence,
307
+ short,
308
+ expanded
309
+ }) {
310
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn(!expanded && "sr-only"), children: sentence }),
312
+ !expanded && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: short })
313
+ ] });
314
+ }
315
+ function RecordingStatusChip({
316
+ sentence,
317
+ short = "Recording",
318
+ compact = false,
319
+ announce = true,
320
+ onAnnounced,
321
+ className,
322
+ ...rest
323
+ }) {
324
+ const expanded = useBriefNotice(announce, onAnnounced);
325
+ return /* @__PURE__ */ jsxRuntime.jsx(
326
+ CallStatusChip,
327
+ {
328
+ tone: "red",
329
+ pulse: true,
330
+ role: "status",
331
+ "aria-live": "polite",
332
+ label: /* @__PURE__ */ jsxRuntime.jsx(FoldingLabel, { sentence, short, expanded }),
333
+ compact: compact && !expanded,
334
+ title: sentence,
335
+ className,
336
+ "data-expanded": expanded,
337
+ ...rest
338
+ }
339
+ );
340
+ }
341
+ function TranscriptionStatusChip({
342
+ sentence,
343
+ announce = true,
344
+ onAnnounced,
345
+ short = "Transcribing",
346
+ detail,
347
+ muted,
348
+ onMutedChange,
349
+ muteLabel,
350
+ unmuteLabel,
351
+ compact = false,
352
+ className,
353
+ ...rest
354
+ }) {
355
+ const expanded = useBriefNotice(announce, onAnnounced);
356
+ const label = muted ? unmuteLabel : muteLabel;
357
+ return /* @__PURE__ */ jsxRuntime.jsx(
358
+ CallStatusChip,
359
+ {
360
+ tone: "red",
361
+ role: "status",
362
+ "aria-live": "polite",
363
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Captions, { className: "size-3.5 shrink-0", "aria-hidden": true }),
364
+ label: /* @__PURE__ */ jsxRuntime.jsx(FoldingLabel, { sentence, short, expanded }),
365
+ compact: compact && !expanded,
366
+ title: detail ? `${sentence}. ${detail}` : sentence,
367
+ className: ui.cn("pr-0.5", className),
368
+ "data-expanded": expanded,
369
+ ...rest,
370
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tooltip, { children: [
371
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
372
+ "button",
373
+ {
374
+ type: "button",
375
+ className: "inline-flex h-5! w-5! shrink-0 items-center justify-center rounded-sm p-0! hover:bg-red-text/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
376
+ "aria-pressed": muted,
377
+ "aria-label": label,
378
+ onClick: () => onMutedChange(!muted),
379
+ "data-testid": "transcription-mute",
380
+ children: muted ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { className: "size-3.5", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "size-3.5", "aria-hidden": true })
381
+ }
382
+ ) }),
383
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipContent, { side: "bottom", children: label })
384
+ ] })
385
+ }
386
+ );
387
+ }
388
+ function EncryptionStatusChip({
389
+ state,
390
+ compact = false,
391
+ labels = {
392
+ on: "Encrypted",
393
+ pending: "Encrypting\u2026",
394
+ off: "Not encrypted",
395
+ onTitle: "Media is end-to-end encrypted; the media server only sees ciphertext.",
396
+ offTitle: "This call is not end-to-end encrypted."
397
+ }
398
+ }) {
399
+ const on = state === "on";
400
+ return /* @__PURE__ */ jsxRuntime.jsx(
401
+ CallStatusChip,
402
+ {
403
+ tone: on ? "neutral" : "yellow",
404
+ icon: on ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Lock, { className: "size-3.5 shrink-0", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LockOpen, { className: "size-3.5 shrink-0", "aria-hidden": true }),
405
+ label: on ? labels.on : state === "pending" ? labels.pending : labels.off,
406
+ compact,
407
+ title: on ? labels.onTitle : labels.offTitle,
408
+ "data-testid": "call-encryption-badge",
409
+ "data-state": state
410
+ }
411
+ );
412
+ }
413
+ function CaptionsStrip({
414
+ captions,
415
+ reserve = false,
416
+ waitingText = "Captions appear here as people speak.",
417
+ className
418
+ }) {
419
+ const [now, setNow] = react.useState(() => Date.now());
420
+ const visible = captions.filter((c) => now - c.receivedAt < chunkMWDW5UBW_cjs.CAPTION_TTL_MS).slice(-chunkMWDW5UBW_cjs.CAPTION_LINES);
421
+ const showing = visible.length > 0;
422
+ const newest = captions[captions.length - 1]?.receivedAt;
423
+ react.useEffect(() => {
424
+ setNow(Date.now());
425
+ }, [newest]);
426
+ react.useEffect(() => {
427
+ if (!showing) return;
428
+ const id = setInterval(() => setNow(Date.now()), 1e3);
429
+ return () => clearInterval(id);
430
+ }, [showing]);
431
+ if (!showing && !reserve) return null;
432
+ return /* @__PURE__ */ jsxRuntime.jsx(
433
+ "div",
434
+ {
435
+ className: ui.cn(
436
+ "shrink-0 rounded-md bg-muted px-3 py-2 text-sm leading-5 text-foreground",
437
+ className
438
+ ),
439
+ "aria-live": "polite",
440
+ "data-testid": "captions-overlay",
441
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-10 flex-col justify-end overflow-hidden", children: [
442
+ !showing && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-muted-foreground", "data-testid": "captions-waiting", children: waitingText }),
443
+ visible.map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
444
+ "p",
445
+ {
446
+ className: "flex min-w-0 shrink-0 gap-1.5",
447
+ "data-testid": "caption-line",
448
+ "data-speaker": c.speakerId,
449
+ children: [
450
+ /* @__PURE__ */ jsxRuntime.jsx(
451
+ "span",
452
+ {
453
+ className: ui.cn(
454
+ "max-w-[40%] shrink-0 truncate font-semibold",
455
+ chunkMWDW5UBW_cjs.speakerAccent(c.speakerId)
456
+ ),
457
+ children: c.name
458
+ }
459
+ ),
460
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: ": " }),
461
+ /* @__PURE__ */ jsxRuntime.jsx(
462
+ "span",
463
+ {
464
+ className: "flex min-w-0 flex-1 flex-col justify-end overflow-hidden",
465
+ style: { height: `${(visible.length > 1 ? 1 : chunkMWDW5UBW_cjs.CAPTION_LINES) * 1.25}rem` },
466
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "break-words", children: c.text })
467
+ }
468
+ )
469
+ ]
470
+ },
471
+ `${c.speakerId}:${c.id}`
472
+ ))
473
+ ] })
474
+ }
475
+ );
476
+ }
477
+ function CallControlBar({
478
+ left,
479
+ right,
480
+ children,
481
+ shortcuts = false,
482
+ className
483
+ }) {
484
+ const ref = react.useRef(null);
485
+ useShortcutKeys(ref, shortcuts);
486
+ return /* @__PURE__ */ jsxRuntime.jsxs(
487
+ "div",
488
+ {
489
+ ref,
490
+ className: ui.cn(
491
+ "grid h-16 shrink-0 grid-cols-[1fr_auto_1fr] items-center gap-2 border-t border-border bg-background px-3 sm:px-4",
492
+ className
493
+ ),
494
+ "data-testid": "call-controls",
495
+ children: [
496
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 items-center gap-2", children: left }),
497
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children }),
498
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end gap-2", children: right })
499
+ ]
500
+ }
501
+ );
502
+ }
503
+ function ControlGroup({ children }) {
504
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children });
505
+ }
506
+ function ControlDivider() {
507
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Separator, { orientation: "vertical", className: "mx-1 h-6" });
508
+ }
509
+ function ElapsedTime({ since, title }) {
510
+ const [now, setNow] = react.useState(() => Date.now());
511
+ react.useEffect(() => {
512
+ const id = setInterval(() => setNow(Date.now()), 1e3);
513
+ return () => clearInterval(id);
514
+ }, []);
515
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm tabular-nums text-muted-foreground", title, children: chunkMWDW5UBW_cjs.formatElapsed(now - since) });
516
+ }
517
+ function MediaControl({
518
+ kind,
519
+ enabled,
520
+ pending = false,
521
+ onToggle,
522
+ devices,
523
+ activeDeviceId,
524
+ onSelectDevice,
525
+ shortcut,
526
+ testId,
527
+ options = []
528
+ }) {
529
+ const mic = kind === "microphone";
530
+ const label = enabled ? `Turn off ${kind}` : `Turn on ${kind}`;
531
+ const Icon = mic ? enabled ? lucideReact.Mic : lucideReact.MicOff : enabled ? lucideReact.Video : lucideReact.VideoOff;
532
+ const title = mic ? "Microphone" : "Camera";
533
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center", role: "group", "aria-label": title, children: [
534
+ /* @__PURE__ */ jsxRuntime.jsx(
535
+ CallControlButton,
536
+ {
537
+ label,
538
+ shortcut,
539
+ tone: enabled ? "neutral" : "off",
540
+ "aria-pressed": enabled,
541
+ disabled: pending,
542
+ onClick: onToggle,
543
+ className: "rounded-r-none",
544
+ "data-testid": testId,
545
+ "data-call-shortcut": shortcut?.toLowerCase(),
546
+ "data-enabled": enabled,
547
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": true })
548
+ }
549
+ ),
550
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
551
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
552
+ ui.Button,
553
+ {
554
+ type: "button",
555
+ variant: "ghost",
556
+ "aria-label": `Choose ${kind}`,
557
+ title: `Choose ${kind}`,
558
+ className: ui.cn(
559
+ "h-9 w-6 shrink-0 rounded-l-none border-l px-0 pointer-coarse:h-11! pointer-coarse:w-8",
560
+ enabled ? "border-background bg-muted text-muted-foreground hover:bg-accent hover:text-foreground" : "border-background bg-red-bg text-red-text hover:bg-red-bg/80"
561
+ ),
562
+ "data-testid": `${testId}-devices`,
563
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "size-3.5", "aria-hidden": true })
564
+ }
565
+ ) }),
566
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuContent, { side: "top", align: "start", className: "w-72", children: [
567
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuLabel, { className: "text-xs font-medium text-muted-foreground", children: title }),
568
+ devices.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuItem, { disabled: true, children: [
569
+ "No ",
570
+ kind,
571
+ " found"
572
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuRadioGroup, { value: activeDeviceId, onValueChange: onSelectDevice, children: devices.map((d, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuRadioItem, { value: d.deviceId, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: d.label || `${title} ${i + 1}` }) }, d.deviceId)) }),
573
+ options.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuSeparator, {}),
574
+ options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
575
+ ui.DropdownMenuCheckboxItem,
576
+ {
577
+ checked: o.checked,
578
+ onCheckedChange: (c) => o.onChange(c === true),
579
+ "data-testid": o.testId,
580
+ children: o.label
581
+ },
582
+ o.label
583
+ ))
584
+ ] })
585
+ ] })
586
+ ] });
587
+ }
588
+ function RecordControl({
589
+ on,
590
+ busy = false,
591
+ modes,
592
+ note,
593
+ onStart,
594
+ onStop
595
+ }) {
596
+ const [choosing, setChoosing] = react.useState(false);
597
+ const menuRef = react.useRef(null);
598
+ react.useEffect(() => {
599
+ if (!choosing) return;
600
+ const onDown = (e) => {
601
+ if (!menuRef.current?.contains(e.target)) setChoosing(false);
602
+ };
603
+ const onKey = (e) => {
604
+ if (e.key === "Escape") setChoosing(false);
605
+ };
606
+ document.addEventListener("pointerdown", onDown);
607
+ document.addEventListener("keydown", onKey);
608
+ return () => {
609
+ document.removeEventListener("pointerdown", onDown);
610
+ document.removeEventListener("keydown", onKey);
611
+ };
612
+ }, [choosing]);
613
+ const base = "h-9 shrink-0 gap-2 px-3 pointer-coarse:h-11!";
614
+ if (on) {
615
+ return /* @__PURE__ */ jsxRuntime.jsxs(
616
+ ui.Button,
617
+ {
618
+ type: "button",
619
+ variant: "ghost",
620
+ className: ui.cn(base, "bg-red-bg text-red-text hover:bg-red-bg/80"),
621
+ disabled: busy,
622
+ onClick: onStop,
623
+ "data-testid": "recording-stop",
624
+ children: [
625
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex size-2.5", "aria-hidden": true, children: [
626
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex size-full animate-ping rounded-full bg-red-solid opacity-60 motion-reduce:hidden" }),
627
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex size-2.5 rounded-full bg-red-solid" })
628
+ ] }),
629
+ "Stop recording"
630
+ ]
631
+ }
632
+ );
633
+ }
634
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: menuRef, className: "relative", children: [
635
+ /* @__PURE__ */ jsxRuntime.jsxs(
636
+ ui.Button,
637
+ {
638
+ type: "button",
639
+ variant: "ghost",
640
+ className: ui.cn(base, "bg-muted text-foreground hover:bg-accent"),
641
+ disabled: busy,
642
+ "aria-expanded": choosing,
643
+ "aria-haspopup": "menu",
644
+ onClick: () => setChoosing((c) => !c),
645
+ "data-testid": "recording-start",
646
+ children: [
647
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { className: "size-3 fill-red-solid text-red-solid", "aria-hidden": true }),
648
+ "Record"
649
+ ]
650
+ }
651
+ ),
652
+ choosing && /* @__PURE__ */ jsxRuntime.jsxs(
653
+ "div",
654
+ {
655
+ role: "menu",
656
+ "aria-label": "Record",
657
+ className: "absolute bottom-full left-1/2 z-50 mb-2 w-64 -translate-x-1/2 rounded-md border bg-popover p-1 text-popover-foreground shadow-lg",
658
+ children: [
659
+ note && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-2 py-1.5 text-xs text-muted-foreground", children: note }),
660
+ modes.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
661
+ "button",
662
+ {
663
+ type: "button",
664
+ role: "menuitem",
665
+ className: "flex w-full flex-col items-start rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent focus-visible:bg-accent pointer-coarse:min-h-11",
666
+ onClick: () => {
667
+ setChoosing(false);
668
+ onStart(m.mode);
669
+ },
670
+ "data-testid": m.testId,
671
+ children: [
672
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: m.label }),
673
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: m.description })
674
+ ]
675
+ },
676
+ m.mode
677
+ ))
678
+ ]
679
+ }
680
+ )
681
+ ] });
682
+ }
683
+ function LeaveButton({
684
+ onLeave,
685
+ compact = false,
686
+ label = "Leave",
687
+ title = "Leave call",
688
+ options
689
+ }) {
690
+ const menu = !!options?.length;
691
+ const button = /* @__PURE__ */ jsxRuntime.jsxs(
692
+ ui.Button,
693
+ {
694
+ type: "button",
695
+ variant: "destructive",
696
+ size: compact ? "icon" : "default",
697
+ className: ui.cn("shrink-0", !compact && "px-4"),
698
+ "aria-label": title,
699
+ title,
700
+ onClick: menu ? void 0 : onLeave,
701
+ "data-testid": "call-leave",
702
+ children: [
703
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { className: "size-4", "aria-hidden": true }),
704
+ !compact && label
705
+ ]
706
+ }
707
+ );
708
+ if (!menu) return button;
709
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
710
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: button }),
711
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuContent, { align: "end", className: "w-56", children: [
712
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuItem, { onSelect: onLeave, "data-testid": "call-leave-only", children: [
713
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { "aria-hidden": true }),
714
+ label
715
+ ] }),
716
+ options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
717
+ ui.DropdownMenuItem,
718
+ {
719
+ onSelect: o.onSelect,
720
+ className: "text-red-text focus:text-red-text",
721
+ "data-testid": o.testId,
722
+ children: o.label
723
+ },
724
+ o.key
725
+ ))
726
+ ] })
727
+ ] });
728
+ }
729
+ var isSection = (g) => !Array.isArray(g);
730
+ function MoreMenu({
731
+ details,
732
+ groups,
733
+ label = "More options"
734
+ }) {
735
+ const shown = groups.filter((g) => isSection(g) || g.length > 0);
736
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
737
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(CallControlButton, { label, tooltip: false, "data-testid": "call-more", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { "aria-hidden": true }) }) }),
738
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuContent, { side: "top", align: "end", className: "w-72", "data-testid": "call-more-menu", children: [
739
+ details && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
740
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuLabel, { className: "flex flex-col gap-1 font-normal", children: details }),
741
+ shown.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuSeparator, {})
742
+ ] }),
743
+ shown.map((group, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "none", children: [
744
+ i > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuSeparator, {}),
745
+ isSection(group) ? group.content : group.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
746
+ ui.DropdownMenuItem,
747
+ {
748
+ onSelect: item.onSelect,
749
+ disabled: item.disabled,
750
+ "data-testid": item.testId,
751
+ children: [
752
+ item.icon,
753
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
754
+ item.trailing
755
+ ]
756
+ },
757
+ item.key
758
+ ))
759
+ ] }, isSection(group) ? group.key : group[0].key))
760
+ ] })
761
+ ] });
762
+ }
763
+ function HeaderIconButton({
764
+ label,
765
+ onClick,
766
+ children,
767
+ testId,
768
+ pressed
769
+ }) {
770
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Tooltip, { children: [
771
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
772
+ ui.Button,
773
+ {
774
+ type: "button",
775
+ variant: "ghost",
776
+ size: "icon",
777
+ className: "text-muted-foreground hover:text-foreground",
778
+ "aria-label": label,
779
+ "aria-pressed": pressed,
780
+ onClick,
781
+ "data-testid": testId,
782
+ children
783
+ }
784
+ ) }),
785
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipContent, { side: "bottom", children: label })
786
+ ] });
787
+ }
788
+ function CallHeader({
789
+ title,
790
+ icon,
791
+ status,
792
+ view,
793
+ onViewChange,
794
+ onMinimise,
795
+ minimiseLabel = "Minimise \u2014 keep working while the call goes on"
796
+ }) {
797
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "@container", children: /* @__PURE__ */ jsxRuntime.jsx(
798
+ chunk7RGLTF76_cjs.TabHeader,
799
+ {
800
+ tabs: [
801
+ {
802
+ id: "call",
803
+ label: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tooltip, { children: [
804
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipTrigger, { asChild: true, showWithText: true, children: /* @__PURE__ */ jsxRuntime.jsx(
805
+ "span",
806
+ {
807
+ className: "block min-w-0 max-w-[calc(100cqw-16rem)] truncate",
808
+ "data-testid": "call-title",
809
+ children: title
810
+ }
811
+ ) }),
812
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipContent, { side: "bottom", children: title })
813
+ ] }),
814
+ icon: icon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { className: "size-4" })
815
+ }
816
+ ],
817
+ activeTabId: "call",
818
+ onTabChange: () => void 0,
819
+ testId: "call-header",
820
+ className: "pb-0",
821
+ rightContent: (compact) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
822
+ typeof status === "function" ? status(compact) : status,
823
+ view && onViewChange && /* @__PURE__ */ jsxRuntime.jsx(
824
+ HeaderIconButton,
825
+ {
826
+ label: view === "speaker" ? "Gallery view (everyone equal)" : "Speaker view (who talks, large)",
827
+ onClick: () => onViewChange(view === "speaker" ? "gallery" : "speaker"),
828
+ testId: "call-view-toggle",
829
+ children: view === "speaker" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutGrid, { className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserRound, { className: "size-4" })
830
+ }
831
+ ),
832
+ onMinimise && /* @__PURE__ */ jsxRuntime.jsx(
833
+ HeaderIconButton,
834
+ {
835
+ label: minimiseLabel,
836
+ onClick: onMinimise,
837
+ testId: "call-dock-toggle",
838
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PictureInPicture2, { className: "size-4" })
839
+ }
840
+ )
841
+ ] })
842
+ }
843
+ ) });
844
+ }
845
+ function SpeakingNow({
846
+ names,
847
+ idleText = "No one is speaking",
848
+ className
849
+ }) {
850
+ const talking = names.length > 0;
851
+ return /* @__PURE__ */ jsxRuntime.jsxs(
852
+ "p",
853
+ {
854
+ className: ui.cn(
855
+ "flex min-w-0 items-center gap-1.5 text-sm",
856
+ talking ? "text-foreground" : "text-muted-foreground",
857
+ className
858
+ ),
859
+ "aria-live": "polite",
860
+ "data-testid": "call-speaking-now",
861
+ children: [
862
+ /* @__PURE__ */ jsxRuntime.jsx(
863
+ lucideReact.AudioLines,
864
+ {
865
+ className: ui.cn(
866
+ "size-4 shrink-0",
867
+ talking ? "text-theme-cyan motion-safe:animate-pulse" : "text-muted-foreground"
868
+ ),
869
+ "aria-hidden": true
870
+ }
871
+ ),
872
+ talking ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1 overflow-hidden", children: [
873
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: "Speaking:" }),
874
+ names.map((n, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex min-w-0 shrink-0 items-center", children: n }, i))
875
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: idleText })
876
+ ]
877
+ }
878
+ );
879
+ }
880
+ function CallNotice({ children }) {
881
+ return /* @__PURE__ */ jsxRuntime.jsxs(
882
+ "p",
883
+ {
884
+ className: "flex min-w-0 items-center gap-1.5 text-sm text-red-text motion-safe:animate-in motion-safe:fade-in-0",
885
+ role: "status",
886
+ "aria-live": "polite",
887
+ "data-testid": "call-notice",
888
+ children: [
889
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 shrink-0 rounded-full bg-red-solid", "aria-hidden": true }),
890
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children })
891
+ ]
892
+ }
893
+ );
894
+ }
895
+ function CallStageFrame({
896
+ header,
897
+ subheader,
898
+ stage,
899
+ stageRef,
900
+ stageOverlay,
901
+ people,
902
+ stageHeight,
903
+ controls,
904
+ className
905
+ }) {
906
+ const [bodyRef, body] = useElementSize();
907
+ const PAD_X = 24;
908
+ const PAD_B = 12;
909
+ const sized = !!people && !!stageHeight && body.width > 0 && body.height > 0 ? Math.ceil(stageHeight(body.width - PAD_X, Math.round(body.height * 0.62)) + PAD_B) : null;
910
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex size-full min-h-0 flex-col bg-background text-foreground", className), children: [
911
+ header,
912
+ subheader && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-9 shrink-0 flex-col justify-center gap-0.5 px-3 py-1", children: subheader }),
913
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: bodyRef, className: "relative flex min-h-0 flex-1 flex-col", children: [
914
+ /* @__PURE__ */ jsxRuntime.jsx(
915
+ "div",
916
+ {
917
+ className: ui.cn("flex min-h-0 flex-col px-3 pb-3", sized == null ? "flex-1" : "shrink-0"),
918
+ style: sized == null ? void 0 : { height: sized },
919
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: stageRef, className: "relative min-h-0 flex-1", "data-testid": "call-stage", children: [
920
+ stage,
921
+ stageOverlay
922
+ ] })
923
+ }
924
+ ),
925
+ people
926
+ ] }),
927
+ controls
928
+ ] });
929
+ }
930
+ function CallSidePanel({
931
+ title,
932
+ icon,
933
+ actions,
934
+ onClose,
935
+ closeLabel,
936
+ closeTestId,
937
+ tabs,
938
+ activeTab,
939
+ onTabChange,
940
+ children,
941
+ className,
942
+ ...rest
943
+ }) {
944
+ const tabbed = !!tabs?.length;
945
+ const current = activeTab ?? tabs?.[0]?.id;
946
+ const header = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border pl-3 pr-1.5", children: [
947
+ tabbed ? /* @__PURE__ */ jsxRuntime.jsx(ui.TabsList, { className: "h-8 min-w-0 flex-1 justify-start overflow-x-auto bg-transparent p-0", children: tabs.map((t) => /* @__PURE__ */ jsxRuntime.jsxs(
948
+ ui.TabsTrigger,
949
+ {
950
+ value: t.id,
951
+ className: "h-8 gap-1.5 px-2.5 data-[state=active]:bg-muted data-[state=active]:text-primary data-[state=active]:shadow-none",
952
+ "data-testid": t.testId,
953
+ children: [
954
+ t.label,
955
+ t.count !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs tabular-nums text-muted-foreground", children: t.count })
956
+ ]
957
+ },
958
+ t.id
959
+ )) }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-base font-semibold text-primary", children: [
960
+ icon,
961
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: title })
962
+ ] }),
963
+ actions,
964
+ /* @__PURE__ */ jsxRuntime.jsx(
965
+ ui.Button,
966
+ {
967
+ type: "button",
968
+ variant: "ghost",
969
+ size: "icon",
970
+ className: "size-8 text-muted-foreground hover:text-foreground",
971
+ "aria-label": closeLabel,
972
+ title: closeLabel,
973
+ onClick: onClose,
974
+ "data-testid": closeTestId,
975
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
976
+ }
977
+ )
978
+ ] });
979
+ return /* @__PURE__ */ jsxRuntime.jsx(
980
+ "aside",
981
+ {
982
+ className: ui.cn("flex min-h-0 flex-col bg-background text-foreground", className),
983
+ "aria-label": title,
984
+ ...rest,
985
+ children: tabbed ? /* @__PURE__ */ jsxRuntime.jsxs(
986
+ ui.Tabs,
987
+ {
988
+ value: current,
989
+ onValueChange: (id) => onTabChange?.(id),
990
+ className: "flex min-h-0 flex-1 flex-col",
991
+ children: [
992
+ header,
993
+ current && /* @__PURE__ */ jsxRuntime.jsx(ui.TabsContent, { value: current, className: "mt-0 flex min-h-0 flex-1 flex-col", children })
994
+ ]
995
+ }
996
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
997
+ header,
998
+ children
999
+ ] })
1000
+ }
1001
+ );
1002
+ }
1003
+ function VoiceMeter({
1004
+ level,
1005
+ muted = false,
1006
+ className
1007
+ }) {
1008
+ if (muted) {
1009
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("flex items-center text-red-text", className), title: "Microphone off", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { className: "size-4", "aria-label": "Microphone off" }) });
1010
+ }
1011
+ const lit = Math.round(Math.min(1, Math.max(0, level)) * 5);
1012
+ return /* @__PURE__ */ jsxRuntime.jsx(
1013
+ "span",
1014
+ {
1015
+ className: ui.cn("flex h-4 items-end gap-0.5", className),
1016
+ role: "meter",
1017
+ "aria-label": "Voice level",
1018
+ "aria-valuemin": 0,
1019
+ "aria-valuemax": 5,
1020
+ "aria-valuenow": lit,
1021
+ children: [0.35, 0.55, 0.75, 0.9, 1].map((h, i) => /* @__PURE__ */ jsxRuntime.jsx(
1022
+ "span",
1023
+ {
1024
+ className: ui.cn("w-1 rounded-sm", i < lit ? "bg-theme-cyan" : "bg-muted-foreground/30"),
1025
+ style: { height: `${h * 100}%` }
1026
+ },
1027
+ i
1028
+ ))
1029
+ }
1030
+ );
1031
+ }
1032
+ function CallPersonLabel({
1033
+ name,
1034
+ entity,
1035
+ isLocal = false,
1036
+ youLabel = "(me)"
1037
+ }) {
1038
+ if (entity) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: entity });
1039
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1040
+ /* @__PURE__ */ jsxRuntime.jsx(
1041
+ "span",
1042
+ {
1043
+ className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-xs font-semibold text-primary",
1044
+ "aria-hidden": true,
1045
+ children: chunkMWDW5UBW_cjs.initialsOf(name)
1046
+ }
1047
+ ),
1048
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate", children: [
1049
+ name,
1050
+ isLocal && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
1051
+ " ",
1052
+ youLabel
1053
+ ] })
1054
+ ] })
1055
+ ] });
1056
+ }
1057
+ function PersonRow({
1058
+ p,
1059
+ canRemove,
1060
+ removingId,
1061
+ onRemove,
1062
+ onAskToMute,
1063
+ onMute
1064
+ }) {
1065
+ const hasActions = !p.isLocal && (canRemove || (onAskToMute || onMute) && !p.micMuted || onMute && p.cameraOn);
1066
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1067
+ "li",
1068
+ {
1069
+ className: "flex h-10 items-center gap-2 rounded-md px-2 hover:bg-muted",
1070
+ "data-testid": `call-person-${p.id}`,
1071
+ "data-speaking": p.speaking || void 0,
1072
+ children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: [
1074
+ p.entity ? (
1075
+ // The ring hugs the person's pill, not the whole row.
1076
+ /* @__PURE__ */ jsxRuntime.jsx(
1077
+ "span",
1078
+ {
1079
+ className: ui.cn(
1080
+ "inline-flex min-w-0 rounded-full",
1081
+ p.speaking && "ring-2 ring-theme-cyan ring-offset-2 ring-offset-background"
1082
+ ),
1083
+ children: p.entity
1084
+ }
1085
+ )
1086
+ ) : /* @__PURE__ */ jsxRuntime.jsx(CallPersonLabel, { name: p.name, isLocal: p.isLocal }),
1087
+ p.speaking && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: " \u2014 speaking" }),
1088
+ p.note && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: p.note })
1089
+ ] }),
1090
+ /* @__PURE__ */ jsxRuntime.jsx(VoiceMeter, { level: p.level ?? 0, muted: p.micMuted }),
1091
+ /* @__PURE__ */ jsxRuntime.jsx(
1092
+ "span",
1093
+ {
1094
+ className: ui.cn(
1095
+ "flex w-4 justify-center",
1096
+ p.cameraOn ? "text-muted-foreground" : "text-red-text"
1097
+ ),
1098
+ title: p.cameraOn ? "Camera on" : "Camera off",
1099
+ children: p.cameraOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { className: "size-4", "aria-label": "Camera on" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { className: "size-4", "aria-label": "Camera off" })
1100
+ }
1101
+ ),
1102
+ hasActions ? /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
1103
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1104
+ ui.Button,
1105
+ {
1106
+ type: "button",
1107
+ variant: "ghost",
1108
+ size: "icon",
1109
+ className: "size-8 text-muted-foreground hover:text-foreground",
1110
+ "aria-label": `Actions for ${p.name}`,
1111
+ "data-testid": `call-person-menu-${p.id}`,
1112
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "size-4" })
1113
+ }
1114
+ ) }),
1115
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuContent, { align: "end", className: "w-56", children: [
1116
+ onAskToMute && !p.micMuted && /* @__PURE__ */ jsxRuntime.jsxs(
1117
+ ui.DropdownMenuItem,
1118
+ {
1119
+ onSelect: () => onAskToMute(p),
1120
+ "data-testid": `call-ask-mute-${p.id}`,
1121
+ children: [
1122
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { "aria-hidden": true }),
1123
+ "Ask to mute"
1124
+ ]
1125
+ }
1126
+ ),
1127
+ onMute && !p.micMuted && /* @__PURE__ */ jsxRuntime.jsxs(
1128
+ ui.DropdownMenuItem,
1129
+ {
1130
+ onSelect: () => onMute(p, "audio"),
1131
+ "data-testid": `call-mute-${p.id}`,
1132
+ children: [
1133
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { "aria-hidden": true }),
1134
+ "Mute"
1135
+ ]
1136
+ }
1137
+ ),
1138
+ onMute && p.cameraOn && /* @__PURE__ */ jsxRuntime.jsxs(
1139
+ ui.DropdownMenuItem,
1140
+ {
1141
+ onSelect: () => onMute(p, "video"),
1142
+ "data-testid": `call-camera-off-${p.id}`,
1143
+ children: [
1144
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { "aria-hidden": true }),
1145
+ "Turn off camera"
1146
+ ]
1147
+ }
1148
+ ),
1149
+ canRemove && /* @__PURE__ */ jsxRuntime.jsxs(
1150
+ ui.DropdownMenuItem,
1151
+ {
1152
+ onSelect: () => onRemove(p),
1153
+ disabled: removingId === p.id,
1154
+ className: "text-destructive focus:text-destructive",
1155
+ "data-testid": `call-remove-${p.id}`,
1156
+ children: [
1157
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserX, { "aria-hidden": true }),
1158
+ "Remove from call"
1159
+ ]
1160
+ }
1161
+ )
1162
+ ] })
1163
+ ] }) : (
1164
+ // Keeps every row's meter and icons in the same column.
1165
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-8 shrink-0", "aria-hidden": true })
1166
+ )
1167
+ ]
1168
+ }
1169
+ );
1170
+ }
1171
+ function CallParticipants({
1172
+ people,
1173
+ canRemove,
1174
+ removingId,
1175
+ onRemove,
1176
+ onAskToMute,
1177
+ onMute,
1178
+ compact = false,
1179
+ className
1180
+ }) {
1181
+ const [expanded, setExpanded] = react.useState(false);
1182
+ const rows = /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "min-h-0 flex-1 overflow-y-auto px-1 pb-2", "data-testid": "call-people-list", children: people.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
1183
+ PersonRow,
1184
+ {
1185
+ p,
1186
+ canRemove,
1187
+ removingId,
1188
+ onRemove,
1189
+ onAskToMute,
1190
+ onMute
1191
+ },
1192
+ p.id
1193
+ )) });
1194
+ const heading = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [
1195
+ "People (",
1196
+ people.length,
1197
+ ")"
1198
+ ] });
1199
+ if (compact && !expanded) {
1200
+ const talking = people.filter((p) => p.speaking);
1201
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1202
+ "section",
1203
+ {
1204
+ className: ui.cn(
1205
+ "flex h-11 shrink-0 items-center gap-2 border-t border-border px-3",
1206
+ className
1207
+ ),
1208
+ "aria-label": "People",
1209
+ "data-testid": "call-people",
1210
+ "data-compact": "",
1211
+ children: [
1212
+ heading,
1213
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex min-w-0 flex-1 items-center gap-1 overflow-hidden", children: talking.map((p) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center", children: p.entity ?? p.name }, p.id)) }),
1214
+ /* @__PURE__ */ jsxRuntime.jsxs(
1215
+ ui.Button,
1216
+ {
1217
+ type: "button",
1218
+ variant: "ghost",
1219
+ size: "sm",
1220
+ className: "h-8 shrink-0",
1221
+ "aria-expanded": false,
1222
+ onClick: () => setExpanded(true),
1223
+ "data-testid": "call-people-show-all",
1224
+ children: [
1225
+ "Show all (",
1226
+ people.length,
1227
+ ")"
1228
+ ]
1229
+ }
1230
+ )
1231
+ ]
1232
+ }
1233
+ );
1234
+ }
1235
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1236
+ "section",
1237
+ {
1238
+ className: ui.cn(
1239
+ "flex min-h-0 flex-col border-t border-border",
1240
+ compact && "absolute inset-x-0 bottom-0 z-20 max-h-[80%] bg-background shadow-lg",
1241
+ className
1242
+ ),
1243
+ "aria-label": "People",
1244
+ "data-testid": "call-people",
1245
+ children: [
1246
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-9 shrink-0 items-center justify-between px-3", children: [
1247
+ heading,
1248
+ compact && /* @__PURE__ */ jsxRuntime.jsx(
1249
+ ui.Button,
1250
+ {
1251
+ type: "button",
1252
+ variant: "ghost",
1253
+ size: "sm",
1254
+ className: "h-7",
1255
+ "aria-expanded": true,
1256
+ onClick: () => setExpanded(false),
1257
+ "data-testid": "call-people-show-less",
1258
+ children: "Show less"
1259
+ }
1260
+ )
1261
+ ] }),
1262
+ rows
1263
+ ]
1264
+ }
1265
+ );
1266
+ }
1267
+ function LobbyRequests({
1268
+ requests,
1269
+ onDecide
1270
+ }) {
1271
+ if (!requests.length) return null;
1272
+ return /* @__PURE__ */ jsxRuntime.jsx(
1273
+ "div",
1274
+ {
1275
+ className: "absolute left-3 top-3 z-10 flex w-72 max-w-[calc(100%-1.5rem)] flex-col gap-2",
1276
+ role: "region",
1277
+ "aria-label": "Lobby",
1278
+ "data-testid": "call-lobby",
1279
+ children: requests.map((k) => /* @__PURE__ */ jsxRuntime.jsxs(
1280
+ "div",
1281
+ {
1282
+ className: "flex items-center gap-3 rounded-md border border-border bg-popover p-3 text-sm text-popover-foreground shadow-lg",
1283
+ "data-testid": "call-lobby-knock",
1284
+ children: [
1285
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-1", children: [
1286
+ k.entity ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: k.name }),
1287
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "wants to join." })
1288
+ ] }),
1289
+ /* @__PURE__ */ jsxRuntime.jsx(
1290
+ ui.Button,
1291
+ {
1292
+ type: "button",
1293
+ variant: "ghost",
1294
+ size: "sm",
1295
+ className: "h-8",
1296
+ onClick: () => onDecide(k.id, false),
1297
+ "data-testid": "call-lobby-deny",
1298
+ children: "Deny"
1299
+ }
1300
+ ),
1301
+ /* @__PURE__ */ jsxRuntime.jsx(
1302
+ ui.Button,
1303
+ {
1304
+ type: "button",
1305
+ size: "sm",
1306
+ className: "h-8",
1307
+ onClick: () => onDecide(k.id, true),
1308
+ "data-testid": "call-lobby-admit",
1309
+ children: "Admit"
1310
+ }
1311
+ )
1312
+ ]
1313
+ },
1314
+ k.id
1315
+ ))
1316
+ }
1317
+ );
1318
+ }
1319
+ function MinimisedCall({
1320
+ title,
1321
+ video,
1322
+ status,
1323
+ speaking,
1324
+ micEnabled,
1325
+ onToggleMic,
1326
+ cameraEnabled,
1327
+ onToggleCamera,
1328
+ onRestore,
1329
+ onLeave,
1330
+ restoreLabel = "Back to the call",
1331
+ shortcuts = true,
1332
+ className
1333
+ }) {
1334
+ const ref = react.useRef(null);
1335
+ useShortcutKeys(ref, shortcuts);
1336
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("flex flex-col", className), "data-testid": "call-minimised", children: [
1337
+ /* @__PURE__ */ jsxRuntime.jsx(
1338
+ "button",
1339
+ {
1340
+ type: "button",
1341
+ className: "group relative aspect-video w-full overflow-hidden rounded-t-md bg-hero focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
1342
+ onClick: onRestore,
1343
+ "aria-label": restoreLabel,
1344
+ "data-testid": "call-minimised-restore",
1345
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "call-tile-box absolute inset-0", children: video ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "call-tile", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "call-tile-placeholder", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "call-tile-avatar call-tile-avatar-sm", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { className: "size-4", "aria-hidden": true }) }) }) }) })
1346
+ }
1347
+ ),
1348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-3 py-2", children: [
1349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
1350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", title, children: title }),
1351
+ status && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center gap-1", children: status })
1352
+ ] }),
1353
+ speaking,
1354
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1355
+ onToggleMic && micEnabled !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
1356
+ CallControlButton,
1357
+ {
1358
+ label: micEnabled ? "Turn off microphone" : "Turn on microphone",
1359
+ shortcut: "M",
1360
+ tone: micEnabled ? "neutral" : "off",
1361
+ "aria-pressed": micEnabled,
1362
+ onClick: onToggleMic,
1363
+ className: "size-8",
1364
+ "data-testid": "call-minimised-mic",
1365
+ "data-call-shortcut": "m",
1366
+ children: micEnabled ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { "aria-hidden": true })
1367
+ }
1368
+ ),
1369
+ onToggleCamera && cameraEnabled !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
1370
+ CallControlButton,
1371
+ {
1372
+ label: cameraEnabled ? "Turn off camera" : "Turn on camera",
1373
+ shortcut: "V",
1374
+ tone: cameraEnabled ? "neutral" : "off",
1375
+ "aria-pressed": cameraEnabled,
1376
+ onClick: onToggleCamera,
1377
+ className: "size-8",
1378
+ "data-testid": "call-minimised-camera",
1379
+ "data-call-shortcut": "v",
1380
+ children: cameraEnabled ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { "aria-hidden": true })
1381
+ }
1382
+ ),
1383
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1" }),
1384
+ /* @__PURE__ */ jsxRuntime.jsx(
1385
+ CallControlButton,
1386
+ {
1387
+ label: restoreLabel,
1388
+ onClick: onRestore,
1389
+ className: "size-8",
1390
+ "data-testid": "call-dock-expand",
1391
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { "aria-hidden": true })
1392
+ }
1393
+ ),
1394
+ /* @__PURE__ */ jsxRuntime.jsx(
1395
+ CallControlButton,
1396
+ {
1397
+ label: "Leave call",
1398
+ tone: "danger",
1399
+ onClick: onLeave,
1400
+ className: "size-8",
1401
+ "data-testid": "call-dock-leave",
1402
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { "aria-hidden": true })
1403
+ }
1404
+ )
1405
+ ] })
1406
+ ] })
1407
+ ] });
1408
+ }
1409
+ function IncomingCallCard({
1410
+ callerName,
1411
+ caller,
1412
+ context,
1413
+ busy = false,
1414
+ muted,
1415
+ onMutedChange,
1416
+ onAccept,
1417
+ onDecline,
1418
+ className
1419
+ }) {
1420
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1421
+ "div",
1422
+ {
1423
+ role: "alertdialog",
1424
+ "aria-labelledby": "incoming-call-title",
1425
+ "aria-describedby": "incoming-call-desc",
1426
+ "data-testid": "incoming-call-dialog",
1427
+ className: ui.cn(
1428
+ "w-80 max-w-[calc(100vw-2rem)] rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-xl motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-top-2",
1429
+ className
1430
+ ),
1431
+ children: [
1432
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
1433
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex size-10 shrink-0", "aria-hidden": true, children: [
1434
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 rounded-full bg-primary/20 motion-safe:animate-ping" }),
1435
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative flex size-10 items-center justify-center rounded-full bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { className: "size-5" }) })
1436
+ ] }),
1437
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 pt-0.5", children: [
1438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { id: "incoming-call-title", className: "flex min-w-0 text-sm font-semibold", children: [
1439
+ caller ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: callerName }),
1440
+ caller && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: callerName })
1441
+ ] }),
1442
+ /* @__PURE__ */ jsxRuntime.jsx("p", { id: "incoming-call-desc", className: "text-xs text-muted-foreground", children: context })
1443
+ ] }),
1444
+ /* @__PURE__ */ jsxRuntime.jsx(
1445
+ ui.Button,
1446
+ {
1447
+ type: "button",
1448
+ variant: "ghost",
1449
+ size: "icon",
1450
+ className: "-mr-1 -mt-1 size-8 shrink-0 text-muted-foreground hover:text-foreground",
1451
+ "aria-pressed": muted,
1452
+ "aria-label": muted ? "Unmute ringtone" : "Mute ringtone",
1453
+ title: muted ? "Unmute ringtone" : "Mute ringtone",
1454
+ onClick: () => onMutedChange(!muted),
1455
+ "data-testid": "incoming-call-mute",
1456
+ children: muted ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BellOff, { className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bell, { className: "size-4" })
1457
+ }
1458
+ )
1459
+ ] }),
1460
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 grid grid-cols-2 gap-2", children: [
1461
+ /* @__PURE__ */ jsxRuntime.jsxs(
1462
+ ui.Button,
1463
+ {
1464
+ type: "button",
1465
+ variant: "destructive",
1466
+ onClick: onDecline,
1467
+ disabled: busy,
1468
+ "data-testid": "incoming-call-decline",
1469
+ children: [
1470
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { className: "size-4", "aria-hidden": true }),
1471
+ "Decline"
1472
+ ]
1473
+ }
1474
+ ),
1475
+ /* @__PURE__ */ jsxRuntime.jsxs(
1476
+ ui.Button,
1477
+ {
1478
+ type: "button",
1479
+ onClick: onAccept,
1480
+ disabled: busy,
1481
+ "data-testid": "incoming-call-accept",
1482
+ autoFocus: true,
1483
+ children: [
1484
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { className: "size-4", "aria-hidden": true }),
1485
+ "Accept"
1486
+ ]
1487
+ }
1488
+ )
1489
+ ] })
1490
+ ]
1491
+ }
1492
+ );
1493
+ }
1494
+ function CallStateMessage({
1495
+ title,
1496
+ description,
1497
+ spinner = true,
1498
+ icon,
1499
+ action,
1500
+ children,
1501
+ testId
1502
+ }) {
1503
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1504
+ "div",
1505
+ {
1506
+ className: "flex size-full flex-col items-center justify-center gap-3 p-6 text-center",
1507
+ role: "status",
1508
+ "data-testid": testId,
1509
+ children: [
1510
+ icon ?? (spinner && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-6 animate-spin text-muted-foreground", "aria-hidden": true })),
1511
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base font-semibold", children: title }),
1512
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: description }),
1513
+ children,
1514
+ action
1515
+ ]
1516
+ }
1517
+ );
1518
+ }
1519
+ function ConnectionOverlay({ label }) {
1520
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-md bg-background/70 text-sm text-muted-foreground", children: [
1521
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": true }),
1522
+ label
1523
+ ] });
1524
+ }
1525
+
1526
+ // src/calls/join-choices.ts
1527
+ var DEFAULT_JOIN_CHOICES = { audioEnabled: true, videoEnabled: false };
1528
+ function describeMediaError(err, kind) {
1529
+ const Kind = kind === "camera" ? "Camera" : "Microphone";
1530
+ const name = err?.name;
1531
+ if (name === "NotAllowedError" || name === "SecurityError")
1532
+ return `${Kind} access is blocked. Allow it in the browser's site settings and try again.`;
1533
+ if (name === "NotFoundError" || name === "OverconstrainedError") return `No ${kind} was found.`;
1534
+ if (name === "NotReadableError") return `${Kind} is in use by another app.`;
1535
+ return `${Kind} could not start${err?.message ? `: ${err.message}` : "."}`;
1536
+ }
1537
+ function useDevices(kind, refreshKey) {
1538
+ const [devices, setDevices] = react.useState([]);
1539
+ react.useEffect(() => {
1540
+ const md = navigator.mediaDevices;
1541
+ if (!md?.enumerateDevices) return;
1542
+ let cancelled = false;
1543
+ const load = () => md.enumerateDevices().then((all) => {
1544
+ if (!cancelled) setDevices(all.filter((d) => d.kind === kind && d.deviceId));
1545
+ }).catch(() => {
1546
+ });
1547
+ void load();
1548
+ md.addEventListener?.("devicechange", load);
1549
+ return () => {
1550
+ cancelled = true;
1551
+ md.removeEventListener?.("devicechange", load);
1552
+ };
1553
+ }, [kind, refreshKey]);
1554
+ return devices;
1555
+ }
1556
+ function usePreviewStream(kind, enabled, deviceId, describe) {
1557
+ const [stream, setStream] = react.useState(null);
1558
+ const [error, setError] = react.useState(null);
1559
+ const describeRef = react.useRef(describe);
1560
+ describeRef.current = describe;
1561
+ react.useEffect(() => {
1562
+ if (!enabled) {
1563
+ setStream(null);
1564
+ return;
1565
+ }
1566
+ const md = navigator.mediaDevices;
1567
+ if (!md?.getUserMedia) {
1568
+ setError(
1569
+ window.isSecureContext ? "This browser cannot access the camera or microphone." : "Camera and microphone need HTTPS (or localhost)."
1570
+ );
1571
+ return;
1572
+ }
1573
+ let cancelled = false;
1574
+ let opened = null;
1575
+ const constraint = deviceId ? { deviceId: { exact: deviceId } } : true;
1576
+ md.getUserMedia(kind === "videoinput" ? { video: constraint } : { audio: constraint }).then((s) => {
1577
+ opened = s;
1578
+ if (cancelled) s.getTracks().forEach((t) => t.stop());
1579
+ else {
1580
+ setError(null);
1581
+ setStream(s);
1582
+ }
1583
+ }).catch((err) => {
1584
+ if (!cancelled)
1585
+ setError(describeRef.current(err, kind === "videoinput" ? "camera" : "microphone"));
1586
+ });
1587
+ return () => {
1588
+ cancelled = true;
1589
+ opened?.getTracks().forEach((t) => t.stop());
1590
+ setStream(null);
1591
+ };
1592
+ }, [kind, enabled, deviceId]);
1593
+ return { stream, error };
1594
+ }
1595
+ function useAudioLevel(stream) {
1596
+ const [level, setLevel] = react.useState(0);
1597
+ react.useEffect(() => {
1598
+ if (!stream || typeof AudioContext === "undefined") {
1599
+ setLevel(0);
1600
+ return;
1601
+ }
1602
+ let ctx;
1603
+ try {
1604
+ ctx = new AudioContext();
1605
+ } catch {
1606
+ return;
1607
+ }
1608
+ const analyser = ctx.createAnalyser();
1609
+ analyser.fftSize = 512;
1610
+ ctx.createMediaStreamSource(stream).connect(analyser);
1611
+ const buf = new Uint8Array(analyser.fftSize);
1612
+ let frame = 0;
1613
+ const tick = () => {
1614
+ analyser.getByteTimeDomainData(buf);
1615
+ let peak = 0;
1616
+ for (const v of buf) peak = Math.max(peak, Math.abs(v - 128));
1617
+ setLevel(Math.min(1, peak / 64));
1618
+ frame = requestAnimationFrame(tick);
1619
+ };
1620
+ void ctx.resume().catch(() => {
1621
+ });
1622
+ tick();
1623
+ return () => {
1624
+ cancelAnimationFrame(frame);
1625
+ void ctx.close().catch(() => {
1626
+ });
1627
+ };
1628
+ }, [stream]);
1629
+ return level;
1630
+ }
1631
+ function DeviceSelect({
1632
+ devices,
1633
+ value,
1634
+ onChange,
1635
+ label,
1636
+ icon,
1637
+ disabled,
1638
+ testId
1639
+ }) {
1640
+ const current = value ?? devices[0]?.deviceId;
1641
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [
1642
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5 text-xs font-medium text-muted-foreground [&_svg]:size-3.5", children: [
1643
+ icon,
1644
+ label
1645
+ ] }),
1646
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: current, onValueChange: onChange, disabled: disabled || devices.length < 2, children: [
1647
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { "aria-label": label, className: "h-9 min-w-0 text-sm", "data-testid": testId, children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, { placeholder: `Default ${label.toLowerCase()}` }) }),
1648
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: devices.map((d, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: d.deviceId, children: d.label || `${label} ${i + 1}` }, d.deviceId)) })
1649
+ ] })
1650
+ ] });
1651
+ }
1652
+ function PreJoin({
1653
+ title,
1654
+ heading = "Ready to join?",
1655
+ headingAs: Heading = "h2",
1656
+ children,
1657
+ joinLabel = "Join",
1658
+ cancelLabel = "Cancel",
1659
+ busy = false,
1660
+ error,
1661
+ onJoin,
1662
+ onCancel,
1663
+ describeError = describeMediaError,
1664
+ mirror = true,
1665
+ className
1666
+ }) {
1667
+ const [choices, setChoices] = react.useState(DEFAULT_JOIN_CHOICES);
1668
+ const video = usePreviewStream(
1669
+ "videoinput",
1670
+ choices.videoEnabled,
1671
+ choices.videoDeviceId,
1672
+ describeError
1673
+ );
1674
+ const audio = usePreviewStream(
1675
+ "audioinput",
1676
+ choices.audioEnabled,
1677
+ choices.audioDeviceId,
1678
+ describeError
1679
+ );
1680
+ const level = useAudioLevel(audio.stream);
1681
+ const cameras = useDevices("videoinput", video.stream);
1682
+ const mics = useDevices("audioinput", audio.stream);
1683
+ const videoRef = react.useRef(null);
1684
+ react.useEffect(() => {
1685
+ if (videoRef.current) videoRef.current.srcObject = video.stream;
1686
+ }, [video.stream]);
1687
+ const set = (patch) => setChoices((c) => ({ ...c, ...patch }));
1688
+ const deviceError = video.error ?? audio.error;
1689
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("@container flex w-full", className), "data-testid": "call-prejoin", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto grid w-full max-w-4xl items-center gap-6 @2xl:grid-cols-[3fr_2fr] @2xl:gap-8", children: [
1690
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
1691
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative aspect-video w-full overflow-hidden rounded-md bg-hero", children: choices.videoEnabled && video.stream ? /* @__PURE__ */ jsxRuntime.jsx(
1692
+ "video",
1693
+ {
1694
+ ref: videoRef,
1695
+ autoPlay: true,
1696
+ muted: true,
1697
+ playsInline: true,
1698
+ className: ui.cn("size-full object-cover", mirror && "-scale-x-100"),
1699
+ "data-testid": "call-prejoin-video"
1700
+ }
1701
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-full items-center justify-center text-sm text-white/80", children: choices.videoEnabled ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-5 animate-spin", "aria-label": "Starting camera" }) : "Camera is off" }) }),
1702
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-3", children: [
1703
+ /* @__PURE__ */ jsxRuntime.jsx(
1704
+ CallControlButton,
1705
+ {
1706
+ label: choices.audioEnabled ? "Turn off microphone" : "Turn on microphone",
1707
+ tone: choices.audioEnabled ? "neutral" : "off",
1708
+ "aria-pressed": choices.audioEnabled,
1709
+ onClick: () => set({ audioEnabled: !choices.audioEnabled }),
1710
+ "data-testid": "call-prejoin-mic-toggle",
1711
+ children: choices.audioEnabled ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { "aria-hidden": true })
1712
+ }
1713
+ ),
1714
+ /* @__PURE__ */ jsxRuntime.jsx(
1715
+ CallControlButton,
1716
+ {
1717
+ label: choices.videoEnabled ? "Turn off camera" : "Turn on camera",
1718
+ tone: choices.videoEnabled ? "neutral" : "off",
1719
+ "aria-pressed": choices.videoEnabled,
1720
+ onClick: () => set({ videoEnabled: !choices.videoEnabled }),
1721
+ "data-testid": "call-prejoin-camera-toggle",
1722
+ children: choices.videoEnabled ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { "aria-hidden": true })
1723
+ }
1724
+ )
1725
+ ] })
1726
+ ] }),
1727
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [
1728
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
1729
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { className: "text-xl font-semibold", children: heading }),
1730
+ title && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-muted-foreground", children: title })
1731
+ ] }),
1732
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
1733
+ /* @__PURE__ */ jsxRuntime.jsx(
1734
+ DeviceSelect,
1735
+ {
1736
+ devices: mics,
1737
+ value: choices.audioDeviceId,
1738
+ onChange: (id) => set({ audioDeviceId: id }),
1739
+ label: "Microphone",
1740
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, {}),
1741
+ disabled: !choices.audioEnabled,
1742
+ testId: "call-prejoin-mic-select"
1743
+ }
1744
+ ),
1745
+ /* @__PURE__ */ jsxRuntime.jsx(
1746
+ "div",
1747
+ {
1748
+ className: "h-1.5 w-full overflow-hidden rounded-full bg-muted",
1749
+ role: "meter",
1750
+ "aria-label": "Microphone level",
1751
+ "aria-valuemin": 0,
1752
+ "aria-valuemax": 100,
1753
+ "aria-valuenow": Math.round(level * 100),
1754
+ "data-testid": "call-prejoin-mic-level",
1755
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1756
+ "div",
1757
+ {
1758
+ className: "h-full bg-primary transition-[width] duration-75 motion-reduce:transition-none",
1759
+ style: { width: `${Math.round(level * 100)}%` }
1760
+ }
1761
+ )
1762
+ }
1763
+ ),
1764
+ /* @__PURE__ */ jsxRuntime.jsx(
1765
+ DeviceSelect,
1766
+ {
1767
+ devices: cameras,
1768
+ value: choices.videoDeviceId,
1769
+ onChange: (id) => set({ videoDeviceId: id }),
1770
+ label: "Camera",
1771
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, {}),
1772
+ disabled: !choices.videoEnabled,
1773
+ testId: "call-prejoin-camera-select"
1774
+ }
1775
+ )
1776
+ ] }),
1777
+ children,
1778
+ (error || deviceError) && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-sm text-destructive", "data-testid": "call-prejoin-error", children: error ?? deviceError }),
1779
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1780
+ /* @__PURE__ */ jsxRuntime.jsxs(
1781
+ ui.Button,
1782
+ {
1783
+ type: "button",
1784
+ className: "flex-1",
1785
+ disabled: busy,
1786
+ onClick: () => onJoin(choices),
1787
+ "data-testid": "call-prejoin-join",
1788
+ children: [
1789
+ busy && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
1790
+ joinLabel
1791
+ ]
1792
+ }
1793
+ ),
1794
+ onCancel && /* @__PURE__ */ jsxRuntime.jsx(
1795
+ ui.Button,
1796
+ {
1797
+ type: "button",
1798
+ variant: "outline",
1799
+ onClick: onCancel,
1800
+ disabled: busy,
1801
+ "data-testid": "call-prejoin-cancel",
1802
+ children: cancelLabel
1803
+ }
1804
+ )
1805
+ ] })
1806
+ ] })
1807
+ ] }) });
1808
+ }
1809
+ function WebinarWaiting({
1810
+ variant,
1811
+ title,
1812
+ when,
1813
+ host,
1814
+ onLeave,
1815
+ leaveLabel = "Leave"
1816
+ }) {
1817
+ const waiting = variant === "waiting";
1818
+ return /* @__PURE__ */ jsxRuntime.jsx(
1819
+ CallStateMessage,
1820
+ {
1821
+ title: waiting ? "Starting soon" : "This webinar has ended",
1822
+ description: waiting ? "The host hasn't gone live yet. You'll join automatically when the webinar starts." : "Thanks for attending.",
1823
+ spinner: waiting,
1824
+ icon: waiting ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarCheck, { className: "size-6 text-muted-foreground", "aria-hidden": true }),
1825
+ testId: waiting ? "webinar-waiting" : "webinar-ended",
1826
+ action: onLeave && /* @__PURE__ */ jsxRuntime.jsx(
1827
+ ui.Button,
1828
+ {
1829
+ type: "button",
1830
+ variant: "outline",
1831
+ onClick: onLeave,
1832
+ "data-testid": "webinar-waiting-leave",
1833
+ children: leaveLabel
1834
+ }
1835
+ ),
1836
+ children: (title || when || host) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-w-sm flex-col items-center gap-1 text-sm", children: [
1837
+ title && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", "data-testid": "webinar-waiting-title", children: title }),
1838
+ when && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", "data-testid": "webinar-waiting-when", children: when }),
1839
+ host && /* @__PURE__ */ jsxRuntime.jsxs(
1840
+ "p",
1841
+ {
1842
+ className: "flex flex-wrap items-center justify-center gap-1 text-muted-foreground",
1843
+ "data-testid": "webinar-waiting-host",
1844
+ children: [
1845
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Hosted by" }),
1846
+ host
1847
+ ]
1848
+ }
1849
+ )
1850
+ ] })
1851
+ }
1852
+ );
1853
+ }
1854
+ function usePending() {
1855
+ const [keys, setKeys] = react.useState(() => /* @__PURE__ */ new Set());
1856
+ const mounted = react.useRef(true);
1857
+ react.useEffect(() => {
1858
+ mounted.current = true;
1859
+ return () => {
1860
+ mounted.current = false;
1861
+ };
1862
+ }, []);
1863
+ const set = react.useCallback((key, on) => {
1864
+ if (!mounted.current) return;
1865
+ setKeys((prev) => {
1866
+ if (prev.has(key) === on) return prev;
1867
+ const next = new Set(prev);
1868
+ if (on) next.add(key);
1869
+ else next.delete(key);
1870
+ return next;
1871
+ });
1872
+ }, []);
1873
+ const run = react.useCallback(
1874
+ async (key, fn) => {
1875
+ let result;
1876
+ try {
1877
+ result = fn();
1878
+ } catch {
1879
+ return false;
1880
+ }
1881
+ if (!result || typeof result.then !== "function") return true;
1882
+ set(key, true);
1883
+ try {
1884
+ await result;
1885
+ return true;
1886
+ } catch {
1887
+ return false;
1888
+ } finally {
1889
+ set(key, false);
1890
+ }
1891
+ },
1892
+ [set]
1893
+ );
1894
+ const isPending = react.useCallback((key) => keys.has(key), [keys]);
1895
+ return { isPending, run };
1896
+ }
1897
+ function useNow(ms = 3e4, fixed) {
1898
+ const [now, setNow] = react.useState(() => /* @__PURE__ */ new Date());
1899
+ const ticking = !fixed;
1900
+ react.useEffect(() => {
1901
+ if (!ticking) return;
1902
+ const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), ms);
1903
+ return () => clearInterval(id);
1904
+ }, [ms, ticking]);
1905
+ return fixed ?? now;
1906
+ }
1907
+ function WebinarLiveChip({
1908
+ compact = false,
1909
+ className
1910
+ }) {
1911
+ return /* @__PURE__ */ jsxRuntime.jsx(
1912
+ CallStatusChip,
1913
+ {
1914
+ tone: "red",
1915
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "size-3.5 shrink-0", "aria-hidden": true }),
1916
+ label: "Live",
1917
+ compact,
1918
+ title: "Live: attendees see and hear the stage",
1919
+ role: "status",
1920
+ className,
1921
+ testId: "webinar-live-chip"
1922
+ }
1923
+ );
1924
+ }
1925
+ function WebinarLiveControl({
1926
+ live,
1927
+ onGoLive,
1928
+ onEnd,
1929
+ compact = false,
1930
+ className
1931
+ }) {
1932
+ const { isPending, run } = usePending();
1933
+ const [confirming, setConfirming] = react.useState(false);
1934
+ if (!live) {
1935
+ if (!onGoLive) return null;
1936
+ const busy = isPending("live");
1937
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1938
+ ui.Button,
1939
+ {
1940
+ type: "button",
1941
+ size: compact ? "icon" : "sm",
1942
+ className: ui.cn("h-8 shrink-0", !compact && "px-3", className),
1943
+ disabled: busy,
1944
+ "aria-label": "Go live",
1945
+ title: "Go live: let attendees see and hear the stage",
1946
+ onClick: () => void run("live", onGoLive),
1947
+ "data-testid": "webinar-go-live",
1948
+ children: [
1949
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "size-4", "aria-hidden": true }),
1950
+ !compact && "Go live"
1951
+ ]
1952
+ }
1953
+ );
1954
+ }
1955
+ if (!onEnd) return null;
1956
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1957
+ /* @__PURE__ */ jsxRuntime.jsxs(
1958
+ ui.Button,
1959
+ {
1960
+ type: "button",
1961
+ variant: "outline",
1962
+ size: compact ? "icon" : "default",
1963
+ className: ui.cn(
1964
+ "shrink-0 border-red-border text-red-text hover:bg-red-bg hover:text-red-text",
1965
+ !compact && "px-4",
1966
+ className
1967
+ ),
1968
+ "aria-label": "End webinar",
1969
+ title: "End webinar for everyone",
1970
+ onClick: () => setConfirming(true),
1971
+ "data-testid": "webinar-end",
1972
+ children: [
1973
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SquareX, { className: "size-4", "aria-hidden": true }),
1974
+ !compact && "End webinar"
1975
+ ]
1976
+ }
1977
+ ),
1978
+ /* @__PURE__ */ jsxRuntime.jsx(WebinarEndConfirm, { open: confirming, onOpenChange: setConfirming, onEnd })
1979
+ ] });
1980
+ }
1981
+ function WebinarEndConfirm({
1982
+ open,
1983
+ onOpenChange,
1984
+ onEnd
1985
+ }) {
1986
+ const { isPending, run } = usePending();
1987
+ const ending = isPending("end");
1988
+ return /* @__PURE__ */ jsxRuntime.jsx(
1989
+ chunkGICUGQNN_cjs.ConfirmDialog,
1990
+ {
1991
+ isOpen: open,
1992
+ onClose: () => onOpenChange(false),
1993
+ onConfirm: () => void run("end", onEnd).then((ok) => {
1994
+ if (ok) onOpenChange(false);
1995
+ }),
1996
+ title: "End the webinar?",
1997
+ description: "Everyone leaves, attendees and panelists alike. The stage closes and cannot be reopened in this session.",
1998
+ confirmText: "End webinar",
1999
+ isLoading: ending,
2000
+ testId: "webinar-end-confirm",
2001
+ testIds: { cancel: "webinar-end-cancel", confirm: "webinar-end-confirm-button" }
2002
+ }
2003
+ );
2004
+ }
2005
+ function BackstageBanner({
2006
+ onGoLive,
2007
+ className
2008
+ }) {
2009
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2010
+ "div",
2011
+ {
2012
+ className: ui.cn(
2013
+ "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",
2014
+ className
2015
+ ),
2016
+ role: "status",
2017
+ "data-testid": "webinar-backstage",
2018
+ children: [
2019
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: "size-4 shrink-0", "aria-hidden": true }),
2020
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
2021
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Backstage" }),
2022
+ " \u2014 attendees can't see or hear you yet",
2023
+ !onGoLive && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-yellow-text/80", children: ". The host goes live when ready." })
2024
+ ] }),
2025
+ onGoLive && /* @__PURE__ */ jsxRuntime.jsx(WebinarLiveControl, { live: false, onGoLive })
2026
+ ]
2027
+ }
2028
+ );
2029
+ }
2030
+ function RaiseHandButton({
2031
+ raised,
2032
+ onToggle,
2033
+ pending = false,
2034
+ shortcut = "H",
2035
+ className
2036
+ }) {
2037
+ const { isPending, run } = usePending();
2038
+ const busy = pending || isPending("hand");
2039
+ return /* @__PURE__ */ jsxRuntime.jsx(
2040
+ CallControlButton,
2041
+ {
2042
+ label: raised ? "Lower hand" : "Raise hand",
2043
+ shortcut,
2044
+ tone: raised ? "active" : "neutral",
2045
+ "aria-pressed": raised,
2046
+ disabled: busy,
2047
+ "aria-busy": busy || void 0,
2048
+ onClick: () => void run("hand", () => onToggle(!raised)),
2049
+ className,
2050
+ "data-testid": "webinar-raise-hand",
2051
+ "data-raised": raised,
2052
+ "data-call-shortcut": shortcut.toLowerCase(),
2053
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hand, { "aria-hidden": true })
2054
+ }
2055
+ );
2056
+ }
2057
+ var ROLE_NOTE = {
2058
+ host: "Host",
2059
+ cohost: "Co-host",
2060
+ panelist: "Panelist",
2061
+ attendee: null
2062
+ };
2063
+ function SectionTitle({ children }) {
2064
+ return /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "flex items-center gap-1.5 px-2 pb-1 pt-3 text-xs font-medium text-muted-foreground", children });
2065
+ }
2066
+ function Row({ testId, children }) {
2067
+ return /* @__PURE__ */ jsxRuntime.jsx(
2068
+ "li",
2069
+ {
2070
+ className: "flex min-h-11 items-center gap-3 rounded-md px-2 py-1 hover:bg-muted",
2071
+ "data-testid": testId,
2072
+ children
2073
+ }
2074
+ );
2075
+ }
2076
+ function WebinarAudiencePanel({
2077
+ hands,
2078
+ attendees,
2079
+ onPromote,
2080
+ onDemote,
2081
+ searchThreshold = 8,
2082
+ now: fixedNow,
2083
+ className
2084
+ }) {
2085
+ const now = useNow(3e4, fixedNow);
2086
+ const { isPending, run } = usePending();
2087
+ const [query, setQuery] = react.useState("");
2088
+ const queue = react.useMemo(
2089
+ () => [...hands].sort((a, b) => Date.parse(a.raisedAt) - Date.parse(b.raisedAt)),
2090
+ [hands]
2091
+ );
2092
+ const searchable = attendees.length > searchThreshold;
2093
+ const q = searchable ? query.trim().toLowerCase() : "";
2094
+ const shown = q ? attendees.filter((a) => a.name.toLowerCase().includes(q)) : attendees;
2095
+ const action = (id, name, promote, where) => {
2096
+ const key = `${promote ? "promote" : "demote"}:${id}`;
2097
+ const busy = isPending(key);
2098
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2099
+ ui.Button,
2100
+ {
2101
+ type: "button",
2102
+ variant: "outline",
2103
+ size: "sm",
2104
+ className: "h-8 shrink-0",
2105
+ disabled: busy,
2106
+ "aria-label": promote ? `Promote ${name} to the stage` : `Move ${name} to the audience`,
2107
+ onClick: () => void run(key, () => promote ? onPromote(id) : onDemote(id)),
2108
+ "data-testid": `webinar-${where === "hand" ? "hand-" : ""}${promote ? "promote" : "demote"}-${id}`,
2109
+ children: [
2110
+ busy && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
2111
+ promote ? "Promote" : "Move to audience"
2112
+ ]
2113
+ }
2114
+ );
2115
+ };
2116
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2117
+ "div",
2118
+ {
2119
+ className: ui.cn("min-h-0 flex-1 overflow-y-auto px-2 pb-2", className),
2120
+ "data-testid": "webinar-audience",
2121
+ children: [
2122
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Raised hands", children: [
2123
+ /* @__PURE__ */ jsxRuntime.jsxs(SectionTitle, { children: [
2124
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hand, { className: "size-3.5", "aria-hidden": true }),
2125
+ "Raised hands (",
2126
+ queue.length,
2127
+ ")"
2128
+ ] }),
2129
+ queue.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-2 py-2 text-sm text-muted-foreground", "data-testid": "webinar-hands-empty", children: "No hands raised." }) : /* @__PURE__ */ jsxRuntime.jsx("ol", { children: queue.map((h) => /* @__PURE__ */ jsxRuntime.jsxs(Row, { testId: `webinar-hand-${h.id}`, children: [
2130
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2131
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex min-w-0 items-center gap-1.5 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(CallPersonLabel, { name: h.name, entity: h.entity }) }),
2132
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
2133
+ "Raised ",
2134
+ chunkKDG6FER6_cjs.fromNow(h.raisedAt, now)
2135
+ ] })
2136
+ ] }),
2137
+ action(h.id, h.name, true, "hand")
2138
+ ] }, h.id)) })
2139
+ ] }),
2140
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Attendees", children: [
2141
+ /* @__PURE__ */ jsxRuntime.jsxs(SectionTitle, { children: [
2142
+ "Attendees (",
2143
+ attendees.length,
2144
+ ")"
2145
+ ] }),
2146
+ searchable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative px-2 pb-1", children: [
2147
+ /* @__PURE__ */ jsxRuntime.jsx(
2148
+ lucideReact.Search,
2149
+ {
2150
+ className: "pointer-events-none absolute left-4.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground",
2151
+ "aria-hidden": true
2152
+ }
2153
+ ),
2154
+ /* @__PURE__ */ jsxRuntime.jsx(
2155
+ ui.Input,
2156
+ {
2157
+ type: "search",
2158
+ value: query,
2159
+ onChange: (e) => setQuery(e.target.value),
2160
+ placeholder: "Find someone",
2161
+ "aria-label": "Find an attendee",
2162
+ className: "h-8 pl-8 text-sm",
2163
+ "data-testid": "webinar-attendee-search"
2164
+ }
2165
+ )
2166
+ ] }),
2167
+ shown.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2168
+ "p",
2169
+ {
2170
+ className: "px-2 py-2 text-sm text-muted-foreground",
2171
+ "data-testid": "webinar-attendees-empty",
2172
+ children: q ? `No one matches \u201C${query.trim()}\u201D.` : "No attendees yet."
2173
+ }
2174
+ ) : /* @__PURE__ */ jsxRuntime.jsx("ul", { children: shown.map((a) => {
2175
+ const note = ROLE_NOTE[a.role];
2176
+ return /* @__PURE__ */ jsxRuntime.jsxs(Row, { testId: `webinar-attendee-${a.id}`, children: [
2177
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: [
2178
+ /* @__PURE__ */ jsxRuntime.jsx(CallPersonLabel, { name: a.name, entity: a.entity }),
2179
+ (note || a.onStage) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: [note, a.onStage ? "On stage" : null].filter(Boolean).join(" \xB7 ") })
2180
+ ] }),
2181
+ a.handRaised && /* @__PURE__ */ jsxRuntime.jsx(
2182
+ lucideReact.Hand,
2183
+ {
2184
+ className: "size-4 shrink-0 text-primary",
2185
+ "aria-label": "Hand raised",
2186
+ "data-testid": `webinar-attendee-hand-${a.id}`
2187
+ }
2188
+ ),
2189
+ a.onStage ? a.role !== "host" && action(a.id, a.name, false, "list") : !a.handRaised && action(a.id, a.name, true, "list")
2190
+ ] }, a.id);
2191
+ }) })
2192
+ ] })
2193
+ ]
2194
+ }
2195
+ );
2196
+ }
2197
+ var DEFAULT_MAX = 500;
2198
+ function sortQuestions(questions, filter) {
2199
+ const match = (s) => filter === "open" ? s === "open" || s === "answering" : s === filter;
2200
+ return questions.filter((q) => match(q.status)).sort(
2201
+ (a, b) => Number(b.status === "answering") - Number(a.status === "answering") || b.voteCount - a.voteCount || Date.parse(a.createdAt) - Date.parse(b.createdAt)
2202
+ );
2203
+ }
2204
+ function PanelComposer({
2205
+ placeholder,
2206
+ ariaLabel,
2207
+ sendLabel,
2208
+ maxLength,
2209
+ busy,
2210
+ onSend,
2211
+ onCancel,
2212
+ autoFocus,
2213
+ testId
2214
+ }) {
2215
+ const [text, setText] = react.useState("");
2216
+ const body = text.trim();
2217
+ const canSend = !!body && body.length <= maxLength && !busy;
2218
+ const send = () => {
2219
+ if (!canSend) return;
2220
+ void onSend(body).then((ok) => {
2221
+ if (ok) setText("");
2222
+ });
2223
+ };
2224
+ const onKeyDown = (e) => {
2225
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
2226
+ e.preventDefault();
2227
+ send();
2228
+ } else if (e.key === "Escape" && onCancel) {
2229
+ e.preventDefault();
2230
+ onCancel();
2231
+ }
2232
+ };
2233
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", "data-testid": testId, children: [
2234
+ /* @__PURE__ */ jsxRuntime.jsx(
2235
+ ui.Textarea,
2236
+ {
2237
+ value: text,
2238
+ onChange: (e) => setText(e.target.value),
2239
+ onKeyDown,
2240
+ placeholder,
2241
+ "aria-label": ariaLabel,
2242
+ maxLength,
2243
+ rows: 2,
2244
+ disabled: busy,
2245
+ autoFocus,
2246
+ className: "min-h-[60px] resize-none",
2247
+ "data-testid": `${testId}-input`
2248
+ }
2249
+ ),
2250
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2251
+ /* @__PURE__ */ jsxRuntime.jsxs(
2252
+ "span",
2253
+ {
2254
+ className: ui.cn(
2255
+ "flex-1 text-xs tabular-nums text-muted-foreground",
2256
+ text.length >= maxLength && "text-red-text"
2257
+ ),
2258
+ "aria-live": "polite",
2259
+ "data-testid": `${testId}-count`,
2260
+ children: [
2261
+ text.length,
2262
+ "/",
2263
+ maxLength
2264
+ ]
2265
+ }
2266
+ ),
2267
+ onCancel && /* @__PURE__ */ jsxRuntime.jsx(
2268
+ ui.Button,
2269
+ {
2270
+ type: "button",
2271
+ variant: "ghost",
2272
+ size: "sm",
2273
+ className: "h-8",
2274
+ onClick: onCancel,
2275
+ disabled: busy,
2276
+ "data-testid": `${testId}-cancel`,
2277
+ children: "Cancel"
2278
+ }
2279
+ ),
2280
+ /* @__PURE__ */ jsxRuntime.jsxs(
2281
+ ui.Button,
2282
+ {
2283
+ type: "button",
2284
+ size: "sm",
2285
+ className: "h-8",
2286
+ onClick: send,
2287
+ disabled: !canSend,
2288
+ "data-testid": `${testId}-send`,
2289
+ children: [
2290
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "size-4", "aria-hidden": true }),
2291
+ sendLabel
2292
+ ]
2293
+ }
2294
+ )
2295
+ ] })
2296
+ ] });
2297
+ }
2298
+ function QuestionItem({
2299
+ q,
2300
+ team,
2301
+ now,
2302
+ maxLength,
2303
+ pending,
2304
+ run,
2305
+ onVote,
2306
+ onStatusChange,
2307
+ onAnswer,
2308
+ onDelete
2309
+ }) {
2310
+ const [answering, setAnswering] = react.useState(false);
2311
+ const voteKey = `vote:${q.id}`;
2312
+ const statusKey = `status:${q.id}`;
2313
+ const answerKey = `answer:${q.id}`;
2314
+ const busy = pending(statusKey) || pending(answerKey) || pending(`delete:${q.id}`);
2315
+ const setStatus = (status) => () => onStatusChange ? onStatusChange(q, status) : void 0;
2316
+ const can = team && !!onStatusChange;
2317
+ const primary = !can ? null : q.status === "open" ? { key: "live", label: "Answer live", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, {}), run: setStatus("answering") } : q.status === "answering" ? {
2318
+ key: "answered",
2319
+ label: "Mark answered",
2320
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCheck, {}),
2321
+ run: setStatus("answered")
2322
+ } : { key: "reopen", label: "Reopen", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, {}), run: setStatus("open") };
2323
+ const more = [
2324
+ ...can && q.status === "open" ? [
2325
+ {
2326
+ key: "answered",
2327
+ label: "Mark answered",
2328
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCheck, {}),
2329
+ run: setStatus("answered")
2330
+ }
2331
+ ] : [],
2332
+ ...can && q.status === "answering" ? [{ key: "reopen", label: "Reopen", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, {}), run: setStatus("open") }] : [],
2333
+ ...can && q.status !== "dismissed" ? [{ key: "dismiss", label: "Dismiss", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, {}), run: setStatus("dismissed") }] : [],
2334
+ ...q.mine && q.status === "open" && onDelete ? [
2335
+ {
2336
+ key: "delete",
2337
+ label: "Delete question",
2338
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, {}),
2339
+ run: () => onDelete(q),
2340
+ destructive: true
2341
+ }
2342
+ ] : []
2343
+ ];
2344
+ const canAnswerInText = team && !!onAnswer && !q.answer && q.status !== "dismissed";
2345
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2346
+ "li",
2347
+ {
2348
+ className: "flex gap-2 rounded-md border border-border p-2",
2349
+ "data-testid": `webinar-question-${q.id}`,
2350
+ "data-status": q.status,
2351
+ children: [
2352
+ /* @__PURE__ */ jsxRuntime.jsxs(
2353
+ ui.Button,
2354
+ {
2355
+ type: "button",
2356
+ variant: "ghost",
2357
+ className: ui.cn(
2358
+ "h-auto w-10 shrink-0 flex-col gap-0 self-start px-0 py-1 text-xs tabular-nums",
2359
+ q.voted ? "bg-primary/10 text-primary hover:bg-primary/15" : "text-muted-foreground"
2360
+ ),
2361
+ "aria-pressed": q.voted,
2362
+ "aria-label": q.mine ? `${q.voteCount} upvotes (your question)` : q.voted ? `Remove upvote (${q.voteCount})` : `Upvote (${q.voteCount})`,
2363
+ title: q.mine ? "You can't upvote your own question" : void 0,
2364
+ disabled: q.mine || pending(voteKey),
2365
+ onClick: () => void run(voteKey, () => onVote(q, !q.voted)),
2366
+ "data-testid": `webinar-question-vote-${q.id}`,
2367
+ children: [
2368
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "size-4", "aria-hidden": true }),
2369
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": `webinar-question-votes-${q.id}`, children: q.voteCount })
2370
+ ]
2371
+ }
2372
+ ),
2373
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
2374
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground", children: [
2375
+ /* @__PURE__ */ jsxRuntime.jsx(
2376
+ "span",
2377
+ {
2378
+ className: "flex min-w-0 items-center gap-1.5 text-sm text-foreground",
2379
+ "data-testid": `webinar-question-asker-${q.id}`,
2380
+ children: q.askerName === null && !q.asker ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Attendee" }) : /* @__PURE__ */ jsxRuntime.jsx(CallPersonLabel, { name: q.askerName ?? "", entity: q.asker, isLocal: q.mine })
2381
+ }
2382
+ ),
2383
+ /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: q.createdAt, children: chunkKDG6FER6_cjs.fromNow(q.createdAt, now) }),
2384
+ q.status === "answering" && /* @__PURE__ */ jsxRuntime.jsx(
2385
+ CallStatusChip,
2386
+ {
2387
+ tone: "red",
2388
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "size-3.5 shrink-0", "aria-hidden": true }),
2389
+ label: "Answering live",
2390
+ testId: `webinar-question-live-${q.id}`
2391
+ }
2392
+ ),
2393
+ q.status === "answered" && /* @__PURE__ */ jsxRuntime.jsx(
2394
+ CallStatusChip,
2395
+ {
2396
+ tone: "green",
2397
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCheck, { className: "size-3.5 shrink-0", "aria-hidden": true }),
2398
+ label: "Answered",
2399
+ testId: `webinar-question-answered-${q.id}`
2400
+ }
2401
+ ),
2402
+ q.status === "dismissed" && /* @__PURE__ */ jsxRuntime.jsx(CallStatusChip, { tone: "neutral", label: "Dismissed" })
2403
+ ] }),
2404
+ /* @__PURE__ */ jsxRuntime.jsx(
2405
+ "p",
2406
+ {
2407
+ className: "whitespace-pre-wrap break-words text-sm",
2408
+ "data-testid": `webinar-question-body-${q.id}`,
2409
+ children: q.body
2410
+ }
2411
+ ),
2412
+ q.answer && /* @__PURE__ */ jsxRuntime.jsxs(
2413
+ "div",
2414
+ {
2415
+ className: "flex flex-col gap-1 rounded-md bg-muted px-2.5 py-2 text-sm",
2416
+ "data-testid": `webinar-question-answer-${q.id}`,
2417
+ children: [
2418
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
2419
+ "Answer",
2420
+ q.answeredBy
2421
+ ] }),
2422
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap break-words", children: q.answer })
2423
+ ]
2424
+ }
2425
+ ),
2426
+ answering && onAnswer && /* @__PURE__ */ jsxRuntime.jsx(
2427
+ PanelComposer,
2428
+ {
2429
+ placeholder: "Write an answer",
2430
+ ariaLabel: "Answer in text",
2431
+ sendLabel: "Send answer",
2432
+ maxLength,
2433
+ busy: pending(answerKey),
2434
+ autoFocus: true,
2435
+ onSend: (text) => run(answerKey, () => onAnswer(q, text)).then((ok) => {
2436
+ if (ok) setAnswering(false);
2437
+ return ok;
2438
+ }),
2439
+ onCancel: () => setAnswering(false),
2440
+ testId: `webinar-answer-${q.id}`
2441
+ }
2442
+ ),
2443
+ (primary || canAnswerInText || more.length > 0) && !answering && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
2444
+ primary && /* @__PURE__ */ jsxRuntime.jsxs(
2445
+ ui.Button,
2446
+ {
2447
+ type: "button",
2448
+ variant: "outline",
2449
+ size: "sm",
2450
+ className: "h-7 gap-1.5 px-2 text-xs [&_svg]:size-3.5",
2451
+ disabled: busy,
2452
+ onClick: () => void run(statusKey, primary.run),
2453
+ "data-testid": `webinar-question-${primary.key}-${q.id}`,
2454
+ children: [
2455
+ pending(statusKey) ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", "aria-hidden": true }) : primary.icon,
2456
+ primary.label
2457
+ ]
2458
+ }
2459
+ ),
2460
+ canAnswerInText && /* @__PURE__ */ jsxRuntime.jsxs(
2461
+ ui.Button,
2462
+ {
2463
+ type: "button",
2464
+ variant: "ghost",
2465
+ size: "sm",
2466
+ className: "h-7 gap-1.5 px-2 text-xs [&_svg]:size-3.5",
2467
+ disabled: busy,
2468
+ onClick: () => setAnswering(true),
2469
+ "data-testid": `webinar-question-answer-text-${q.id}`,
2470
+ children: [
2471
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareText, { "aria-hidden": true }),
2472
+ "Answer in text"
2473
+ ]
2474
+ }
2475
+ ),
2476
+ more.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2477
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
2478
+ ui.Button,
2479
+ {
2480
+ type: "button",
2481
+ variant: "ghost",
2482
+ size: "icon",
2483
+ className: "ml-auto size-7 text-muted-foreground hover:text-foreground",
2484
+ "aria-label": "More question actions",
2485
+ disabled: busy,
2486
+ "data-testid": `webinar-question-menu-${q.id}`,
2487
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "size-4" })
2488
+ }
2489
+ ) }),
2490
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuContent, { align: "end", className: "w-48", children: more.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
2491
+ ui.DropdownMenuItem,
2492
+ {
2493
+ onSelect: () => void run(a.key === "delete" ? `delete:${q.id}` : statusKey, a.run),
2494
+ className: ui.cn(a.destructive && "text-destructive focus:text-destructive"),
2495
+ "data-testid": `webinar-question-${a.key}-${q.id}`,
2496
+ children: [
2497
+ a.icon,
2498
+ a.label
2499
+ ]
2500
+ },
2501
+ a.key
2502
+ )) })
2503
+ ] })
2504
+ ] })
2505
+ ] })
2506
+ ]
2507
+ }
2508
+ );
2509
+ }
2510
+ var EMPTY = {
2511
+ open: {
2512
+ team: "Questions from the audience appear here.",
2513
+ audience: "No questions yet. Ask the first one."
2514
+ },
2515
+ answered: { team: "No answered questions yet.", audience: "No answered questions yet." },
2516
+ dismissed: { team: "Nothing dismissed.", audience: "Nothing dismissed." }
2517
+ };
2518
+ function QAPanel({
2519
+ questions,
2520
+ team = false,
2521
+ maxLength = DEFAULT_MAX,
2522
+ canAsk = true,
2523
+ onAsk,
2524
+ onVote,
2525
+ onStatusChange,
2526
+ onAnswer,
2527
+ onDelete,
2528
+ defaultFilter = "open",
2529
+ now: fixedNow,
2530
+ className
2531
+ }) {
2532
+ const now = useNow(3e4, fixedNow);
2533
+ const { isPending, run } = usePending();
2534
+ const [filter, setFilter] = react.useState(
2535
+ defaultFilter === "dismissed" && !team ? "open" : defaultFilter
2536
+ );
2537
+ const filters = [
2538
+ { id: "open", label: "Open" },
2539
+ { id: "answered", label: "Answered" },
2540
+ ...team ? [{ id: "dismissed", label: "Dismissed" }] : []
2541
+ ];
2542
+ const lists = react.useMemo(
2543
+ () => ({
2544
+ open: sortQuestions(questions, "open"),
2545
+ answered: sortQuestions(questions, "answered"),
2546
+ // Dismissed questions are the team's business only.
2547
+ dismissed: team ? sortQuestions(questions, "dismissed") : []
2548
+ }),
2549
+ [questions, team]
2550
+ );
2551
+ const shown = lists[filter];
2552
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex min-h-0 flex-1 flex-col", className), "data-testid": "webinar-qa", children: [
2553
+ /* @__PURE__ */ jsxRuntime.jsxs(
2554
+ ui.Tabs,
2555
+ {
2556
+ value: filter,
2557
+ onValueChange: (v) => setFilter(v),
2558
+ className: "flex min-h-0 flex-1 flex-col",
2559
+ children: [
2560
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 px-3 pt-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.TabsList, { className: "h-8 w-full", children: filters.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
2561
+ ui.TabsTrigger,
2562
+ {
2563
+ value: f.id,
2564
+ className: "h-6 flex-1 gap-1.5 px-2 text-xs",
2565
+ "data-testid": `webinar-qa-filter-${f.id}`,
2566
+ children: [
2567
+ f.label,
2568
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums text-muted-foreground", children: lists[f.id].length })
2569
+ ]
2570
+ },
2571
+ f.id
2572
+ )) }) }),
2573
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TabsContent, { value: filter, className: "mt-0 min-h-0 flex-1 overflow-y-auto p-3", children: shown.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2574
+ "p",
2575
+ {
2576
+ className: "py-6 text-center text-sm text-muted-foreground",
2577
+ "data-testid": "webinar-qa-empty",
2578
+ children: filter === "open" && questions.length > 0 ? "No open questions right now." : EMPTY[filter][team ? "team" : "audience"]
2579
+ }
2580
+ ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "flex flex-col gap-2", children: shown.map((q) => /* @__PURE__ */ jsxRuntime.jsx(
2581
+ QuestionItem,
2582
+ {
2583
+ q,
2584
+ team,
2585
+ now,
2586
+ maxLength,
2587
+ pending: isPending,
2588
+ run,
2589
+ onVote,
2590
+ onStatusChange,
2591
+ onAnswer,
2592
+ onDelete
2593
+ },
2594
+ q.id
2595
+ )) }) })
2596
+ ]
2597
+ }
2598
+ ),
2599
+ canAsk && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 border-t border-border p-3", children: /* @__PURE__ */ jsxRuntime.jsx(
2600
+ PanelComposer,
2601
+ {
2602
+ placeholder: "Ask a question",
2603
+ ariaLabel: "Ask a question",
2604
+ sendLabel: "Ask",
2605
+ maxLength,
2606
+ busy: isPending("ask"),
2607
+ onSend: (text) => run("ask", () => onAsk(text)),
2608
+ testId: "webinar-ask"
2609
+ }
2610
+ ) })
2611
+ ] });
2612
+ }
2613
+ var OPTIONS = [
2614
+ { value: "panelists", label: "Panelists only" },
2615
+ { value: "everyone", label: "Everyone" }
2616
+ ];
2617
+ function ChatPermissionSetting({
2618
+ value,
2619
+ onChange,
2620
+ pending = false
2621
+ }) {
2622
+ const [choosing, setChoosing] = react.useState(null);
2623
+ const busy = pending || choosing !== null;
2624
+ const select = (next) => {
2625
+ const v = next;
2626
+ if (v === value || busy) return;
2627
+ const result = onChange(v);
2628
+ if (result && typeof result.then === "function") {
2629
+ setChoosing(v);
2630
+ void result.then(
2631
+ () => setChoosing(null),
2632
+ () => setChoosing(null)
2633
+ );
2634
+ }
2635
+ };
2636
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": "Who can chat", "data-testid": "webinar-chat-permission", children: [
2637
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuLabel, { className: "flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
2638
+ "Who can chat",
2639
+ busy && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-3.5 animate-spin", "aria-label": "Saving" })
2640
+ ] }),
2641
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuRadioGroup, { value: choosing ?? value, onValueChange: select, children: OPTIONS.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
2642
+ ui.DropdownMenuRadioItem,
2643
+ {
2644
+ value: o.value,
2645
+ disabled: busy,
2646
+ "data-testid": `webinar-chat-${o.value}`,
2647
+ children: o.label
2648
+ },
2649
+ o.value
2650
+ )) })
2651
+ ] });
2652
+ }
2653
+ function chatPermissionSection(props) {
2654
+ return { key: "webinar-chat", content: /* @__PURE__ */ jsxRuntime.jsx(ChatPermissionSetting, { ...props }) };
2655
+ }
2656
+ function OnStageCheck({
2657
+ open,
2658
+ onJoinStage,
2659
+ onStayInAudience,
2660
+ busy = false,
2661
+ error,
2662
+ mirror = true,
2663
+ describeError = describeMediaError
2664
+ }) {
2665
+ const { isPending, run } = usePending();
2666
+ const working = busy || isPending("join") || isPending("stay");
2667
+ const stay = () => {
2668
+ if (!working) void run("stay", onStayInAudience);
2669
+ };
2670
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, { open, onOpenChange: (next) => !next && stay(), children: /* @__PURE__ */ jsxRuntime.jsx(ui.DialogContent, { presentation: "sheet", className: "max-w-3xl", "data-testid": "webinar-on-stage", children: /* @__PURE__ */ jsxRuntime.jsx(
2671
+ PreJoin,
2672
+ {
2673
+ heading: "You're on stage",
2674
+ headingAs: ui.DialogTitle,
2675
+ joinLabel: "Join stage",
2676
+ cancelLabel: "Stay in audience",
2677
+ busy: working,
2678
+ error,
2679
+ mirror,
2680
+ describeError,
2681
+ onJoin: (choices) => void run("join", () => onJoinStage(choices)),
2682
+ onCancel: stay,
2683
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.DialogDescription, { children: "A host invited you to speak. Check your microphone and camera: once you join, everyone sees and hears you." })
2684
+ }
2685
+ ) }) });
2686
+ }
2687
+ function WebinarViewer({
2688
+ header,
2689
+ stage,
2690
+ stageRef,
2691
+ stageOverlay,
2692
+ captions,
2693
+ side,
2694
+ sideToggle,
2695
+ hand,
2696
+ onLeave,
2697
+ left,
2698
+ controls,
2699
+ shortcuts = true,
2700
+ className
2701
+ }) {
2702
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2703
+ "div",
2704
+ {
2705
+ className: ui.cn(
2706
+ "@container flex size-full min-h-0 flex-col bg-background text-foreground",
2707
+ className
2708
+ ),
2709
+ "data-testid": "webinar-viewer",
2710
+ children: [
2711
+ header,
2712
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex min-h-0 flex-1", children: [
2713
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col gap-2 px-3 pb-3 pt-1", children: [
2714
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: stageRef, className: "relative min-h-0 flex-1", "data-testid": "call-stage", children: [
2715
+ stage,
2716
+ stageOverlay
2717
+ ] }),
2718
+ captions
2719
+ ] }),
2720
+ side && /* @__PURE__ */ jsxRuntime.jsx(
2721
+ "div",
2722
+ {
2723
+ 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",
2724
+ "data-testid": "webinar-viewer-side",
2725
+ children: side
2726
+ }
2727
+ )
2728
+ ] }),
2729
+ /* @__PURE__ */ jsxRuntime.jsx(CallControlBar, { shortcuts, left, right: /* @__PURE__ */ jsxRuntime.jsx(LeaveButton, { onLeave }), children: /* @__PURE__ */ jsxRuntime.jsxs(ControlGroup, { children: [
2730
+ /* @__PURE__ */ jsxRuntime.jsx(RaiseHandButton, { ...hand }),
2731
+ sideToggle && /* @__PURE__ */ jsxRuntime.jsxs(
2732
+ CallControlButton,
2733
+ {
2734
+ label: sideToggle.open ? `Close ${sideToggle.label}` : sideToggle.label,
2735
+ tone: sideToggle.open ? "active" : "neutral",
2736
+ "aria-pressed": sideToggle.open,
2737
+ onClick: sideToggle.onToggle,
2738
+ "data-testid": "webinar-side-toggle",
2739
+ children: [
2740
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageCircleQuestion, { "aria-hidden": true }),
2741
+ !!sideToggle.count && /* @__PURE__ */ jsxRuntime.jsx(ControlBadge, { count: sideToggle.count, tone: "quiet" })
2742
+ ]
2743
+ }
2744
+ ),
2745
+ controls
2746
+ ] }) })
2747
+ ]
2748
+ }
2749
+ );
2750
+ }
2751
+
2752
+ Object.defineProperty(exports, "CAPTION_LINES", {
2753
+ enumerable: true,
2754
+ get: function () { return chunkMWDW5UBW_cjs.CAPTION_LINES; }
2755
+ });
2756
+ Object.defineProperty(exports, "CAPTION_TTL_MS", {
2757
+ enumerable: true,
2758
+ get: function () { return chunkMWDW5UBW_cjs.CAPTION_TTL_MS; }
2759
+ });
2760
+ Object.defineProperty(exports, "INITIAL_SPOTLIGHT", {
2761
+ enumerable: true,
2762
+ get: function () { return chunkMWDW5UBW_cjs.INITIAL_SPOTLIGHT; }
2763
+ });
2764
+ Object.defineProperty(exports, "MAX_GRID_TILES", {
2765
+ enumerable: true,
2766
+ get: function () { return chunkMWDW5UBW_cjs.MAX_GRID_TILES; }
2767
+ });
2768
+ Object.defineProperty(exports, "NOTICE_MS", {
2769
+ enumerable: true,
2770
+ get: function () { return chunkMWDW5UBW_cjs.NOTICE_MS; }
2771
+ });
2772
+ Object.defineProperty(exports, "SPEAKER_ACCENTS", {
2773
+ enumerable: true,
2774
+ get: function () { return chunkMWDW5UBW_cjs.SPEAKER_ACCENTS; }
2775
+ });
2776
+ Object.defineProperty(exports, "SPEAKING_LEVEL", {
2777
+ enumerable: true,
2778
+ get: function () { return chunkMWDW5UBW_cjs.SPEAKING_LEVEL; }
2779
+ });
2780
+ Object.defineProperty(exports, "TILE_ASPECT", {
2781
+ enumerable: true,
2782
+ get: function () { return chunkMWDW5UBW_cjs.TILE_ASPECT; }
2783
+ });
2784
+ Object.defineProperty(exports, "formatElapsed", {
2785
+ enumerable: true,
2786
+ get: function () { return chunkMWDW5UBW_cjs.formatElapsed; }
2787
+ });
2788
+ Object.defineProperty(exports, "gridLayout", {
2789
+ enumerable: true,
2790
+ get: function () { return chunkMWDW5UBW_cjs.gridLayout; }
2791
+ });
2792
+ Object.defineProperty(exports, "gridSlots", {
2793
+ enumerable: true,
2794
+ get: function () { return chunkMWDW5UBW_cjs.gridSlots; }
2795
+ });
2796
+ Object.defineProperty(exports, "initialsOf", {
2797
+ enumerable: true,
2798
+ get: function () { return chunkMWDW5UBW_cjs.initialsOf; }
2799
+ });
2800
+ Object.defineProperty(exports, "nearestCorner", {
2801
+ enumerable: true,
2802
+ get: function () { return chunkMWDW5UBW_cjs.nearestCorner; }
2803
+ });
2804
+ Object.defineProperty(exports, "nextSpotlight", {
2805
+ enumerable: true,
2806
+ get: function () { return chunkMWDW5UBW_cjs.nextSpotlight; }
2807
+ });
2808
+ Object.defineProperty(exports, "pickSpotlight", {
2809
+ enumerable: true,
2810
+ get: function () { return chunkMWDW5UBW_cjs.pickSpotlight; }
2811
+ });
2812
+ Object.defineProperty(exports, "rowCounts", {
2813
+ enumerable: true,
2814
+ get: function () { return chunkMWDW5UBW_cjs.rowCounts; }
2815
+ });
2816
+ Object.defineProperty(exports, "speakerAccent", {
2817
+ enumerable: true,
2818
+ get: function () { return chunkMWDW5UBW_cjs.speakerAccent; }
2819
+ });
2820
+ Object.defineProperty(exports, "speakerBorder", {
2821
+ enumerable: true,
2822
+ get: function () { return chunkMWDW5UBW_cjs.speakerBorder; }
2823
+ });
2824
+ Object.defineProperty(exports, "spotlightBox", {
2825
+ enumerable: true,
2826
+ get: function () { return chunkMWDW5UBW_cjs.spotlightBox; }
2827
+ });
2828
+ Object.defineProperty(exports, "stageContentHeight", {
2829
+ enumerable: true,
2830
+ get: function () { return chunkMWDW5UBW_cjs.stageContentHeight; }
2831
+ });
2832
+ exports.BackstageBanner = BackstageBanner;
2833
+ exports.CallControlBar = CallControlBar;
2834
+ exports.CallControlButton = CallControlButton;
2835
+ exports.CallFocus = CallFocus;
2836
+ exports.CallGrid = CallGrid;
2837
+ exports.CallHeader = CallHeader;
2838
+ exports.CallNotice = CallNotice;
2839
+ exports.CallParticipants = CallParticipants;
2840
+ exports.CallPersonLabel = CallPersonLabel;
2841
+ exports.CallSidePanel = CallSidePanel;
2842
+ exports.CallStageFrame = CallStageFrame;
2843
+ exports.CallStateMessage = CallStateMessage;
2844
+ exports.CallStatusChip = CallStatusChip;
2845
+ exports.CallTile = CallTile;
2846
+ exports.CaptionsStrip = CaptionsStrip;
2847
+ exports.ChatPermissionSetting = ChatPermissionSetting;
2848
+ exports.ConnectionOverlay = ConnectionOverlay;
2849
+ exports.ControlBadge = ControlBadge;
2850
+ exports.ControlDivider = ControlDivider;
2851
+ exports.ControlGroup = ControlGroup;
2852
+ exports.DEFAULT_JOIN_CHOICES = DEFAULT_JOIN_CHOICES;
2853
+ exports.ElapsedTime = ElapsedTime;
2854
+ exports.EncryptionStatusChip = EncryptionStatusChip;
2855
+ exports.IncomingCallCard = IncomingCallCard;
2856
+ exports.LeaveButton = LeaveButton;
2857
+ exports.LobbyRequests = LobbyRequests;
2858
+ exports.MediaControl = MediaControl;
2859
+ exports.MinimisedCall = MinimisedCall;
2860
+ exports.MoreMenu = MoreMenu;
2861
+ exports.OnStageCheck = OnStageCheck;
2862
+ exports.PanelComposer = PanelComposer;
2863
+ exports.PreJoin = PreJoin;
2864
+ exports.QAPanel = QAPanel;
2865
+ exports.RaiseHandButton = RaiseHandButton;
2866
+ exports.RecordControl = RecordControl;
2867
+ exports.RecordingStatusChip = RecordingStatusChip;
2868
+ exports.SpeakingNow = SpeakingNow;
2869
+ exports.TranscriptionStatusChip = TranscriptionStatusChip;
2870
+ exports.VoiceMeter = VoiceMeter;
2871
+ exports.WebinarAudiencePanel = WebinarAudiencePanel;
2872
+ exports.WebinarEndConfirm = WebinarEndConfirm;
2873
+ exports.WebinarLiveChip = WebinarLiveChip;
2874
+ exports.WebinarLiveControl = WebinarLiveControl;
2875
+ exports.WebinarViewer = WebinarViewer;
2876
+ exports.WebinarWaiting = WebinarWaiting;
2877
+ exports.chatPermissionSection = chatPermissionSection;
2878
+ exports.describeMediaError = describeMediaError;
2879
+ exports.sortQuestions = sortQuestions;
2880
+ exports.useBriefNotice = useBriefNotice;
2881
+ exports.useElementSize = useElementSize;
2882
+ exports.useShortcutKeys = useShortcutKeys;
2883
+ //# sourceMappingURL=index.cjs.map
2884
+ //# sourceMappingURL=index.cjs.map