@aperture-browser/session-react 0.0.0-init.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2212 @@
1
+ import { a as makeApertureRuntime, c as collaborationPaintLifetimeMs, d as RuntimeProviderMissingError, f as useBaseUrl, g as useRuntime, h as useRunApi, i as ApertureProvider, l as useLiveSession, m as useFork, n as parseShareToken, o as useBrowserControl, p as useEffectCallback, r as useSharedSession, s as SessionViewport, t as LiveSessionError, u as RuntimeProvider } from "./headless-CKFacFX3.js";
2
+ import { Fragment, createContext, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import * as Effect from "effect/Effect";
4
+ import { VIEWPORT_DEVICE_SCALE_FACTORS, VIEWPORT_PRESETS, createViewportPreset, formatViewportScale } from "@aperture-browser/live-session";
5
+ import * as Data from "effect/Data";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
+ import { ArrowLeft, ArrowRight, CheckIcon, ChevronRightIcon, Circle, CircleCheckIcon, Copy, Download, Gauge, Globe2, Info, InfoIcon, Link2Off, Loader2, Loader2Icon, Lock, LockOpen, Maximize2, Monitor, MoreVertical, MousePointer2, OctagonXIcon, PanelBottom, PanelRight, Pencil, Plus, Radio, RefreshCw, RotateCcw, Share2, Square, TriangleAlertIcon, Unplug, Wrench, X } from "lucide-react";
8
+ import { cva } from "class-variance-authority";
9
+ import { clsx } from "clsx";
10
+ import { twMerge } from "tailwind-merge";
11
+ import { useTheme } from "next-themes";
12
+ import { Toaster, toast } from "sonner";
13
+ import { Tooltip } from "@base-ui/react/tooltip";
14
+ import * as ResizablePrimitive from "react-resizable-panels";
15
+ import { usePanelRef } from "react-resizable-panels";
16
+ import * as Schedule from "effect/Schedule";
17
+ import { Button } from "@base-ui/react/button";
18
+ import { ContextMenu } from "@base-ui/react/context-menu";
19
+ import { Input } from "@base-ui/react/input";
20
+ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
21
+ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
22
+ import { ScrollArea } from "@base-ui/react/scroll-area";
23
+ import "@base-ui/react/separator";
24
+ import { Menu } from "@base-ui/react/menu";
25
+ import { Avatar } from "@base-ui/react/avatar";
26
+ import { Popover } from "@base-ui/react/popover";
27
+ import { mergeProps } from "@base-ui/react/merge-props";
28
+ import { useRender } from "@base-ui/react/use-render";
29
+ //#region ../ui/src/utils.ts
30
+ function cn(...inputs) {
31
+ return twMerge(clsx(inputs));
32
+ }
33
+ //#endregion
34
+ //#region ../ui/src/components/empty.tsx
35
+ function Empty({ className, ...props }) {
36
+ return /* @__PURE__ */ jsx("div", {
37
+ "data-slot": "empty",
38
+ className: cn("flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance", className),
39
+ ...props
40
+ });
41
+ }
42
+ function EmptyHeader({ className, ...props }) {
43
+ return /* @__PURE__ */ jsx("div", {
44
+ "data-slot": "empty-header",
45
+ className: cn("flex max-w-sm flex-col items-center gap-2", className),
46
+ ...props
47
+ });
48
+ }
49
+ const emptyMediaVariants = cva("mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", {
50
+ variants: { variant: {
51
+ default: "bg-transparent",
52
+ icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4"
53
+ } },
54
+ defaultVariants: { variant: "default" }
55
+ });
56
+ function EmptyMedia({ className, variant = "default", ...props }) {
57
+ return /* @__PURE__ */ jsx("div", {
58
+ "data-slot": "empty-icon",
59
+ "data-variant": variant,
60
+ className: cn(emptyMediaVariants({
61
+ variant,
62
+ className
63
+ })),
64
+ ...props
65
+ });
66
+ }
67
+ function EmptyTitle({ className, ...props }) {
68
+ return /* @__PURE__ */ jsx("div", {
69
+ "data-slot": "empty-title",
70
+ className: cn("text-sm font-medium tracking-tight", className),
71
+ ...props
72
+ });
73
+ }
74
+ function EmptyDescription({ className, ...props }) {
75
+ return /* @__PURE__ */ jsx("div", {
76
+ "data-slot": "empty-description",
77
+ className: cn("text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", className),
78
+ ...props
79
+ });
80
+ }
81
+ //#endregion
82
+ //#region ../ui/src/components/sonner.tsx
83
+ const Toaster$1 = ({ ...props }) => {
84
+ const { theme = "system" } = useTheme();
85
+ return /* @__PURE__ */ jsx(Toaster, {
86
+ theme,
87
+ className: "toaster group",
88
+ icons: {
89
+ success: /* @__PURE__ */ jsx(CircleCheckIcon, { className: "size-4" }),
90
+ info: /* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }),
91
+ warning: /* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4" }),
92
+ error: /* @__PURE__ */ jsx(OctagonXIcon, { className: "size-4" }),
93
+ loading: /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" })
94
+ },
95
+ style: {
96
+ "--normal-bg": "var(--popover)",
97
+ "--normal-text": "var(--popover-foreground)",
98
+ "--normal-border": "var(--border)",
99
+ "--border-radius": "var(--radius)"
100
+ },
101
+ toastOptions: { classNames: { toast: "cn-toast" } },
102
+ ...props
103
+ });
104
+ };
105
+ //#endregion
106
+ //#region ../ui/src/portal.tsx
107
+ const PortalContainerContext = createContext(null);
108
+ function PortalContainerProvider({ container, children }) {
109
+ return /* @__PURE__ */ jsx(PortalContainerContext.Provider, {
110
+ value: container,
111
+ children
112
+ });
113
+ }
114
+ function usePortalContainer() {
115
+ return useContext(PortalContainerContext) ?? void 0;
116
+ }
117
+ //#endregion
118
+ //#region ../ui/src/components/tooltip.tsx
119
+ function TooltipProvider({ delay = 0, ...props }) {
120
+ return /* @__PURE__ */ jsx(Tooltip.Provider, {
121
+ "data-slot": "tooltip-provider",
122
+ delay,
123
+ ...props
124
+ });
125
+ }
126
+ function Tooltip$1({ ...props }) {
127
+ return /* @__PURE__ */ jsx(Tooltip.Root, {
128
+ "data-slot": "tooltip",
129
+ ...props
130
+ });
131
+ }
132
+ function TooltipTrigger({ ...props }) {
133
+ return /* @__PURE__ */ jsx(Tooltip.Trigger, {
134
+ "data-slot": "tooltip-trigger",
135
+ ...props
136
+ });
137
+ }
138
+ function TooltipContent({ className, side = "top", sideOffset = 4, align = "center", alignOffset = 0, children, ...props }) {
139
+ return /* @__PURE__ */ jsx(Tooltip.Portal, {
140
+ container: usePortalContainer(),
141
+ children: /* @__PURE__ */ jsx(Tooltip.Positioner, {
142
+ align,
143
+ alignOffset,
144
+ side,
145
+ sideOffset,
146
+ className: "isolate z-50",
147
+ children: /* @__PURE__ */ jsxs(Tooltip.Popup, {
148
+ "data-slot": "tooltip-content",
149
+ className: cn("z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
150
+ ...props,
151
+ children: [children, /* @__PURE__ */ jsx(Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" })]
152
+ })
153
+ })
154
+ });
155
+ }
156
+ //#endregion
157
+ //#region src/features.ts
158
+ function resolveSessionFeatures(features) {
159
+ return {
160
+ tabs: true,
161
+ navigation: true,
162
+ addressBar: true,
163
+ presence: true,
164
+ drawing: true,
165
+ devTools: true,
166
+ menus: true,
167
+ statusBadge: true,
168
+ ...features
169
+ };
170
+ }
171
+ //#endregion
172
+ //#region ../ui/src/components/button.tsx
173
+ const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm leading-none font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
174
+ variants: {
175
+ variant: {
176
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
177
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
178
+ secondary: "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
179
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
180
+ destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
181
+ link: "text-primary underline-offset-4 hover:underline"
182
+ },
183
+ size: {
184
+ default: "h-8 gap-1.5 px-2.5 pt-px has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
185
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 pt-px text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
186
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 pt-px text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
187
+ lg: "h-9 gap-1.5 px-2.5 pt-px has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
188
+ icon: "size-8",
189
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
190
+ "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
191
+ "icon-lg": "size-9"
192
+ }
193
+ },
194
+ defaultVariants: {
195
+ variant: "default",
196
+ size: "default"
197
+ }
198
+ });
199
+ function Button$1({ className, variant = "default", size = "default", ...props }) {
200
+ return /* @__PURE__ */ jsx(Button, {
201
+ "data-slot": "button",
202
+ className: cn(buttonVariants({
203
+ variant,
204
+ size,
205
+ className
206
+ })),
207
+ ...props
208
+ });
209
+ }
210
+ //#endregion
211
+ //#region ../ui/src/components/context-menu.tsx
212
+ function ContextMenu$1({ ...props }) {
213
+ return /* @__PURE__ */ jsx(ContextMenu.Root, {
214
+ "data-slot": "context-menu",
215
+ ...props
216
+ });
217
+ }
218
+ function ContextMenuTrigger({ className, ...props }) {
219
+ return /* @__PURE__ */ jsx(ContextMenu.Trigger, {
220
+ "data-slot": "context-menu-trigger",
221
+ className: cn("select-none", className),
222
+ ...props
223
+ });
224
+ }
225
+ function ContextMenuContent({ className, align = "start", alignOffset = 4, side = "right", sideOffset = 0, ...props }) {
226
+ return /* @__PURE__ */ jsx(ContextMenu.Portal, {
227
+ container: usePortalContainer(),
228
+ children: /* @__PURE__ */ jsx(ContextMenu.Positioner, {
229
+ className: "isolate z-50 outline-none",
230
+ align,
231
+ alignOffset,
232
+ side,
233
+ sideOffset,
234
+ children: /* @__PURE__ */ jsx(ContextMenu.Popup, {
235
+ "data-slot": "context-menu-content",
236
+ className: cn("z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
237
+ ...props
238
+ })
239
+ })
240
+ });
241
+ }
242
+ function ContextMenuGroup({ ...props }) {
243
+ return /* @__PURE__ */ jsx(ContextMenu.Group, {
244
+ "data-slot": "context-menu-group",
245
+ ...props
246
+ });
247
+ }
248
+ function ContextMenuLabel({ className, inset, ...props }) {
249
+ return /* @__PURE__ */ jsx(ContextMenu.GroupLabel, {
250
+ "data-slot": "context-menu-label",
251
+ "data-inset": inset,
252
+ className: cn("px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7", className),
253
+ ...props
254
+ });
255
+ }
256
+ function ContextMenuItem({ className, inset, variant = "default", ...props }) {
257
+ return /* @__PURE__ */ jsx(ContextMenu.Item, {
258
+ "data-slot": "context-menu-item",
259
+ "data-inset": inset,
260
+ "data-variant": variant,
261
+ className: cn("group/context-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:*:[svg]:text-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive", className),
262
+ ...props
263
+ });
264
+ }
265
+ function ContextMenuRadioGroup({ ...props }) {
266
+ return /* @__PURE__ */ jsx(ContextMenu.RadioGroup, {
267
+ "data-slot": "context-menu-radio-group",
268
+ ...props
269
+ });
270
+ }
271
+ function ContextMenuRadioItem({ className, children, inset, ...props }) {
272
+ return /* @__PURE__ */ jsxs(ContextMenu.RadioItem, {
273
+ "data-slot": "context-menu-radio-item",
274
+ "data-inset": inset,
275
+ className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
276
+ ...props,
277
+ children: [/* @__PURE__ */ jsx("span", {
278
+ className: "pointer-events-none absolute right-2",
279
+ children: /* @__PURE__ */ jsx(ContextMenu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
280
+ }), children]
281
+ });
282
+ }
283
+ function ContextMenuSeparator({ className, ...props }) {
284
+ return /* @__PURE__ */ jsx(ContextMenu.Separator, {
285
+ "data-slot": "context-menu-separator",
286
+ className: cn("-mx-1 my-1 h-px bg-border", className),
287
+ ...props
288
+ });
289
+ }
290
+ //#endregion
291
+ //#region ../ui/src/components/input.tsx
292
+ function Input$1({ className, type, ...props }) {
293
+ return /* @__PURE__ */ jsx(Input, {
294
+ type,
295
+ "data-slot": "input",
296
+ className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
297
+ ...props
298
+ });
299
+ }
300
+ //#endregion
301
+ //#region ../ui/src/components/input-group.tsx
302
+ function InputGroup({ className, ...props }) {
303
+ return /* @__PURE__ */ jsx("div", {
304
+ "data-slot": "input-group",
305
+ role: "group",
306
+ className: cn("group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", className),
307
+ ...props
308
+ });
309
+ }
310
+ cva("flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", {
311
+ variants: { align: {
312
+ "inline-start": "order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
313
+ "inline-end": "order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
314
+ "block-start": "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
315
+ "block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"
316
+ } },
317
+ defaultVariants: { align: "inline-start" }
318
+ });
319
+ cva("flex items-center gap-2 text-sm shadow-none", {
320
+ variants: { size: {
321
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
322
+ sm: "",
323
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
324
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0"
325
+ } },
326
+ defaultVariants: { size: "xs" }
327
+ });
328
+ function InputGroupInput({ className, ...props }) {
329
+ return /* @__PURE__ */ jsx(Input$1, {
330
+ "data-slot": "input-group-control",
331
+ className: cn("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent", className),
332
+ ...props
333
+ });
334
+ }
335
+ //#endregion
336
+ //#region ../ui/src/components/scroll-area.tsx
337
+ function ScrollArea$1({ className, viewportClassName, scrollbars = "vertical", children, ...props }) {
338
+ const vertical = scrollbars === "vertical" || scrollbars === "both";
339
+ const horizontal = scrollbars === "horizontal" || scrollbars === "both";
340
+ return /* @__PURE__ */ jsxs(ScrollArea.Root, {
341
+ "data-slot": "scroll-area",
342
+ className: cn("relative", className),
343
+ ...props,
344
+ children: [
345
+ /* @__PURE__ */ jsx(ScrollArea.Viewport, {
346
+ "data-slot": "scroll-area-viewport",
347
+ style: {
348
+ overflowX: horizontal ? "scroll" : "hidden",
349
+ overflowY: vertical ? "scroll" : "hidden"
350
+ },
351
+ className: cn("size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1", viewportClassName),
352
+ children
353
+ }),
354
+ vertical ? /* @__PURE__ */ jsx(ScrollBar, {}) : null,
355
+ horizontal ? /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" }) : null,
356
+ /* @__PURE__ */ jsx(ScrollArea.Corner, {})
357
+ ]
358
+ });
359
+ }
360
+ function ScrollBar({ className, orientation = "vertical", ...props }) {
361
+ return /* @__PURE__ */ jsx(ScrollArea.Scrollbar, {
362
+ "data-slot": "scroll-area-scrollbar",
363
+ "data-orientation": orientation,
364
+ orientation,
365
+ className: cn("flex touch-none p-px transition-colors select-none hover:bg-muted/50 data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col data-[orientation=horizontal]:border-t data-[orientation=horizontal]:border-t-transparent data-[orientation=vertical]:h-full data-[orientation=vertical]:w-2.5 data-[orientation=vertical]:border-l data-[orientation=vertical]:border-l-transparent", className),
366
+ ...props,
367
+ children: /* @__PURE__ */ jsx(ScrollArea.Thumb, {
368
+ "data-slot": "scroll-area-thumb",
369
+ className: "relative flex-1 rounded-full bg-foreground/35 transition-colors hover:bg-foreground/55"
370
+ })
371
+ });
372
+ }
373
+ //#endregion
374
+ //#region src/clipboard.ts
375
+ /** The browser refused to write to the clipboard. */
376
+ var ClipboardError = class extends Data.TaggedError("ClipboardError") {
377
+ message = "Clipboard write is unavailable";
378
+ };
379
+ const copyText = (value) => Effect.suspend(() => {
380
+ const clipboard = navigator.clipboard;
381
+ if (clipboard?.writeText) return Effect.tryPromise({
382
+ try: () => clipboard.writeText(value),
383
+ catch: (cause) => new ClipboardError({ cause })
384
+ });
385
+ const textArea = document.createElement("textarea");
386
+ textArea.value = value;
387
+ textArea.readOnly = true;
388
+ textArea.style.position = "fixed";
389
+ textArea.style.top = "0";
390
+ textArea.style.left = "-9999px";
391
+ document.body.append(textArea);
392
+ textArea.select();
393
+ const copied = document.execCommand("copy");
394
+ textArea.remove();
395
+ return copied ? Effect.void : Effect.fail(new ClipboardError({}));
396
+ });
397
+ /** Copies `value`, then runs `onCopied`, or shows an error toast if the copy fails. */
398
+ const copyTextWithToast = (value, onCopied = () => {}) => copyText(value).pipe(Effect.andThen(Effect.sync(onCopied)), Effect.catchTag("ClipboardError", (error) => Effect.sync(() => {
399
+ console.warn("Copy failed", error);
400
+ toast.error("Copy failed");
401
+ })));
402
+ //#endregion
403
+ //#region src/components/browser-tab-strip.tsx
404
+ const BROWSER_TAB_DRAG_KIND = "browser-tab";
405
+ function BrowserTabStrip({ targets, activeTargetId, recordingTargetIds, devToolsTargetIds, disabled, mutationDisabled, onActivate, onCreate, onDuplicate, onClose, onReload, onReorder }) {
406
+ if (targets.length === 0) return /* @__PURE__ */ jsxs("div", {
407
+ className: "flex h-8 min-w-0 flex-1 items-center gap-2 px-2 text-xs text-muted-foreground",
408
+ children: [/* @__PURE__ */ jsx("span", { children: "No tabs" }), /* @__PURE__ */ jsx(NewTabButton, {
409
+ disabled: disabled || mutationDisabled,
410
+ onCreate
411
+ })]
412
+ });
413
+ return /* @__PURE__ */ jsx(ScrollArea$1, {
414
+ scrollbars: "horizontal",
415
+ className: "h-8 min-w-0 flex-1",
416
+ children: /* @__PURE__ */ jsxs("div", {
417
+ className: "flex min-w-max items-end gap-0.5 px-1 pt-1",
418
+ children: [targets.map((target, index) => {
419
+ return /* @__PURE__ */ jsx(BrowserTab, {
420
+ target,
421
+ active: target.id === activeTargetId,
422
+ recording: recordingTargetIds.has(target.id),
423
+ devToolsOpen: devToolsTargetIds.has(target.id),
424
+ disabled,
425
+ mutationDisabled,
426
+ onActivate,
427
+ onDuplicate,
428
+ onClose,
429
+ onReload,
430
+ onReorder,
431
+ closeOtherTargetIds: targets.filter((current) => current.id !== target.id).map((current) => current.id),
432
+ closeRightTargetIds: targets.slice(index + 1).map((current) => current.id)
433
+ }, target.id);
434
+ }), /* @__PURE__ */ jsx(NewTabButton, {
435
+ disabled: disabled || mutationDisabled,
436
+ onCreate
437
+ })]
438
+ })
439
+ });
440
+ }
441
+ function BrowserTab({ target, active, recording, devToolsOpen, disabled, mutationDisabled, onActivate, onDuplicate, onClose, onReload, onReorder, closeOtherTargetIds, closeRightTargetIds }) {
442
+ const tabRef = useRef(null);
443
+ const [dragging, setDragging] = useState(false);
444
+ const [dropPlacement, setDropPlacement] = useState(null);
445
+ const label = simplifyUrl(target.url);
446
+ const copyUrl = useEffectCallback((url) => copyTextWithToast(url), []);
447
+ useEffect(() => {
448
+ const element = tabRef.current;
449
+ if (!element) return;
450
+ return combine(draggable({
451
+ element,
452
+ getInitialData: () => ({
453
+ kind: BROWSER_TAB_DRAG_KIND,
454
+ targetId: target.id
455
+ }),
456
+ onDragStart: () => setDragging(true),
457
+ onDrop: () => setDragging(false)
458
+ }), dropTargetForElements({
459
+ element,
460
+ canDrop: ({ source }) => {
461
+ return isBrowserTabDragData(source.data) && source.data.targetId !== target.id;
462
+ },
463
+ getData: () => ({
464
+ kind: BROWSER_TAB_DRAG_KIND,
465
+ targetId: target.id
466
+ }),
467
+ onDrag: ({ location, self }) => {
468
+ setDropPlacement(dropPlacementFromClientX(self.element, location.current.input.clientX));
469
+ },
470
+ onDragEnter: ({ location, self }) => {
471
+ setDropPlacement(dropPlacementFromClientX(self.element, location.current.input.clientX));
472
+ },
473
+ onDragLeave: () => setDropPlacement(null),
474
+ onDrop: ({ source, self, location }) => {
475
+ setDropPlacement(null);
476
+ if (!isBrowserTabDragData(source.data)) return;
477
+ onReorder(source.data.targetId, target.id, dropPlacementFromClientX(self.element, location.current.input.clientX));
478
+ }
479
+ }));
480
+ }, [onReorder, target.id]);
481
+ return /* @__PURE__ */ jsxs(ContextMenu$1, { children: [/* @__PURE__ */ jsxs(ContextMenuTrigger, {
482
+ render: /* @__PURE__ */ jsx("div", {
483
+ ref: tabRef,
484
+ "data-browser-tab": true,
485
+ className: cn("group relative flex h-7 w-52 max-w-[38vw] min-w-28 cursor-grab select-none items-center gap-1.5 rounded-t-lg border border-b-0 px-2 text-left text-xs transition-[background-color,border-color,color,opacity] active:cursor-grabbing", active ? "border-border bg-background text-foreground" : "border-transparent bg-muted/55 text-muted-foreground hover:bg-muted", dragging && "opacity-60"),
486
+ title: target.url || "about:blank",
487
+ onMouseDown: (event) => {
488
+ if (event.button === 1) event.preventDefault();
489
+ },
490
+ onAuxClick: (event) => {
491
+ if (event.button === 1 && !disabled && !mutationDisabled) {
492
+ event.preventDefault();
493
+ onClose(target.id);
494
+ }
495
+ }
496
+ }),
497
+ children: [
498
+ /* @__PURE__ */ jsx("span", { className: cn("pointer-events-none absolute inset-y-1 left-0 w-0.5 rounded-full bg-primary opacity-0", dropPlacement === "before" && "opacity-100") }),
499
+ /* @__PURE__ */ jsx("span", { className: cn("pointer-events-none absolute inset-y-1 right-0 w-0.5 rounded-full bg-primary opacity-0", dropPlacement === "after" && "opacity-100") }),
500
+ /* @__PURE__ */ jsxs("button", {
501
+ type: "button",
502
+ "aria-current": active ? "page" : void 0,
503
+ disabled,
504
+ className: "flex min-w-0 flex-1 items-center gap-1.5 text-left",
505
+ onClick: () => onActivate(target.id),
506
+ children: [
507
+ /* @__PURE__ */ jsx(TabFavicon, { url: target.url }),
508
+ devToolsOpen ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Wrench, {
509
+ "aria-hidden": true,
510
+ className: "size-3.5 shrink-0 text-muted-foreground"
511
+ }), /* @__PURE__ */ jsx("span", {
512
+ className: "sr-only",
513
+ children: "DevTools open"
514
+ })] }) : null,
515
+ recording ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
516
+ "aria-hidden": true,
517
+ className: "size-2 shrink-0 rounded-full bg-destructive"
518
+ }), /* @__PURE__ */ jsx("span", {
519
+ className: "sr-only",
520
+ children: "Recording"
521
+ })] }) : null,
522
+ /* @__PURE__ */ jsx("span", {
523
+ className: "min-w-0 truncate font-mono",
524
+ children: label
525
+ })
526
+ ]
527
+ }),
528
+ /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
529
+ render: /* @__PURE__ */ jsx(Button$1, {
530
+ type: "button",
531
+ variant: "ghost",
532
+ size: "icon-xs",
533
+ className: "shrink-0 opacity-55 hover:opacity-100 group-hover:opacity-100",
534
+ "aria-label": "Close tab",
535
+ disabled: disabled || mutationDisabled,
536
+ onClick: () => onClose(target.id)
537
+ }),
538
+ children: /* @__PURE__ */ jsx(X, {})
539
+ }), /* @__PURE__ */ jsx(TooltipContent, {
540
+ side: "bottom",
541
+ children: "Close tab"
542
+ })] })
543
+ ]
544
+ }), /* @__PURE__ */ jsxs(ContextMenuContent, {
545
+ className: "min-w-48",
546
+ children: [
547
+ /* @__PURE__ */ jsxs(ContextMenuGroup, { children: [
548
+ /* @__PURE__ */ jsx(ContextMenuItem, {
549
+ disabled: disabled || mutationDisabled,
550
+ onClick: () => onReload(target.id),
551
+ children: "Reload"
552
+ }),
553
+ /* @__PURE__ */ jsx(ContextMenuItem, {
554
+ disabled: disabled || mutationDisabled,
555
+ onClick: () => onDuplicate(target),
556
+ children: "Duplicate"
557
+ }),
558
+ /* @__PURE__ */ jsx(ContextMenuItem, {
559
+ onClick: () => copyUrl(target.url || "about:blank"),
560
+ children: "Copy URL"
561
+ })
562
+ ] }),
563
+ /* @__PURE__ */ jsx(ContextMenuSeparator, {}),
564
+ /* @__PURE__ */ jsxs(ContextMenuGroup, { children: [
565
+ /* @__PURE__ */ jsx(ContextMenuItem, {
566
+ disabled: disabled || mutationDisabled,
567
+ onClick: () => onClose(target.id),
568
+ children: "Close"
569
+ }),
570
+ /* @__PURE__ */ jsx(ContextMenuItem, {
571
+ disabled: disabled || mutationDisabled || closeOtherTargetIds.length === 0,
572
+ onClick: () => {
573
+ for (const targetId of closeOtherTargetIds) onClose(targetId);
574
+ },
575
+ children: "Close other tabs"
576
+ }),
577
+ /* @__PURE__ */ jsx(ContextMenuItem, {
578
+ disabled: disabled || mutationDisabled || closeRightTargetIds.length === 0,
579
+ onClick: () => {
580
+ for (const targetId of closeRightTargetIds) onClose(targetId);
581
+ },
582
+ children: "Close tabs to the right"
583
+ })
584
+ ] })
585
+ ]
586
+ })] });
587
+ }
588
+ function NewTabButton({ disabled, onCreate }) {
589
+ return /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
590
+ render: /* @__PURE__ */ jsx(Button$1, {
591
+ type: "button",
592
+ variant: "ghost",
593
+ size: "icon-xs",
594
+ className: "mb-px shrink-0",
595
+ "aria-label": "New tab",
596
+ disabled,
597
+ onClick: onCreate
598
+ }),
599
+ children: /* @__PURE__ */ jsx(Plus, {})
600
+ }), /* @__PURE__ */ jsx(TooltipContent, {
601
+ side: "bottom",
602
+ children: "New tab"
603
+ })] });
604
+ }
605
+ function TabFavicon({ url }) {
606
+ const [failed, setFailed] = useState(false);
607
+ const faviconUrl = resolveFaviconUrl(url);
608
+ useEffect(() => {
609
+ setFailed(false);
610
+ }, [faviconUrl]);
611
+ if (!faviconUrl || failed) return /* @__PURE__ */ jsx(Globe2, { className: "size-4 shrink-0 text-muted-foreground" });
612
+ return /* @__PURE__ */ jsx("img", {
613
+ src: faviconUrl,
614
+ alt: "",
615
+ className: "size-4 shrink-0",
616
+ onError: () => setFailed(true)
617
+ });
618
+ }
619
+ function resolveFaviconUrl(url) {
620
+ try {
621
+ const parsed = new URL(url);
622
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
623
+ return `${parsed.origin}/favicon.ico`;
624
+ } catch {
625
+ return null;
626
+ }
627
+ }
628
+ function simplifyUrl(url) {
629
+ if (!url || url === "about:blank") return "about:blank";
630
+ try {
631
+ const parsed = new URL(url);
632
+ return `${parsed.host}${parsed.pathname === "/" ? "" : parsed.pathname}`;
633
+ } catch {
634
+ return url;
635
+ }
636
+ }
637
+ function isBrowserTabDragData(data) {
638
+ return data.kind === BROWSER_TAB_DRAG_KIND && typeof data.targetId === "string";
639
+ }
640
+ function dropPlacementFromClientX(element, clientX) {
641
+ const rect = element.getBoundingClientRect();
642
+ return clientX < rect.left + rect.width / 2 ? "before" : "after";
643
+ }
644
+ //#endregion
645
+ //#region ../ui/src/components/label.tsx
646
+ function Label({ className, ...props }) {
647
+ return /* @__PURE__ */ jsx("label", {
648
+ "data-slot": "label",
649
+ className: cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
650
+ ...props
651
+ });
652
+ }
653
+ //#endregion
654
+ //#region ../ui/src/components/field.tsx
655
+ function FieldGroup({ className, ...props }) {
656
+ return /* @__PURE__ */ jsx("div", {
657
+ "data-slot": "field-group",
658
+ className: cn("group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4", className),
659
+ ...props
660
+ });
661
+ }
662
+ const fieldVariants = cva("group/field flex w-full gap-2 data-[invalid=true]:text-destructive", {
663
+ variants: { orientation: {
664
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
665
+ horizontal: "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
666
+ responsive: "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
667
+ } },
668
+ defaultVariants: { orientation: "vertical" }
669
+ });
670
+ function Field({ className, orientation = "vertical", ...props }) {
671
+ return /* @__PURE__ */ jsx("div", {
672
+ role: "group",
673
+ "data-slot": "field",
674
+ "data-orientation": orientation,
675
+ className: cn(fieldVariants({ orientation }), className),
676
+ ...props
677
+ });
678
+ }
679
+ function FieldLabel({ className, ...props }) {
680
+ return /* @__PURE__ */ jsx(Label, {
681
+ "data-slot": "field-label",
682
+ className: cn("group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", className),
683
+ ...props
684
+ });
685
+ }
686
+ //#endregion
687
+ //#region ../ui/src/components/dropdown-menu.tsx
688
+ function DropdownMenu({ ...props }) {
689
+ return /* @__PURE__ */ jsx(Menu.Root, {
690
+ "data-slot": "dropdown-menu",
691
+ ...props
692
+ });
693
+ }
694
+ function DropdownMenuTrigger({ ...props }) {
695
+ return /* @__PURE__ */ jsx(Menu.Trigger, {
696
+ "data-slot": "dropdown-menu-trigger",
697
+ ...props
698
+ });
699
+ }
700
+ function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4, className, ...props }) {
701
+ return /* @__PURE__ */ jsx(Menu.Portal, {
702
+ container: usePortalContainer(),
703
+ children: /* @__PURE__ */ jsx(Menu.Positioner, {
704
+ className: "isolate z-50 outline-none",
705
+ align,
706
+ alignOffset,
707
+ side,
708
+ sideOffset,
709
+ children: /* @__PURE__ */ jsx(Menu.Popup, {
710
+ "data-slot": "dropdown-menu-content",
711
+ className: cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className),
712
+ ...props
713
+ })
714
+ })
715
+ });
716
+ }
717
+ function DropdownMenuGroup({ ...props }) {
718
+ return /* @__PURE__ */ jsx(Menu.Group, {
719
+ "data-slot": "dropdown-menu-group",
720
+ ...props
721
+ });
722
+ }
723
+ function DropdownMenuLabel({ className, inset, ...props }) {
724
+ return /* @__PURE__ */ jsx("div", {
725
+ "data-slot": "dropdown-menu-label",
726
+ "data-inset": inset,
727
+ className: cn("px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7", className),
728
+ ...props
729
+ });
730
+ }
731
+ function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
732
+ return /* @__PURE__ */ jsx(Menu.Item, {
733
+ "data-slot": "dropdown-menu-item",
734
+ "data-inset": inset,
735
+ "data-variant": variant,
736
+ className: cn("group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive", className),
737
+ ...props
738
+ });
739
+ }
740
+ function DropdownMenuSub({ ...props }) {
741
+ return /* @__PURE__ */ jsx(Menu.SubmenuRoot, {
742
+ "data-slot": "dropdown-menu-sub",
743
+ ...props
744
+ });
745
+ }
746
+ function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
747
+ return /* @__PURE__ */ jsxs(Menu.SubmenuTrigger, {
748
+ "data-slot": "dropdown-menu-sub-trigger",
749
+ "data-inset": inset,
750
+ className: cn("flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
751
+ ...props,
752
+ children: [children, /* @__PURE__ */ jsx(ChevronRightIcon, { className: "ml-auto" })]
753
+ });
754
+ }
755
+ function DropdownMenuSubContent({ align = "start", alignOffset = -3, side = "right", sideOffset = 0, className, ...props }) {
756
+ return /* @__PURE__ */ jsx(DropdownMenuContent, {
757
+ "data-slot": "dropdown-menu-sub-content",
758
+ className: cn("w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
759
+ align,
760
+ alignOffset,
761
+ side,
762
+ sideOffset,
763
+ ...props
764
+ });
765
+ }
766
+ function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }) {
767
+ return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
768
+ "data-slot": "dropdown-menu-checkbox-item",
769
+ "data-inset": inset,
770
+ className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
771
+ checked,
772
+ ...props,
773
+ children: [/* @__PURE__ */ jsx("span", {
774
+ className: "pointer-events-none absolute right-2 flex items-center justify-center",
775
+ "data-slot": "dropdown-menu-checkbox-item-indicator",
776
+ children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
777
+ }), children]
778
+ });
779
+ }
780
+ function DropdownMenuRadioGroup({ ...props }) {
781
+ return /* @__PURE__ */ jsx(Menu.RadioGroup, {
782
+ "data-slot": "dropdown-menu-radio-group",
783
+ ...props
784
+ });
785
+ }
786
+ function DropdownMenuRadioItem({ className, children, inset, ...props }) {
787
+ return /* @__PURE__ */ jsxs(Menu.RadioItem, {
788
+ "data-slot": "dropdown-menu-radio-item",
789
+ "data-inset": inset,
790
+ className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
791
+ ...props,
792
+ children: [/* @__PURE__ */ jsx("span", {
793
+ className: "pointer-events-none absolute right-2 flex items-center justify-center",
794
+ "data-slot": "dropdown-menu-radio-item-indicator",
795
+ children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
796
+ }), children]
797
+ });
798
+ }
799
+ function DropdownMenuSeparator({ className, ...props }) {
800
+ return /* @__PURE__ */ jsx(Menu.Separator, {
801
+ "data-slot": "dropdown-menu-separator",
802
+ className: cn("-mx-1 my-1 h-px bg-border", className),
803
+ ...props
804
+ });
805
+ }
806
+ //#endregion
807
+ //#region src/components/browser-toolbar-menus.tsx
808
+ const STREAM_PRESETS = [
809
+ {
810
+ id: "low",
811
+ label: "Low",
812
+ detail: "20 fps · max 800 kbps",
813
+ settings: {
814
+ fps: 20,
815
+ bitrateKbps: 800
816
+ }
817
+ },
818
+ {
819
+ id: "balanced",
820
+ label: "Balanced",
821
+ detail: "30 fps · max 3000 kbps",
822
+ settings: {
823
+ fps: 30,
824
+ bitrateKbps: 3e3
825
+ }
826
+ },
827
+ {
828
+ id: "high",
829
+ label: "High",
830
+ detail: "60 fps · max 20000 kbps",
831
+ settings: {
832
+ fps: 60,
833
+ bitrateKbps: 2e4
834
+ }
835
+ }
836
+ ];
837
+ const STREAM_LIMITS = {
838
+ fps: {
839
+ min: 1,
840
+ max: 120
841
+ },
842
+ bitrateKbps: {
843
+ min: 100,
844
+ max: 5e4
845
+ }
846
+ };
847
+ const VIEWPORT_LIMITS = {
848
+ width: {
849
+ min: 1,
850
+ max: 16384
851
+ },
852
+ height: {
853
+ min: 1,
854
+ max: 16384
855
+ },
856
+ deviceScaleFactor: {
857
+ min: .25,
858
+ max: 4
859
+ }
860
+ };
861
+ function BrowserMenus({ control, cdpUrl, shareUrls, busy, connected, localCursorEnabled, onLocalCursorChange, onReconnect, onSessionDetails, now }) {
862
+ const runningRecordings = control.recordings.filter((recording) => recording.status === "starting" || recording.status === "running");
863
+ const recordingActive = runningRecordings.length > 0;
864
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
865
+ className: "shrink-0 sm:hidden",
866
+ children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
867
+ render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
868
+ type: "button",
869
+ variant: recordingActive ? "destructive" : "ghost",
870
+ size: "icon-sm",
871
+ "aria-label": recordingActive ? "Browser menu, recording active" : "Browser menu"
872
+ }) }),
873
+ children: recordingActive ? /* @__PURE__ */ jsx(Circle, { fill: "currentColor" }) : /* @__PURE__ */ jsx(MoreVertical, {})
874
+ }), /* @__PURE__ */ jsx(TooltipContent, {
875
+ side: "bottom",
876
+ children: recordingActive ? "Browser menu, recording active" : "Browser menu"
877
+ })] }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
878
+ align: "end",
879
+ className: "w-72",
880
+ children: [
881
+ /* @__PURE__ */ jsx(RestMenuItems, {
882
+ control,
883
+ cdpUrl,
884
+ shareUrls,
885
+ onSessionDetails
886
+ }),
887
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
888
+ /* @__PURE__ */ jsx(RecordingMenuItems, {
889
+ control,
890
+ connected: connected && control.collaboration.role === "owner",
891
+ runningRecordings,
892
+ now
893
+ }),
894
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
895
+ /* @__PURE__ */ jsx(ViewportStreamMenuItems, {
896
+ control,
897
+ connected: connected && control.collaboration.role !== "viewer",
898
+ localCursorEnabled,
899
+ onLocalCursorChange,
900
+ busy,
901
+ onReconnect
902
+ })
903
+ ]
904
+ })] })
905
+ }), /* @__PURE__ */ jsxs("div", {
906
+ className: "hidden shrink-0 items-center gap-0.5 sm:flex",
907
+ children: [
908
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
909
+ render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
910
+ type: "button",
911
+ variant: recordingActive ? "destructive" : "ghost",
912
+ size: "icon-sm",
913
+ "aria-label": recordingActive ? "Recording in progress" : "Recording"
914
+ }) }),
915
+ children: /* @__PURE__ */ jsx(Circle, { fill: recordingActive ? "currentColor" : "none" })
916
+ }), /* @__PURE__ */ jsx(TooltipContent, {
917
+ side: "bottom",
918
+ children: recordingActive ? "Recording in progress" : "Recording"
919
+ })] }), /* @__PURE__ */ jsx(DropdownMenuContent, {
920
+ align: "end",
921
+ className: "w-72",
922
+ children: /* @__PURE__ */ jsx(RecordingMenuItems, {
923
+ control,
924
+ connected: connected && control.collaboration.role === "owner",
925
+ runningRecordings,
926
+ now
927
+ })
928
+ })] }),
929
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
930
+ render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
931
+ type: "button",
932
+ variant: "ghost",
933
+ size: "icon-sm",
934
+ "aria-label": "Viewport and stream"
935
+ }) }),
936
+ children: /* @__PURE__ */ jsx(Monitor, {})
937
+ }), /* @__PURE__ */ jsx(TooltipContent, {
938
+ side: "bottom",
939
+ children: "Viewport and stream"
940
+ })] }), /* @__PURE__ */ jsx(DropdownMenuContent, {
941
+ align: "end",
942
+ className: "w-56",
943
+ children: /* @__PURE__ */ jsx(ViewportStreamMenuItems, {
944
+ control,
945
+ connected: connected && control.collaboration.role !== "viewer",
946
+ localCursorEnabled,
947
+ onLocalCursorChange,
948
+ busy,
949
+ onReconnect
950
+ })
951
+ })] }),
952
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
953
+ render: /* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
954
+ type: "button",
955
+ variant: "ghost",
956
+ size: "icon-sm",
957
+ "aria-label": "More browser actions"
958
+ }) }),
959
+ children: /* @__PURE__ */ jsx(MoreVertical, {})
960
+ }), /* @__PURE__ */ jsx(TooltipContent, {
961
+ side: "bottom",
962
+ children: "More browser actions"
963
+ })] }), /* @__PURE__ */ jsx(DropdownMenuContent, {
964
+ align: "end",
965
+ className: "w-48",
966
+ children: /* @__PURE__ */ jsx(RestMenuItems, {
967
+ control,
968
+ cdpUrl,
969
+ shareUrls,
970
+ onSessionDetails
971
+ })
972
+ })] })
973
+ ]
974
+ })] });
975
+ }
976
+ function RestMenuItems({ control, cdpUrl, shareUrls, onSessionDetails }) {
977
+ const copy = useEffectCallback((value, copied) => copyTextWithToast(value, () => toast.success(copied)), []);
978
+ return /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
979
+ /* @__PURE__ */ jsx(InputControlMenuItem, { control }),
980
+ onSessionDetails ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
981
+ onClick: onSessionDetails,
982
+ children: [/* @__PURE__ */ jsx(Info, {}), "Session details"]
983
+ }) : null,
984
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
985
+ disabled: !cdpUrl,
986
+ onClick: () => {
987
+ if (!cdpUrl) return;
988
+ copy(cdpUrl, "CDP URL copied");
989
+ },
990
+ children: [/* @__PURE__ */ jsx(Copy, {}), "Copy CDP URL"]
991
+ }),
992
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
993
+ disabled: !shareUrls,
994
+ onClick: () => {
995
+ if (!shareUrls) return;
996
+ copy(shareUrls.editor, "Editor URL copied");
997
+ },
998
+ children: [/* @__PURE__ */ jsx(Share2, {}), "Copy editor URL"]
999
+ }),
1000
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
1001
+ disabled: !shareUrls,
1002
+ onClick: () => {
1003
+ if (!shareUrls) return;
1004
+ copy(shareUrls.viewer, "Viewer URL copied");
1005
+ },
1006
+ children: [/* @__PURE__ */ jsx(Share2, {}), "Copy viewer URL"]
1007
+ })
1008
+ ] });
1009
+ }
1010
+ function InputControlMenuItem({ control }) {
1011
+ const collaboration = control.collaboration;
1012
+ const targetId = control.activeTargetId;
1013
+ const explicitControlClaimed = collaboration.holderClientId !== null && collaboration.leaseMode === "explicit";
1014
+ const localControlClaimed = collaboration.hasControl && collaboration.leaseMode === "explicit";
1015
+ let label = "Control offline";
1016
+ let disabled = collaboration.phase !== "connected" || !targetId;
1017
+ let action = () => void 0;
1018
+ if (collaboration.role === "viewer") {
1019
+ label = "View only";
1020
+ disabled = true;
1021
+ } else if (collaboration.hasControl && collaboration.leaseMode === "explicit") {
1022
+ label = "Release control";
1023
+ action = () => {
1024
+ collaboration.release();
1025
+ };
1026
+ } else if (collaboration.hasControl) {
1027
+ label = "Claim control";
1028
+ action = () => {
1029
+ if (targetId) collaboration.claim(targetId, "explicit");
1030
+ };
1031
+ } else {
1032
+ const holder = collaboration.participants.find((participant) => participant.clientId === collaboration.holderClientId);
1033
+ const canPreempt = collaboration.role === "owner" && holder?.role === "editor";
1034
+ const controlBlocked = explicitControlClaimed && !canPreempt;
1035
+ label = controlBlocked ? "Control in use" : "Claim control";
1036
+ disabled = disabled || controlBlocked;
1037
+ action = () => {
1038
+ if (targetId) collaboration.claim(targetId, "explicit");
1039
+ };
1040
+ }
1041
+ return /* @__PURE__ */ jsxs(DropdownMenuItem, {
1042
+ disabled,
1043
+ onClick: action,
1044
+ children: [localControlClaimed ? /* @__PURE__ */ jsx(Lock, {}) : /* @__PURE__ */ jsx(LockOpen, {}), label]
1045
+ });
1046
+ }
1047
+ function RecordingMenuItems({ control, connected, runningRecordings, now }) {
1048
+ const recordingAvailable = connected && control.collaboration.role === "owner";
1049
+ const canStart = recordingAvailable && Boolean(control.activeTargetId) && !control.recordingBusy;
1050
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
1051
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Recording" }),
1052
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
1053
+ disabled: !canStart,
1054
+ onClick: () => control.startRecording("tab"),
1055
+ children: [/* @__PURE__ */ jsx(Circle, {}), /* @__PURE__ */ jsxs("span", {
1056
+ className: "flex min-w-0 flex-col",
1057
+ children: [/* @__PURE__ */ jsx("span", { children: "Record this tab" }), /* @__PURE__ */ jsx("span", {
1058
+ className: "text-xs text-muted-foreground",
1059
+ children: "Stay pinned to this target"
1060
+ })]
1061
+ })]
1062
+ }),
1063
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
1064
+ disabled: !canStart,
1065
+ onClick: () => control.startRecording("viewer"),
1066
+ children: [/* @__PURE__ */ jsx(Monitor, {}), /* @__PURE__ */ jsxs("span", {
1067
+ className: "flex min-w-0 flex-col",
1068
+ children: [/* @__PURE__ */ jsx("span", { children: "Record this viewer" }), /* @__PURE__ */ jsx("span", {
1069
+ className: "text-xs text-muted-foreground",
1070
+ children: recordingAvailable ? "Follow tab switches" : "Owner connection required"
1071
+ })]
1072
+ })]
1073
+ })
1074
+ ] }), runningRecordings.map((recording) => {
1075
+ const target = control.targets.find((candidate) => candidate.id === recording.targetId);
1076
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
1077
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: /* @__PURE__ */ jsxs("span", {
1078
+ className: "flex min-w-0 items-center justify-between gap-2",
1079
+ children: [/* @__PURE__ */ jsxs("span", {
1080
+ className: "truncate",
1081
+ children: [
1082
+ recording.mode === "viewer" ? "Viewer" : "Tab",
1083
+ ":",
1084
+ " ",
1085
+ target?.title || recording.targetId.slice(0, 8)
1086
+ ]
1087
+ }), /* @__PURE__ */ jsx("span", {
1088
+ className: "shrink-0 tabular-nums",
1089
+ children: formatElapsed(recording.startedAt, now)
1090
+ })]
1091
+ }) }),
1092
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
1093
+ disabled: control.recordingBusy,
1094
+ onClick: () => control.stopRecording(recording.recordingId),
1095
+ children: [/* @__PURE__ */ jsx(Download, {}), "Stop and download"]
1096
+ }),
1097
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
1098
+ disabled: control.recordingBusy,
1099
+ onClick: () => control.cancelRecording(recording.recordingId),
1100
+ children: [/* @__PURE__ */ jsx(X, {}), "Cancel without download"]
1101
+ })
1102
+ ] })] }, recording.recordingId);
1103
+ })] });
1104
+ }
1105
+ function ViewportStreamMenuItems({ control, connected, localCursorEnabled, onLocalCursorChange, busy, onReconnect }) {
1106
+ return /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
1107
+ /* @__PURE__ */ jsx(ViewportMenu, {
1108
+ control,
1109
+ connected
1110
+ }),
1111
+ control.mediaVideoProfiles.length > 0 ? /* @__PURE__ */ jsx(StreamMenu, {
1112
+ control,
1113
+ qualityUpdatesEnabled: control.collaboration.role !== "viewer"
1114
+ }) : null,
1115
+ /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
1116
+ disabled: !connected || control.collaboration.role === "viewer",
1117
+ checked: control.remoteCursorEnabled,
1118
+ onCheckedChange: control.setRemoteCursorEnabled,
1119
+ children: [/* @__PURE__ */ jsx(MousePointer2, {}), "Remote cursor"]
1120
+ }),
1121
+ /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
1122
+ checked: localCursorEnabled,
1123
+ onCheckedChange: onLocalCursorChange,
1124
+ children: [/* @__PURE__ */ jsx(MousePointer2, {}), "Local cursor"]
1125
+ }),
1126
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
1127
+ disabled: busy,
1128
+ onClick: onReconnect,
1129
+ children: [/* @__PURE__ */ jsx(RotateCcw, {}), "Reconnect"]
1130
+ })
1131
+ ] });
1132
+ }
1133
+ function StreamMenu({ control, qualityUpdatesEnabled }) {
1134
+ const settings = control.mediaStreamSettings;
1135
+ const source = control.mediaPath === "websocket-live" ? "jpeg" : qualityUpdatesEnabled ? settings?.profile : "webrtc";
1136
+ const disabled = control.mediaSwitching || control.phase !== "connected";
1137
+ return /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Gauge, {}), "Stream"] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, {
1138
+ className: "w-64",
1139
+ children: /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
1140
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Source" }),
1141
+ /* @__PURE__ */ jsxs(DropdownMenuRadioGroup, {
1142
+ value: source,
1143
+ onValueChange: (value) => {
1144
+ if (value === "jpeg") {
1145
+ control.selectMediaStream({ kind: "jpeg" });
1146
+ return;
1147
+ }
1148
+ if (value === "webrtc") {
1149
+ control.selectMediaStream({ kind: "webrtc-retry" });
1150
+ return;
1151
+ }
1152
+ const profile = control.mediaVideoProfiles.find((candidate) => candidate.id === value);
1153
+ if (profile && settings) control.selectMediaStream({
1154
+ kind: "webrtc",
1155
+ quality: {
1156
+ ...settings,
1157
+ profile: profile.id
1158
+ }
1159
+ });
1160
+ },
1161
+ children: [
1162
+ !qualityUpdatesEnabled ? /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
1163
+ value: "webrtc",
1164
+ disabled,
1165
+ children: /* @__PURE__ */ jsxs("span", {
1166
+ className: "flex min-w-0 flex-col",
1167
+ children: [/* @__PURE__ */ jsx("span", { children: "WebRTC" }), /* @__PURE__ */ jsx("span", {
1168
+ className: "text-xs text-muted-foreground",
1169
+ children: "Live video"
1170
+ })]
1171
+ })
1172
+ }) : null,
1173
+ qualityUpdatesEnabled ? control.mediaVideoProfiles.map((profile) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
1174
+ value: profile.id,
1175
+ disabled: disabled || !settings,
1176
+ children: /* @__PURE__ */ jsxs("span", {
1177
+ className: "flex min-w-0 flex-col",
1178
+ children: [/* @__PURE__ */ jsx("span", { children: profile.label }), /* @__PURE__ */ jsxs("span", {
1179
+ className: "text-xs text-muted-foreground",
1180
+ children: [profile.codec, " · WebRTC"]
1181
+ })]
1182
+ })
1183
+ }, profile.id)) : null,
1184
+ /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
1185
+ value: "jpeg",
1186
+ disabled,
1187
+ children: /* @__PURE__ */ jsxs("span", {
1188
+ className: "flex min-w-0 flex-col",
1189
+ children: [/* @__PURE__ */ jsx("span", { children: "JPEG" }), /* @__PURE__ */ jsx("span", {
1190
+ className: "text-xs text-muted-foreground",
1191
+ children: "WebSocket raster stream"
1192
+ })]
1193
+ })
1194
+ })
1195
+ ]
1196
+ }),
1197
+ qualityUpdatesEnabled && control.mediaPath === "webrtc-live" && settings ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1198
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1199
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Quality" }),
1200
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
1201
+ value: activeStreamPresetId(settings),
1202
+ onValueChange: (value) => {
1203
+ const preset = STREAM_PRESETS.find((item) => item.id === value);
1204
+ if (preset) control.setWebRTCStreamSettings({
1205
+ ...settings,
1206
+ ...preset.settings
1207
+ });
1208
+ },
1209
+ children: STREAM_PRESETS.map((preset) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
1210
+ value: preset.id,
1211
+ disabled,
1212
+ children: /* @__PURE__ */ jsxs("span", {
1213
+ className: "flex min-w-0 flex-col",
1214
+ children: [/* @__PURE__ */ jsx("span", { children: preset.label }), /* @__PURE__ */ jsx("span", {
1215
+ className: "text-xs text-muted-foreground",
1216
+ children: preset.detail
1217
+ })]
1218
+ })
1219
+ }, preset.id))
1220
+ }),
1221
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1222
+ /* @__PURE__ */ jsx(CustomStreamSettings, {
1223
+ settings,
1224
+ disabled,
1225
+ onApply: control.setWebRTCStreamSettings
1226
+ })
1227
+ ] }) : null
1228
+ ] })
1229
+ })] });
1230
+ }
1231
+ function ViewportMenu({ control, connected }) {
1232
+ return /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Monitor, {}), "Viewport"] }), /* @__PURE__ */ jsxs(DropdownMenuSubContent, {
1233
+ className: "w-64",
1234
+ children: [
1235
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Viewport" }),
1236
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
1237
+ disabled: !connected || !control.browserViewportSize,
1238
+ onClick: () => control.setViewportToBrowserSize(),
1239
+ children: [/* @__PURE__ */ jsx(Maximize2, {}), "Set to browser size"]
1240
+ }),
1241
+ /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
1242
+ disabled: !connected || !control.browserViewportSize,
1243
+ checked: control.viewportAutoSync,
1244
+ onCheckedChange: control.setViewportAutoSync,
1245
+ children: [/* @__PURE__ */ jsx(Monitor, {}), "Auto sync browser size"]
1246
+ }),
1247
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1248
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
1249
+ value: activeViewportPresetId(control.viewport),
1250
+ onValueChange: (value) => {
1251
+ const preset = VIEWPORT_PRESETS.find((item) => item.id === value);
1252
+ if (preset) control.setViewport(createViewportPreset(preset.width, preset.height, control.viewport.deviceScaleFactor));
1253
+ },
1254
+ children: VIEWPORT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, {
1255
+ value: preset.id,
1256
+ disabled: !connected,
1257
+ children: [/* @__PURE__ */ jsx(Monitor, {}), preset.label]
1258
+ }, preset.id))
1259
+ }),
1260
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1261
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Scale" }),
1262
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
1263
+ value: activeViewportScaleId(control.viewport.deviceScaleFactor),
1264
+ onValueChange: (value) => {
1265
+ const nextScale = VIEWPORT_DEVICE_SCALE_FACTORS.find((item) => formatViewportScale(item) === value);
1266
+ if (nextScale) control.setViewport(createViewportPreset(control.viewport.width, control.viewport.height, nextScale));
1267
+ },
1268
+ children: VIEWPORT_DEVICE_SCALE_FACTORS.map((deviceScaleFactor) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, {
1269
+ value: formatViewportScale(deviceScaleFactor),
1270
+ disabled: !connected,
1271
+ children: [
1272
+ /* @__PURE__ */ jsx(Monitor, {}),
1273
+ formatViewportScale(deviceScaleFactor),
1274
+ "x"
1275
+ ]
1276
+ }, deviceScaleFactor))
1277
+ }),
1278
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1279
+ /* @__PURE__ */ jsx(CustomViewportSettings, {
1280
+ viewport: control.viewport,
1281
+ connected,
1282
+ onApply: control.setViewport
1283
+ })
1284
+ ]
1285
+ })] });
1286
+ }
1287
+ function CustomViewportSettings({ viewport, connected, onApply }) {
1288
+ const [width, setWidth] = useState(String(viewport.width));
1289
+ const [height, setHeight] = useState(String(viewport.height));
1290
+ const [deviceScaleFactor, setDeviceScaleFactor] = useState(formatViewportScale(viewport.deviceScaleFactor));
1291
+ useEffect(() => {
1292
+ setWidth(String(viewport.width));
1293
+ setHeight(String(viewport.height));
1294
+ setDeviceScaleFactor(formatViewportScale(viewport.deviceScaleFactor));
1295
+ }, [viewport]);
1296
+ const nextViewport = parseViewportSettings({
1297
+ width,
1298
+ height,
1299
+ deviceScaleFactor
1300
+ });
1301
+ const unchanged = nextViewport ? viewport.width === nextViewport.width && viewport.height === nextViewport.height && viewport.deviceScaleFactor === nextViewport.deviceScaleFactor : false;
1302
+ return /* @__PURE__ */ jsxs("div", {
1303
+ className: "grid gap-2 px-2 py-1.5",
1304
+ onClick: (event) => event.stopPropagation(),
1305
+ onKeyDown: (event) => event.stopPropagation(),
1306
+ children: [
1307
+ /* @__PURE__ */ jsx(DropdownMenuLabel, {
1308
+ className: "px-0",
1309
+ children: "Custom"
1310
+ }),
1311
+ /* @__PURE__ */ jsxs("div", {
1312
+ className: "grid grid-cols-3 gap-2",
1313
+ children: [
1314
+ /* @__PURE__ */ jsx(StreamNumberField, {
1315
+ label: "Width",
1316
+ value: width,
1317
+ onChange: setWidth
1318
+ }),
1319
+ /* @__PURE__ */ jsx(StreamNumberField, {
1320
+ label: "Height",
1321
+ value: height,
1322
+ onChange: setHeight
1323
+ }),
1324
+ /* @__PURE__ */ jsx(ViewportScaleField, {
1325
+ label: "Scale",
1326
+ value: deviceScaleFactor,
1327
+ onChange: setDeviceScaleFactor
1328
+ })
1329
+ ]
1330
+ }),
1331
+ /* @__PURE__ */ jsx(Button$1, {
1332
+ type: "button",
1333
+ size: "sm",
1334
+ className: "h-7",
1335
+ disabled: !connected || !nextViewport || unchanged,
1336
+ onClick: () => {
1337
+ if (nextViewport) onApply(nextViewport);
1338
+ },
1339
+ children: "Apply"
1340
+ })
1341
+ ]
1342
+ });
1343
+ }
1344
+ function CustomStreamSettings({ settings, disabled, onApply }) {
1345
+ const [fps, setFps] = useState(String(settings.fps));
1346
+ const [bitrateKbps, setBitrateKbps] = useState(String(settings.bitrateKbps));
1347
+ useEffect(() => {
1348
+ setFps(String(settings.fps));
1349
+ setBitrateKbps(String(settings.bitrateKbps));
1350
+ }, [settings]);
1351
+ const nextSettings = parseStreamSettings({
1352
+ fps,
1353
+ bitrateKbps
1354
+ });
1355
+ const unchanged = settings.fps === nextSettings?.fps && settings.bitrateKbps === nextSettings?.bitrateKbps;
1356
+ return /* @__PURE__ */ jsxs(FieldGroup, {
1357
+ className: "gap-2 px-2 py-1.5",
1358
+ onClick: (event) => event.stopPropagation(),
1359
+ onKeyDown: (event) => event.stopPropagation(),
1360
+ children: [
1361
+ /* @__PURE__ */ jsx(DropdownMenuLabel, {
1362
+ className: "px-0",
1363
+ children: "Custom"
1364
+ }),
1365
+ /* @__PURE__ */ jsxs("div", {
1366
+ className: "grid grid-cols-2 gap-2",
1367
+ children: [/* @__PURE__ */ jsx(StreamNumberField, {
1368
+ label: "FPS",
1369
+ value: fps,
1370
+ onChange: setFps
1371
+ }), /* @__PURE__ */ jsx(StreamNumberField, {
1372
+ label: "Max Kbps",
1373
+ value: bitrateKbps,
1374
+ onChange: setBitrateKbps
1375
+ })]
1376
+ }),
1377
+ /* @__PURE__ */ jsx(Button$1, {
1378
+ type: "button",
1379
+ size: "sm",
1380
+ className: "h-7 w-full",
1381
+ disabled: disabled || !nextSettings || unchanged,
1382
+ onClick: () => {
1383
+ if (nextSettings) onApply({
1384
+ ...settings,
1385
+ ...nextSettings
1386
+ });
1387
+ },
1388
+ children: "Apply"
1389
+ })
1390
+ ]
1391
+ });
1392
+ }
1393
+ function StreamNumberField({ label, value, onChange }) {
1394
+ const id = useId();
1395
+ return /* @__PURE__ */ jsxs(Field, {
1396
+ className: "gap-1",
1397
+ children: [/* @__PURE__ */ jsx(FieldLabel, {
1398
+ htmlFor: id,
1399
+ children: label
1400
+ }), /* @__PURE__ */ jsx(Input$1, {
1401
+ id,
1402
+ type: "text",
1403
+ inputMode: "numeric",
1404
+ value,
1405
+ onChange: (event) => onChange(digitsOnly(event.currentTarget.value)),
1406
+ onFocus: (event) => event.currentTarget.select(),
1407
+ className: "h-7"
1408
+ })]
1409
+ });
1410
+ }
1411
+ function ViewportScaleField({ label, value, onChange }) {
1412
+ const id = useId();
1413
+ return /* @__PURE__ */ jsxs(Field, {
1414
+ className: "gap-1",
1415
+ children: [/* @__PURE__ */ jsx(FieldLabel, {
1416
+ htmlFor: id,
1417
+ children: label
1418
+ }), /* @__PURE__ */ jsx(Input$1, {
1419
+ id,
1420
+ type: "text",
1421
+ inputMode: "decimal",
1422
+ value,
1423
+ onChange: (event) => onChange(decimalNumber(event.currentTarget.value)),
1424
+ onFocus: (event) => event.currentTarget.select(),
1425
+ className: "h-7"
1426
+ })]
1427
+ });
1428
+ }
1429
+ function parseViewportSettings({ width, height, deviceScaleFactor }) {
1430
+ if (!width || !height || !deviceScaleFactor) return null;
1431
+ const parsedWidth = Number(width);
1432
+ const parsedHeight = Number(height);
1433
+ const parsedScale = Number(deviceScaleFactor);
1434
+ if (!Number.isFinite(parsedWidth) || !Number.isFinite(parsedHeight) || !Number.isFinite(parsedScale)) return null;
1435
+ const nextWidth = clampInteger(parsedWidth, VIEWPORT_LIMITS.width.min, VIEWPORT_LIMITS.width.max);
1436
+ const nextHeight = clampInteger(parsedHeight, VIEWPORT_LIMITS.height.min, VIEWPORT_LIMITS.height.max);
1437
+ const maxScale = Math.min(VIEWPORT_LIMITS.deviceScaleFactor.max, VIEWPORT_LIMITS.width.max / nextWidth, VIEWPORT_LIMITS.height.max / nextHeight);
1438
+ return createViewportPreset(nextWidth, nextHeight, clampDecimal(parsedScale, VIEWPORT_LIMITS.deviceScaleFactor.min, maxScale));
1439
+ }
1440
+ function parseStreamSettings({ fps, bitrateKbps }) {
1441
+ if (!fps || !bitrateKbps) return null;
1442
+ return {
1443
+ fps: clampInteger(Number(fps), STREAM_LIMITS.fps.min, STREAM_LIMITS.fps.max),
1444
+ bitrateKbps: clampInteger(Number(bitrateKbps), STREAM_LIMITS.bitrateKbps.min, STREAM_LIMITS.bitrateKbps.max)
1445
+ };
1446
+ }
1447
+ function digitsOnly(value) {
1448
+ return value.replace(/\D/g, "");
1449
+ }
1450
+ function decimalNumber(value) {
1451
+ const [integer = "", ...fraction] = value.replace(/[^\d.]/g, "").split(".");
1452
+ return fraction.length ? `${integer}.${fraction.join("")}` : integer;
1453
+ }
1454
+ function clampInteger(value, min, max) {
1455
+ return Math.min(Math.max(Math.round(value), min), max);
1456
+ }
1457
+ function clampDecimal(value, min, max) {
1458
+ return Math.min(Math.max(Math.round(value * 100) / 100, min), max);
1459
+ }
1460
+ function activeViewportPresetId(viewport) {
1461
+ return VIEWPORT_PRESETS.find((item) => viewport.width === item.width && viewport.height === item.height)?.id ?? "";
1462
+ }
1463
+ function activeViewportScaleId(deviceScaleFactor) {
1464
+ const preset = VIEWPORT_DEVICE_SCALE_FACTORS.find((item) => item === deviceScaleFactor);
1465
+ return preset ? formatViewportScale(preset) : "";
1466
+ }
1467
+ function activeStreamPresetId(settings) {
1468
+ if (!settings) return "";
1469
+ return STREAM_PRESETS.find((item) => settings.fps === item.settings.fps && settings.bitrateKbps === item.settings.bitrateKbps)?.id ?? "";
1470
+ }
1471
+ function formatElapsed(startedAt, now) {
1472
+ const elapsed = Math.max(0, Math.floor((now - Date.parse(startedAt)) / 1e3));
1473
+ const hours = Math.floor(elapsed / 3600);
1474
+ const minutes = Math.floor(elapsed % 3600 / 60);
1475
+ const seconds = elapsed % 60;
1476
+ return hours > 0 ? `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}` : `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
1477
+ }
1478
+ //#endregion
1479
+ //#region ../ui/src/components/avatar.tsx
1480
+ function Avatar$1({ className, size = "default", ...props }) {
1481
+ return /* @__PURE__ */ jsx(Avatar.Root, {
1482
+ "data-slot": "avatar",
1483
+ "data-size": size,
1484
+ className: cn("group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten", className),
1485
+ ...props
1486
+ });
1487
+ }
1488
+ function AvatarImage({ className, ...props }) {
1489
+ return /* @__PURE__ */ jsx(Avatar.Image, {
1490
+ "data-slot": "avatar-image",
1491
+ className: cn("aspect-square size-full rounded-full object-cover", className),
1492
+ ...props
1493
+ });
1494
+ }
1495
+ function AvatarFallback({ className, ...props }) {
1496
+ return /* @__PURE__ */ jsx(Avatar.Fallback, {
1497
+ "data-slot": "avatar-fallback",
1498
+ className: cn("flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs", className),
1499
+ ...props
1500
+ });
1501
+ }
1502
+ function AvatarBadge({ className, ...props }) {
1503
+ return /* @__PURE__ */ jsx("span", {
1504
+ "data-slot": "avatar-badge",
1505
+ className: cn("absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none", "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden", "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", className),
1506
+ ...props
1507
+ });
1508
+ }
1509
+ //#endregion
1510
+ //#region ../ui/src/components/popover.tsx
1511
+ function Popover$1({ ...props }) {
1512
+ return /* @__PURE__ */ jsx(Popover.Root, {
1513
+ "data-slot": "popover",
1514
+ ...props
1515
+ });
1516
+ }
1517
+ function PopoverTrigger({ ...props }) {
1518
+ return /* @__PURE__ */ jsx(Popover.Trigger, {
1519
+ "data-slot": "popover-trigger",
1520
+ ...props
1521
+ });
1522
+ }
1523
+ function PopoverContent({ className, align = "center", alignOffset = 0, side = "bottom", sideOffset = 4, ...props }) {
1524
+ return /* @__PURE__ */ jsx(Popover.Portal, {
1525
+ container: usePortalContainer(),
1526
+ children: /* @__PURE__ */ jsx(Popover.Positioner, {
1527
+ align,
1528
+ alignOffset,
1529
+ side,
1530
+ sideOffset,
1531
+ className: "isolate z-50",
1532
+ children: /* @__PURE__ */ jsx(Popover.Popup, {
1533
+ "data-slot": "popover-content",
1534
+ className: cn("z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
1535
+ ...props
1536
+ })
1537
+ })
1538
+ });
1539
+ }
1540
+ function PopoverTitle({ className, ...props }) {
1541
+ return /* @__PURE__ */ jsx(Popover.Title, {
1542
+ "data-slot": "popover-title",
1543
+ className: cn("font-medium", className),
1544
+ ...props
1545
+ });
1546
+ }
1547
+ //#endregion
1548
+ //#region src/components/collaboration-presence.tsx
1549
+ const visibleParticipantCount = 5;
1550
+ function CollaborationPresence({ collaboration }) {
1551
+ const participants = [...collaboration.participants].sort((left, right) => {
1552
+ if (left.clientId === collaboration.clientId) return -1;
1553
+ if (right.clientId === collaboration.clientId) return 1;
1554
+ if (left.holdingInput !== right.holdingInput) return left.holdingInput ? -1 : 1;
1555
+ return left.name.localeCompare(right.name);
1556
+ });
1557
+ const visible = participants.slice(0, visibleParticipantCount);
1558
+ const overflow = participants.length - visible.length;
1559
+ if (visible.length === 0) return null;
1560
+ return /* @__PURE__ */ jsxs("div", {
1561
+ className: "group flex shrink-0 items-center px-2",
1562
+ "aria-label": "Session participants",
1563
+ children: [overflow > 0 ? /* @__PURE__ */ jsx("div", {
1564
+ className: "relative transition-[margin] duration-200",
1565
+ style: { zIndex: visible.length + 1 },
1566
+ children: /* @__PURE__ */ jsx(OverflowParticipants, {
1567
+ overflow,
1568
+ participants,
1569
+ collaboration
1570
+ })
1571
+ }) : null, visible.map((participant, index) => /* @__PURE__ */ jsx("div", {
1572
+ className: cn("relative transition-[margin] duration-200", (index > 0 || overflow > 0) && "-ml-4 group-hover:ml-0 group-focus-within:ml-0"),
1573
+ style: { zIndex: visible.length - index },
1574
+ children: /* @__PURE__ */ jsx(ParticipantButton, {
1575
+ participant,
1576
+ collaboration
1577
+ })
1578
+ }, participant.clientId))]
1579
+ });
1580
+ }
1581
+ function OverflowParticipants({ overflow, participants, collaboration }) {
1582
+ return /* @__PURE__ */ jsxs(Popover$1, { children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
1583
+ render: /* @__PURE__ */ jsx(Button$1, {
1584
+ type: "button",
1585
+ variant: "ghost",
1586
+ size: "icon-sm",
1587
+ className: "shrink-0 rounded-full border bg-background text-[0.65rem] text-muted-foreground",
1588
+ "aria-label": `${overflow} more participants`
1589
+ }),
1590
+ children: ["+", overflow]
1591
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
1592
+ align: "end",
1593
+ className: "w-auto gap-2",
1594
+ children: [/* @__PURE__ */ jsx(PopoverTitle, {
1595
+ className: "text-xs text-muted-foreground",
1596
+ children: "Everyone"
1597
+ }), /* @__PURE__ */ jsx("div", {
1598
+ className: "flex flex-wrap items-center gap-1",
1599
+ children: participants.map((participant) => /* @__PURE__ */ jsx(ParticipantButton, {
1600
+ participant,
1601
+ collaboration
1602
+ }, participant.clientId))
1603
+ })]
1604
+ })] });
1605
+ }
1606
+ function ParticipantButton({ participant, collaboration }) {
1607
+ const local = participant.clientId === collaboration.clientId;
1608
+ const followable = participant.role !== "automation";
1609
+ const following = participant.clientId === collaboration.followingClientId;
1610
+ const label = local ? `${participant.name} (you)` : !followable ? participant.name : following ? `${participant.name} · click to stop following` : `${participant.name} · click to follow`;
1611
+ return /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1612
+ render: local || !followable ? /* @__PURE__ */ jsx("span", {
1613
+ className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full",
1614
+ "aria-label": label
1615
+ }) : /* @__PURE__ */ jsx(Button$1, {
1616
+ type: "button",
1617
+ variant: following ? "secondary" : "ghost",
1618
+ size: "icon-sm",
1619
+ className: "shrink-0 rounded-full p-0",
1620
+ "aria-label": label,
1621
+ "aria-pressed": following,
1622
+ onClick: () => collaboration.follow(following ? null : participant.clientId)
1623
+ }),
1624
+ children: /* @__PURE__ */ jsx(ParticipantAvatar, {
1625
+ participant,
1626
+ following
1627
+ })
1628
+ }), /* @__PURE__ */ jsx(TooltipContent, {
1629
+ side: "bottom",
1630
+ children: label
1631
+ })] });
1632
+ }
1633
+ function ParticipantAvatar({ participant, following }) {
1634
+ const controlClaimed = participant.holdingInput && participant.leaseMode === "explicit";
1635
+ return /* @__PURE__ */ jsxs(Avatar$1, {
1636
+ size: "sm",
1637
+ className: cn("shrink-0 ring-2 ring-background", following && "ring-ring"),
1638
+ children: [
1639
+ /* @__PURE__ */ jsx(AvatarImage, {
1640
+ src: gravatarURL(participant.avatarHash),
1641
+ alt: ""
1642
+ }),
1643
+ /* @__PURE__ */ jsx(AvatarFallback, { children: initials(participant.name) }),
1644
+ participant.holdingInput ? /* @__PURE__ */ jsx(AvatarBadge, {
1645
+ className: cn("bg-emerald-500", controlClaimed && "group-data-[size=sm]/avatar:size-3 group-data-[size=sm]/avatar:[&>svg]:!block group-data-[size=sm]/avatar:[&>svg]:size-2"),
1646
+ title: controlClaimed ? "Control claimed" : "Has input control",
1647
+ children: controlClaimed ? /* @__PURE__ */ jsx(Lock, {}) : /* @__PURE__ */ jsx(Radio, {})
1648
+ }) : null
1649
+ ]
1650
+ });
1651
+ }
1652
+ function gravatarURL(hash) {
1653
+ return `https://www.gravatar.com/avatar/${encodeURIComponent(hash)}?d=identicon&s=64`;
1654
+ }
1655
+ function initials(name) {
1656
+ return name.split(/\s+/).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
1657
+ }
1658
+ //#endregion
1659
+ //#region src/components/browser-toolbar.tsx
1660
+ function BrowserToolbar({ control, leading, features, collaborationRole, cdpUrl, shareUrls, localCursorEnabled, onLocalCursorChange, paintingEnabled, onPaintingEnabledChange, devToolsOpen, devToolsTargetIds, devToolsDock, onDevToolsOpenChange, onDevToolsDockChange, onSessionDetails }) {
1661
+ const [urlDraft, setUrlDraft] = useState(null);
1662
+ const displayUrl = control.activeTarget?.url ?? "";
1663
+ const busy = control.phase === "connecting";
1664
+ const connected = control.phase === "connected";
1665
+ const browserMutationEnabled = connected && collaborationRole !== "viewer";
1666
+ const drawingAvailable = connected && control.collaboration.phase === "connected" && Boolean(control.activeTargetId);
1667
+ const loading = control.activeTarget?.loading ?? false;
1668
+ const runningRecordings = control.recordings.filter((recording) => recording.status === "starting" || recording.status === "running");
1669
+ const hasRunningRecordings = runningRecordings.length > 0;
1670
+ const recordingTargetIds = new Set(runningRecordings.map((recording) => recording.targetId));
1671
+ const [recordingNow, setRecordingNow] = useState(Date.now());
1672
+ useFork(() => hasRunningRecordings ? Effect.repeat(Effect.sync(() => setRecordingNow(Date.now())), Schedule.spaced(1e3)) : void 0, [hasRunningRecordings]);
1673
+ function handleNavigate(value) {
1674
+ const nextUrl = value.trim();
1675
+ if (!nextUrl) return;
1676
+ control.navigate(normalizeUrl(nextUrl));
1677
+ setUrlDraft(null);
1678
+ }
1679
+ return /* @__PURE__ */ jsxs("div", {
1680
+ className: "flex min-w-0 flex-col bg-background",
1681
+ children: [leading || features.tabs || features.presence ? /* @__PURE__ */ jsxs("div", {
1682
+ "data-workbench-titlebar": true,
1683
+ className: "flex min-w-0 shrink-0 items-stretch border-b bg-muted/35",
1684
+ children: [
1685
+ leading,
1686
+ features.tabs ? /* @__PURE__ */ jsx(BrowserTabStrip, {
1687
+ targets: control.targets,
1688
+ activeTargetId: control.activeTargetId,
1689
+ recordingTargetIds,
1690
+ devToolsTargetIds,
1691
+ disabled: !connected,
1692
+ mutationDisabled: !browserMutationEnabled,
1693
+ onActivate: control.activateTarget,
1694
+ onCreate: () => control.createTarget("about:blank"),
1695
+ onDuplicate: control.duplicateTarget,
1696
+ onClose: control.closeTarget,
1697
+ onReload: control.reload,
1698
+ onReorder: control.reorderTargets
1699
+ }) : /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1" }),
1700
+ features.presence ? /* @__PURE__ */ jsx(CollaborationPresence, { collaboration: control.collaboration }) : null
1701
+ ]
1702
+ }) : null, features.navigation || features.addressBar || features.drawing || features.devTools || features.menus ? /* @__PURE__ */ jsxs("div", {
1703
+ className: "flex h-9 items-center gap-1 px-1.5",
1704
+ children: [
1705
+ features.navigation ? /* @__PURE__ */ jsxs("div", {
1706
+ className: "flex shrink-0 items-center gap-0.5",
1707
+ children: [
1708
+ /* @__PURE__ */ jsx(ToolbarButton, {
1709
+ label: "Back",
1710
+ disabled: !browserMutationEnabled,
1711
+ onClick: () => control.historyBack(),
1712
+ children: /* @__PURE__ */ jsx(ArrowLeft, {})
1713
+ }),
1714
+ /* @__PURE__ */ jsx(ToolbarButton, {
1715
+ label: "Forward",
1716
+ disabled: !browserMutationEnabled,
1717
+ onClick: () => control.historyForward(),
1718
+ children: /* @__PURE__ */ jsx(ArrowRight, {})
1719
+ }),
1720
+ /* @__PURE__ */ jsx(ToolbarButton, {
1721
+ label: loading ? "Stop loading" : "Reload",
1722
+ disabled: !browserMutationEnabled,
1723
+ onClick: () => {
1724
+ if (loading) control.stopLoading();
1725
+ else if (control.activeTargetId) control.reload(control.activeTargetId);
1726
+ },
1727
+ children: loading ? /* @__PURE__ */ jsx(Square, {}) : /* @__PURE__ */ jsx(RefreshCw, {})
1728
+ })
1729
+ ]
1730
+ }) : null,
1731
+ features.addressBar ? /* @__PURE__ */ jsx(InputGroup, {
1732
+ className: "h-7 border-transparent bg-transparent transition-colors hover:border-input/50 hover:bg-muted/35 has-[[data-slot=input-group-control]:focus-visible]:border-input/70 has-[[data-slot=input-group-control]:focus-visible]:bg-background has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/20 dark:hover:bg-input/20",
1733
+ children: /* @__PURE__ */ jsx(InputGroupInput, {
1734
+ value: urlDraft ?? displayUrl,
1735
+ onChange: (event) => setUrlDraft(event.target.value),
1736
+ onFocus: (event) => event.currentTarget.select(),
1737
+ onKeyDown: (event) => {
1738
+ if (event.key === "Enter") {
1739
+ event.preventDefault();
1740
+ handleNavigate(event.currentTarget.value);
1741
+ }
1742
+ },
1743
+ placeholder: "URL",
1744
+ className: "h-7 px-2 font-mono text-xs text-muted-foreground transition-colors focus-visible:text-foreground",
1745
+ disabled: !browserMutationEnabled
1746
+ })
1747
+ }) : /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1" }),
1748
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 shrink-0 animate-spin text-muted-foreground" }) : null,
1749
+ features.drawing ? /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1750
+ render: /* @__PURE__ */ jsx(Button$1, {
1751
+ type: "button",
1752
+ variant: paintingEnabled ? "secondary" : "ghost",
1753
+ size: "icon-sm",
1754
+ className: "shrink-0",
1755
+ disabled: !paintingEnabled && !drawingAvailable,
1756
+ "aria-label": paintingEnabled ? "Stop drawing" : "Draw on this tab",
1757
+ "aria-pressed": paintingEnabled,
1758
+ onClick: () => onPaintingEnabledChange(!paintingEnabled)
1759
+ }),
1760
+ children: /* @__PURE__ */ jsx(Pencil, {})
1761
+ }), /* @__PURE__ */ jsx(TooltipContent, {
1762
+ side: "bottom",
1763
+ children: paintingEnabled ? "Stop drawing" : "Draw on this tab"
1764
+ })] }) : null,
1765
+ features.devTools ? /* @__PURE__ */ jsx(DevToolsButton, {
1766
+ open: devToolsOpen,
1767
+ dock: devToolsDock,
1768
+ available: collaborationRole === "owner" && connected && Boolean(cdpUrl && control.activeTargetId),
1769
+ onOpenChange: onDevToolsOpenChange,
1770
+ onDockChange: onDevToolsDockChange
1771
+ }) : null,
1772
+ features.menus ? /* @__PURE__ */ jsx(BrowserMenus, {
1773
+ control,
1774
+ cdpUrl,
1775
+ shareUrls,
1776
+ busy,
1777
+ connected,
1778
+ localCursorEnabled,
1779
+ onLocalCursorChange,
1780
+ onReconnect: () => control.reconnect(),
1781
+ onSessionDetails,
1782
+ now: recordingNow
1783
+ }) : null
1784
+ ]
1785
+ }) : null]
1786
+ });
1787
+ }
1788
+ function DevToolsButton({ open, dock, available, onOpenChange, onDockChange }) {
1789
+ const label = open ? "Close DevTools" : "Open DevTools";
1790
+ return /* @__PURE__ */ jsxs(ContextMenu$1, { children: [/* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1791
+ render: /* @__PURE__ */ jsx(ContextMenuTrigger, { render: /* @__PURE__ */ jsx(Button$1, {
1792
+ type: "button",
1793
+ variant: open ? "secondary" : "ghost",
1794
+ size: "icon-sm",
1795
+ disabled: !open && !available,
1796
+ "aria-label": label,
1797
+ "aria-pressed": open,
1798
+ onClick: () => onOpenChange(!open)
1799
+ }) }),
1800
+ children: /* @__PURE__ */ jsx(Wrench, {})
1801
+ }), /* @__PURE__ */ jsxs(TooltipContent, {
1802
+ side: "bottom",
1803
+ children: [label, ". Right-click to choose dock side."]
1804
+ })] }), /* @__PURE__ */ jsx(ContextMenuContent, { children: /* @__PURE__ */ jsxs(ContextMenuGroup, { children: [/* @__PURE__ */ jsx(ContextMenuLabel, { children: "Dock side" }), /* @__PURE__ */ jsxs(ContextMenuRadioGroup, {
1805
+ value: dock,
1806
+ onValueChange: (value) => {
1807
+ switch (value) {
1808
+ case "bottom":
1809
+ onDockChange("bottom");
1810
+ break;
1811
+ case "right":
1812
+ onDockChange("right");
1813
+ break;
1814
+ }
1815
+ },
1816
+ children: [/* @__PURE__ */ jsxs(ContextMenuRadioItem, {
1817
+ value: "bottom",
1818
+ children: [/* @__PURE__ */ jsx(PanelBottom, {}), "Bottom"]
1819
+ }), /* @__PURE__ */ jsxs(ContextMenuRadioItem, {
1820
+ value: "right",
1821
+ children: [/* @__PURE__ */ jsx(PanelRight, {}), "Right"]
1822
+ })]
1823
+ })] }) })] });
1824
+ }
1825
+ function ToolbarButton({ label, disabled, onClick, children }) {
1826
+ return /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1827
+ render: /* @__PURE__ */ jsx(Button$1, {
1828
+ type: "button",
1829
+ variant: "ghost",
1830
+ size: "icon-sm",
1831
+ disabled,
1832
+ onClick
1833
+ }),
1834
+ children
1835
+ }), /* @__PURE__ */ jsx(TooltipContent, {
1836
+ side: "bottom",
1837
+ children: label
1838
+ })] });
1839
+ }
1840
+ function normalizeUrl(value) {
1841
+ const trimmed = value.trim();
1842
+ if (isLocalHost(trimmed)) return `http://${trimmed}`;
1843
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) || /^(about|chrome|devtools|data|file):/i.test(trimmed)) return trimmed;
1844
+ if (isLikelyHost(trimmed)) return `https://${trimmed}`;
1845
+ return `https://www.google.com/search?q=${encodeURIComponent(trimmed)}`;
1846
+ }
1847
+ function isLocalHost(value) {
1848
+ return /^localhost(?::\d+)?(?:[/?#].*)?$/i.test(value) || /^127(?:\.\d{1,3}){3}(?::\d+)?(?:[/?#].*)?$/.test(value) || /^[a-z0-9-]+:\d+(?:[/?#].*)?$/i.test(value);
1849
+ }
1850
+ function isLikelyHost(value) {
1851
+ return !/\s/.test(value) && /^[a-z0-9-]+(?:\.[a-z0-9-]+)+(?:[/:?#].*)?$/i.test(value);
1852
+ }
1853
+ //#endregion
1854
+ //#region ../ui/src/components/badge.tsx
1855
+ const badgeVariants = cva("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", {
1856
+ variants: { variant: {
1857
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
1858
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
1859
+ destructive: "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
1860
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
1861
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
1862
+ link: "text-primary underline-offset-4 hover:underline"
1863
+ } },
1864
+ defaultVariants: { variant: "default" }
1865
+ });
1866
+ function Badge({ className, variant = "default", render, ...props }) {
1867
+ return useRender({
1868
+ defaultTagName: "span",
1869
+ props: mergeProps({ className: cn(badgeVariants({ variant }), className) }, props),
1870
+ render,
1871
+ state: {
1872
+ slot: "badge",
1873
+ variant
1874
+ }
1875
+ });
1876
+ }
1877
+ //#endregion
1878
+ //#region src/notices.ts
1879
+ function showNotice(notice) {
1880
+ if (notice.level === "error") toast.error(notice.message);
1881
+ else toast.success(notice.message);
1882
+ }
1883
+ //#endregion
1884
+ //#region src/components/browser-viewport.tsx
1885
+ function BrowserViewport({ statusBadge = true, className, ...props }) {
1886
+ return /* @__PURE__ */ jsx(SessionViewport, {
1887
+ ...props,
1888
+ className: className ?? "bg-background",
1889
+ onNotice: showNotice,
1890
+ renderOverlay: (overlay) => /* @__PURE__ */ jsx(ViewportOverlay, {
1891
+ overlay,
1892
+ statusBadge
1893
+ })
1894
+ });
1895
+ }
1896
+ function ViewportOverlay({ overlay, statusBadge }) {
1897
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1898
+ overlay.placeholder ? /* @__PURE__ */ jsx("div", {
1899
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center",
1900
+ children: /* @__PURE__ */ jsx(ViewportPlaceholder, { placeholder: overlay.placeholder })
1901
+ }) : null,
1902
+ statusBadge ? /* @__PURE__ */ jsx("div", {
1903
+ className: "pointer-events-none absolute right-2 bottom-2 flex items-center gap-1.5",
1904
+ children: /* @__PURE__ */ jsx(StatusBadge, { status: overlay.status })
1905
+ }) : null,
1906
+ overlay.followedCursor ? /* @__PURE__ */ jsxs("div", {
1907
+ className: "pointer-events-none absolute z-30 flex translate-x-[-2px] translate-y-[-2px] items-start text-primary drop-shadow-sm",
1908
+ style: {
1909
+ left: overlay.followedCursor.x,
1910
+ top: overlay.followedCursor.y
1911
+ },
1912
+ children: [/* @__PURE__ */ jsx(MousePointer2, { className: "size-5 fill-primary stroke-background stroke-[1.5]" }), /* @__PURE__ */ jsx("span", {
1913
+ className: "mt-4 -ml-1 rounded bg-primary px-1.5 py-0.5 text-[10px] leading-none font-medium whitespace-nowrap text-primary-foreground",
1914
+ children: overlay.followedCursor.name
1915
+ })]
1916
+ }) : null,
1917
+ overlay.cursorHint ? /* @__PURE__ */ jsx("div", {
1918
+ className: "pointer-events-none absolute z-20 max-w-64 translate-x-3 translate-y-3 rounded-md border bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md",
1919
+ style: {
1920
+ left: overlay.cursorHint.x,
1921
+ top: overlay.cursorHint.y
1922
+ },
1923
+ children: overlay.cursorHint.text
1924
+ }) : null,
1925
+ overlay.collaborationError ? /* @__PURE__ */ jsx("div", {
1926
+ className: "pointer-events-none absolute bottom-10 left-2 max-w-[80%] rounded-md border border-amber-500/40 bg-background/90 px-2 py-1 text-xs text-amber-800 dark:text-amber-300",
1927
+ children: overlay.collaborationError
1928
+ }) : null
1929
+ ] });
1930
+ }
1931
+ const placeholderLabels = {
1932
+ connecting: "Connecting",
1933
+ "connecting-media": "Connecting media",
1934
+ switching: "Switching target",
1935
+ disconnected: "Disconnected",
1936
+ waiting: "Waiting for frame"
1937
+ };
1938
+ function ViewportPlaceholder({ placeholder }) {
1939
+ return /* @__PURE__ */ jsxs("div", {
1940
+ className: "flex flex-col items-center gap-2 text-sm text-muted-foreground",
1941
+ children: [placeholder === "disconnected" ? /* @__PURE__ */ jsx(Unplug, { className: "size-5" }) : /* @__PURE__ */ jsx(Loader2, { className: "size-5 animate-spin" }), placeholderLabels[placeholder]]
1942
+ });
1943
+ }
1944
+ function StatusBadge({ status }) {
1945
+ if (status === "webrtc" || status === "websocket") return /* @__PURE__ */ jsx(Badge, {
1946
+ variant: "secondary",
1947
+ className: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
1948
+ children: status
1949
+ });
1950
+ return /* @__PURE__ */ jsx(Badge, {
1951
+ variant: "outline",
1952
+ children: "offline"
1953
+ });
1954
+ }
1955
+ //#endregion
1956
+ //#region src/components/browser-devtools-pane.tsx
1957
+ function BrowserDevToolsPane({ cdpUrl, targetId }) {
1958
+ const src = useMemo(() => {
1959
+ if (!cdpUrl || !targetId) return null;
1960
+ const cdp = new URL(cdpUrl);
1961
+ const cdpPath = cdp.pathname.replace(/\/$/, "");
1962
+ const frame = new URL(`${cdpPath}/devtools/devtools_app.html`, cdp.origin);
1963
+ const socket = `${cdp.host}${cdpPath}/devtools/page/${encodeURIComponent(targetId)}`;
1964
+ frame.searchParams.set(cdp.protocol === "https:" ? "wss" : "ws", socket);
1965
+ return frame.toString();
1966
+ }, [cdpUrl, targetId]);
1967
+ if (!src) return /* @__PURE__ */ jsx(Empty, {
1968
+ className: "h-full rounded-none border-none",
1969
+ children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
1970
+ /* @__PURE__ */ jsx(EmptyMedia, {
1971
+ variant: "icon",
1972
+ children: /* @__PURE__ */ jsx(Wrench, {})
1973
+ }),
1974
+ /* @__PURE__ */ jsx(EmptyTitle, { children: "DevTools unavailable" }),
1975
+ /* @__PURE__ */ jsx(EmptyDescription, { children: "Connect to a browser target to inspect it." })
1976
+ ] })
1977
+ });
1978
+ return /* @__PURE__ */ jsx("iframe", {
1979
+ className: "h-full w-full border-0 bg-background",
1980
+ src,
1981
+ title: "Browser DevTools",
1982
+ allow: "clipboard-read; clipboard-write",
1983
+ referrerPolicy: "no-referrer"
1984
+ });
1985
+ }
1986
+ //#endregion
1987
+ //#region ../ui/src/components/resizable.tsx
1988
+ function ResizablePanelGroup({ className, ...props }) {
1989
+ return /* @__PURE__ */ jsx(ResizablePrimitive.Group, {
1990
+ "data-slot": "resizable-panel-group",
1991
+ className: cn("flex h-full w-full aria-[orientation=vertical]:flex-col", className),
1992
+ ...props
1993
+ });
1994
+ }
1995
+ function ResizablePanel({ ...props }) {
1996
+ return /* @__PURE__ */ jsx(ResizablePrimitive.Panel, {
1997
+ "data-slot": "resizable-panel",
1998
+ ...props
1999
+ });
2000
+ }
2001
+ function ResizableHandle({ withHandle, className, ...props }) {
2002
+ return /* @__PURE__ */ jsx(ResizablePrimitive.Separator, {
2003
+ "data-slot": "resizable-handle",
2004
+ className: cn("relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90", className),
2005
+ ...props,
2006
+ children: withHandle && /* @__PURE__ */ jsx("div", { className: "z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" })
2007
+ });
2008
+ }
2009
+ //#endregion
2010
+ //#region src/components/browser-control-pane.tsx
2011
+ const LOCAL_CURSOR_STORAGE_KEY = "aperture.workbench.localCursorEnabled";
2012
+ function BrowserControlPane({ control, leading, features: featureOverrides, collaborationRole, cdpUrl, shareUrls, onSessionDetails }) {
2013
+ const features = resolveSessionFeatures(featureOverrides);
2014
+ const [localCursorEnabled, setLocalCursorEnabled] = useState(true);
2015
+ const [paintingEnabled, setPaintingEnabled] = useState(false);
2016
+ const [devToolsTargetIds, setDevToolsTargetIds] = useState(() => /* @__PURE__ */ new Set());
2017
+ const [devToolsDock, setDevToolsDock] = useState("bottom");
2018
+ const devToolsPanelRef = usePanelRef();
2019
+ const activeTargetId = control.activeTargetId;
2020
+ const devToolsOpen = features.devTools && activeTargetId !== null && devToolsTargetIds.has(activeTargetId);
2021
+ useEffect(() => {
2022
+ try {
2023
+ if (window.localStorage.getItem(LOCAL_CURSOR_STORAGE_KEY) === "false") setLocalCursorEnabled(false);
2024
+ } catch {}
2025
+ }, []);
2026
+ useEffect(() => {
2027
+ const targetIds = new Set(control.targets.map((target) => target.id));
2028
+ setDevToolsTargetIds((current) => {
2029
+ let changed = false;
2030
+ const next = /* @__PURE__ */ new Set();
2031
+ for (const targetId of current) if (targetIds.has(targetId)) next.add(targetId);
2032
+ else changed = true;
2033
+ return changed ? next : current;
2034
+ });
2035
+ }, [control.targets]);
2036
+ useLayoutEffect(() => {
2037
+ if (devToolsOpen) devToolsPanelRef.current?.expand();
2038
+ else devToolsPanelRef.current?.collapse();
2039
+ }, [devToolsOpen, devToolsPanelRef]);
2040
+ function handleDevToolsOpenChange(open) {
2041
+ if (!activeTargetId) return;
2042
+ setDevToolsTargetIds((current) => {
2043
+ const next = new Set(current);
2044
+ if (open) next.add(activeTargetId);
2045
+ else next.delete(activeTargetId);
2046
+ return next;
2047
+ });
2048
+ }
2049
+ function handleLocalCursorChange(enabled) {
2050
+ setLocalCursorEnabled(enabled);
2051
+ try {
2052
+ window.localStorage.setItem(LOCAL_CURSOR_STORAGE_KEY, String(enabled));
2053
+ } catch {}
2054
+ }
2055
+ return /* @__PURE__ */ jsxs("div", {
2056
+ className: "flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
2057
+ children: [/* @__PURE__ */ jsx(BrowserToolbar, {
2058
+ control,
2059
+ leading,
2060
+ features,
2061
+ collaborationRole,
2062
+ cdpUrl,
2063
+ shareUrls,
2064
+ localCursorEnabled,
2065
+ onLocalCursorChange: handleLocalCursorChange,
2066
+ paintingEnabled,
2067
+ onPaintingEnabledChange: setPaintingEnabled,
2068
+ devToolsOpen,
2069
+ devToolsTargetIds,
2070
+ devToolsDock,
2071
+ onDevToolsOpenChange: handleDevToolsOpenChange,
2072
+ onDevToolsDockChange: setDevToolsDock,
2073
+ onSessionDetails
2074
+ }), /* @__PURE__ */ jsxs(ResizablePanelGroup, {
2075
+ orientation: devToolsDock === "bottom" ? "vertical" : "horizontal",
2076
+ className: "min-h-0 min-w-0 flex-1 has-[[data-separator=active]]:[&_iframe]:pointer-events-none",
2077
+ children: [
2078
+ /* @__PURE__ */ jsx(ResizablePanel, {
2079
+ className: "flex min-h-0 min-w-0",
2080
+ defaultSize: "60%",
2081
+ minSize: "20%",
2082
+ children: /* @__PURE__ */ jsx(BrowserViewport, {
2083
+ control,
2084
+ viewport: control.viewport,
2085
+ localCursorEnabled,
2086
+ paintingEnabled: features.drawing && paintingEnabled,
2087
+ onPaintingEnabledChange: setPaintingEnabled,
2088
+ statusBadge: features.statusBadge
2089
+ })
2090
+ }),
2091
+ /* @__PURE__ */ jsx(ResizableHandle, {
2092
+ withHandle: true,
2093
+ disabled: !devToolsOpen,
2094
+ hidden: !devToolsOpen
2095
+ }),
2096
+ /* @__PURE__ */ jsx(ResizablePanel, {
2097
+ panelRef: devToolsPanelRef,
2098
+ className: "flex min-h-0 min-w-0",
2099
+ collapsible: true,
2100
+ defaultSize: "40%",
2101
+ minSize: "20%",
2102
+ children: Array.from(features.devTools ? devToolsTargetIds : [], (targetId) => /* @__PURE__ */ jsx("div", {
2103
+ className: "h-full min-h-0 w-full min-w-0",
2104
+ hidden: targetId !== activeTargetId,
2105
+ children: /* @__PURE__ */ jsx(BrowserDevToolsPane, {
2106
+ cdpUrl,
2107
+ targetId
2108
+ })
2109
+ }, targetId))
2110
+ })
2111
+ ]
2112
+ })]
2113
+ });
2114
+ }
2115
+ //#endregion
2116
+ //#region src/aperture-session.tsx
2117
+ function ApertureSession({ baseUrl, theme = "system", className, ...props }) {
2118
+ const [root, setRoot] = useState(null);
2119
+ const dark = useDarkTheme(theme);
2120
+ return /* @__PURE__ */ jsx("div", {
2121
+ ref: setRoot,
2122
+ className: cn("aperture-root relative h-full w-full", dark && "dark", className),
2123
+ children: root ? /* @__PURE__ */ jsx(ApertureProvider, {
2124
+ baseUrl,
2125
+ children: /* @__PURE__ */ jsx(PortalContainerProvider, {
2126
+ container: root,
2127
+ children: /* @__PURE__ */ jsxs(TooltipProvider, { children: [/* @__PURE__ */ jsx(SharedSession, { ...props }), props.features?.toaster !== false ? /* @__PURE__ */ jsx(Toaster$1, {
2128
+ theme: dark ? "dark" : "light",
2129
+ richColors: true,
2130
+ closeButton: true,
2131
+ position: "bottom-center"
2132
+ }) : null] })
2133
+ })
2134
+ }) : null
2135
+ });
2136
+ }
2137
+ function useDarkTheme(theme) {
2138
+ const [systemDark, setSystemDark] = useState(false);
2139
+ useEffect(() => {
2140
+ if (theme !== "system") return;
2141
+ const query = window.matchMedia("(prefers-color-scheme: dark)");
2142
+ const update = () => setSystemDark(query.matches);
2143
+ update();
2144
+ query.addEventListener("change", update);
2145
+ return () => query.removeEventListener("change", update);
2146
+ }, [theme]);
2147
+ return theme === "dark" || theme === "system" && systemDark;
2148
+ }
2149
+ function SharedSession({ token, features, leading }) {
2150
+ const { status, share, control } = useSharedSession({
2151
+ token,
2152
+ onNotice: showNotice
2153
+ });
2154
+ switch (status) {
2155
+ case "invalid": return /* @__PURE__ */ jsx(SessionState, {
2156
+ icon: /* @__PURE__ */ jsx(Link2Off, {}),
2157
+ title: "Invalid share link",
2158
+ description: "This link does not contain a valid session capability."
2159
+ });
2160
+ case "loading": return /* @__PURE__ */ jsx(SessionState, {
2161
+ icon: /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }),
2162
+ title: "Opening shared session"
2163
+ });
2164
+ case "expired": return /* @__PURE__ */ jsx(SessionState, {
2165
+ icon: /* @__PURE__ */ jsx(Link2Off, {}),
2166
+ title: "Share link expired",
2167
+ description: "This session capability has expired. Ask the session owner for a new link."
2168
+ });
2169
+ case "unavailable": return /* @__PURE__ */ jsx(SessionState, {
2170
+ icon: /* @__PURE__ */ jsx(Link2Off, {}),
2171
+ title: "Shared session unavailable",
2172
+ description: "This link is invalid, revoked, or the shared session is no longer available."
2173
+ });
2174
+ case "ready": return /* @__PURE__ */ jsx("div", {
2175
+ className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background",
2176
+ children: /* @__PURE__ */ jsx(BrowserControlPane, {
2177
+ control,
2178
+ collaborationRole: share?.role ?? "viewer",
2179
+ cdpUrl: null,
2180
+ shareUrls: null,
2181
+ leading,
2182
+ features: {
2183
+ devTools: false,
2184
+ ...features
2185
+ }
2186
+ })
2187
+ });
2188
+ }
2189
+ }
2190
+ function devToolsUrl(origin, cdpUrl, sessionToken) {
2191
+ const sourceUrl = new URL(cdpUrl, origin);
2192
+ const url = new URL(origin);
2193
+ url.pathname = `${sourceUrl.pathname.replace(/\/$/, "")}/${encodeURIComponent(sessionToken)}`;
2194
+ return url.toString();
2195
+ }
2196
+ function SessionState({ icon, title, description }) {
2197
+ return /* @__PURE__ */ jsx(Empty, {
2198
+ className: "h-full border-none",
2199
+ children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
2200
+ /* @__PURE__ */ jsx(EmptyMedia, {
2201
+ variant: "icon",
2202
+ children: icon
2203
+ }),
2204
+ /* @__PURE__ */ jsx(EmptyTitle, { children: title }),
2205
+ description ? /* @__PURE__ */ jsx(EmptyDescription, { children: description }) : null
2206
+ ] })
2207
+ });
2208
+ }
2209
+ //#endregion
2210
+ export { ApertureProvider, ApertureSession, BrowserControlPane, BrowserViewport, ClipboardError, LiveSessionError, RuntimeProvider, RuntimeProviderMissingError, SessionViewport, SharedSession, collaborationPaintLifetimeMs, copyText, copyTextWithToast, devToolsUrl, makeApertureRuntime, parseShareToken, resolveSessionFeatures, showNotice, useBaseUrl, useBrowserControl, useEffectCallback, useFork, useLiveSession, useRunApi, useRuntime, useSharedSession };
2211
+
2212
+ //# sourceMappingURL=index.js.map