@browsernode/elements 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +107 -0
  2. package/dist/console.d.ts +3 -0
  3. package/dist/console.d.ts.map +1 -0
  4. package/dist/console.js +161 -0
  5. package/dist/console.js.map +1 -0
  6. package/dist/editor/internal/language.d.ts +3 -0
  7. package/dist/editor/internal/language.d.ts.map +1 -0
  8. package/dist/editor/internal/tree.d.ts +13 -0
  9. package/dist/editor/internal/tree.d.ts.map +1 -0
  10. package/dist/editor/primitives/code-editor.d.ts +26 -0
  11. package/dist/editor/primitives/code-editor.d.ts.map +1 -0
  12. package/dist/editor/primitives/file-tree.d.ts +27 -0
  13. package/dist/editor/primitives/file-tree.d.ts.map +1 -0
  14. package/dist/editor/sandbox-code-editor.d.ts +32 -0
  15. package/dist/editor/sandbox-code-editor.d.ts.map +1 -0
  16. package/dist/editor/sandbox-file-tree.d.ts +29 -0
  17. package/dist/editor/sandbox-file-tree.d.ts.map +1 -0
  18. package/dist/editor/sandbox-ide.d.ts +23 -0
  19. package/dist/editor/sandbox-ide.d.ts.map +1 -0
  20. package/dist/editor.d.ts +6 -0
  21. package/dist/editor.d.ts.map +1 -0
  22. package/dist/editor.js +990 -0
  23. package/dist/editor.js.map +1 -0
  24. package/dist/files/geist-cyrillic-wght-normal.woff2 +0 -0
  25. package/dist/files/geist-latin-ext-wght-normal.woff2 +0 -0
  26. package/dist/files/geist-latin-wght-normal.woff2 +0 -0
  27. package/dist/index.d.ts +9 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +2556 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/preview/primitives/web-preview.d.ts +36 -0
  32. package/dist/preview/primitives/web-preview.d.ts.map +1 -0
  33. package/dist/preview/sandbox-preview.d.ts +58 -0
  34. package/dist/preview/sandbox-preview.d.ts.map +1 -0
  35. package/dist/primitives/lib/utils.d.ts +3 -0
  36. package/dist/primitives/lib/utils.d.ts.map +1 -0
  37. package/dist/primitives/stack-trace.d.ts +38 -0
  38. package/dist/primitives/stack-trace.d.ts.map +1 -0
  39. package/dist/primitives/ui/avatar.d.ts +12 -0
  40. package/dist/primitives/ui/avatar.d.ts.map +1 -0
  41. package/dist/primitives/ui/badge.d.ts +8 -0
  42. package/dist/primitives/ui/badge.d.ts.map +1 -0
  43. package/dist/primitives/ui/button-group.d.ts +11 -0
  44. package/dist/primitives/ui/button-group.d.ts.map +1 -0
  45. package/dist/primitives/ui/button.d.ts +9 -0
  46. package/dist/primitives/ui/button.d.ts.map +1 -0
  47. package/dist/primitives/ui/collapsible.d.ts +6 -0
  48. package/dist/primitives/ui/collapsible.d.ts.map +1 -0
  49. package/dist/primitives/ui/dialog.d.ts +18 -0
  50. package/dist/primitives/ui/dialog.d.ts.map +1 -0
  51. package/dist/primitives/ui/dropdown-menu.d.ts +30 -0
  52. package/dist/primitives/ui/dropdown-menu.d.ts.map +1 -0
  53. package/dist/primitives/ui/empty.d.ts +12 -0
  54. package/dist/primitives/ui/empty.d.ts.map +1 -0
  55. package/dist/primitives/ui/hover-card.d.ts +6 -0
  56. package/dist/primitives/ui/hover-card.d.ts.map +1 -0
  57. package/dist/primitives/ui/input-group.d.ts +19 -0
  58. package/dist/primitives/ui/input-group.d.ts.map +1 -0
  59. package/dist/primitives/ui/input.d.ts +4 -0
  60. package/dist/primitives/ui/input.d.ts.map +1 -0
  61. package/dist/primitives/ui/loading-dots.d.ts +7 -0
  62. package/dist/primitives/ui/loading-dots.d.ts.map +1 -0
  63. package/dist/primitives/ui/loading.d.ts +6 -0
  64. package/dist/primitives/ui/loading.d.ts.map +1 -0
  65. package/dist/primitives/ui/popover.d.ts +10 -0
  66. package/dist/primitives/ui/popover.d.ts.map +1 -0
  67. package/dist/primitives/ui/scroll-area.d.ts +5 -0
  68. package/dist/primitives/ui/scroll-area.d.ts.map +1 -0
  69. package/dist/primitives/ui/select.d.ts +16 -0
  70. package/dist/primitives/ui/select.d.ts.map +1 -0
  71. package/dist/primitives/ui/separator.d.ts +4 -0
  72. package/dist/primitives/ui/separator.d.ts.map +1 -0
  73. package/dist/primitives/ui/spinner.d.ts +3 -0
  74. package/dist/primitives/ui/spinner.d.ts.map +1 -0
  75. package/dist/primitives/ui/tabs.d.ts +11 -0
  76. package/dist/primitives/ui/tabs.d.ts.map +1 -0
  77. package/dist/primitives/ui/textarea.d.ts +4 -0
  78. package/dist/primitives/ui/textarea.d.ts.map +1 -0
  79. package/dist/primitives/ui/tooltip.d.ts +7 -0
  80. package/dist/primitives/ui/tooltip.d.ts.map +1 -0
  81. package/dist/provider/sandbox-provider.d.ts +44 -0
  82. package/dist/provider/sandbox-provider.d.ts.map +1 -0
  83. package/dist/sandbox-attribution.d.ts +9 -0
  84. package/dist/sandbox-attribution.d.ts.map +1 -0
  85. package/dist/sandbox-chrome.d.ts +38 -0
  86. package/dist/sandbox-chrome.d.ts.map +1 -0
  87. package/dist/sandbox.d.ts +10 -0
  88. package/dist/sandbox.d.ts.map +1 -0
  89. package/dist/snapshots/sandbox-snapshots.d.ts +14 -0
  90. package/dist/snapshots/sandbox-snapshots.d.ts.map +1 -0
  91. package/dist/styles.css +2 -0
  92. package/package.json +76 -0
package/dist/index.js ADDED
@@ -0,0 +1,2556 @@
1
+ // src/provider/sandbox-provider.tsx
2
+ import {
3
+ createContext,
4
+ useCallback,
5
+ useContext,
6
+ useMemo,
7
+ useState
8
+ } from "react";
9
+ import {
10
+ SandboxRuntime
11
+ } from "@browsernode/react";
12
+ import { esbuild } from "@browsernode/esbuild-wasm";
13
+ import { nextjs } from "@browsernode/nextjs";
14
+ import { useSandbox } from "@browsernode/react";
15
+ import { jsx } from "react/jsx-runtime";
16
+ var ElementsContext = createContext(null);
17
+ function toTab(t) {
18
+ return typeof t === "string" ? { path: t } : t;
19
+ }
20
+ function resolveFramework(framework) {
21
+ if (framework === "nextjs") return nextjs();
22
+ if (typeof framework === "string") {
23
+ throw new Error(`Unsupported SandboxProvider framework "${framework}".`);
24
+ }
25
+ return framework;
26
+ }
27
+ function SandboxProvider({ children, ...props }) {
28
+ const {
29
+ initialActivePanel,
30
+ initialOpenTabs,
31
+ initialSelectedPath,
32
+ theme,
33
+ framework,
34
+ bundler,
35
+ ...sandboxOptions
36
+ } = props;
37
+ const options = useMemo(
38
+ () => ({
39
+ ...sandboxOptions,
40
+ framework: resolveFramework(framework),
41
+ bundler: bundler ?? esbuild()
42
+ }),
43
+ // The sandbox is created once on mount (managed by <SandboxRuntime>'s
44
+ // useEffect deps); listing only framework/bundler here keeps the
45
+ // ref-stable options object the way callers expect.
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ [framework, bundler]
48
+ );
49
+ return /* @__PURE__ */ jsx(SandboxRuntime, { options, children: /* @__PURE__ */ jsx(
50
+ ElementsExtrasProvider,
51
+ {
52
+ initialActivePanel,
53
+ initialOpenTabs,
54
+ initialSelectedPath,
55
+ theme,
56
+ children
57
+ }
58
+ ) });
59
+ }
60
+ function ElementsExtrasProvider({
61
+ children,
62
+ initialActivePanel = "center",
63
+ initialOpenTabs = [],
64
+ initialSelectedPath = null,
65
+ theme = "light"
66
+ }) {
67
+ const [selectedPath, setSelectedPath] = useState(initialSelectedPath);
68
+ const [openTabs, setOpenTabs] = useState(
69
+ () => initialOpenTabs.map(toTab)
70
+ );
71
+ const [activePanel, setActivePanel] = useState(initialActivePanel);
72
+ const openTab = useCallback((t) => {
73
+ const tab = toTab(t);
74
+ setOpenTabs((prev) => {
75
+ if (prev.some((x) => x.path === tab.path)) return prev;
76
+ return [...prev, tab];
77
+ });
78
+ setSelectedPath(tab.path);
79
+ }, []);
80
+ const closeTab = useCallback((path) => {
81
+ setOpenTabs((prev) => {
82
+ const next = prev.filter((t) => t.path !== path);
83
+ setSelectedPath((cur) => {
84
+ if (cur !== path) return cur;
85
+ const idx = prev.findIndex((t) => t.path === path);
86
+ const fallback = next[idx - 1] ?? next[idx] ?? next[0] ?? null;
87
+ return fallback?.path ?? null;
88
+ });
89
+ return next;
90
+ });
91
+ }, []);
92
+ const extras = useMemo(
93
+ () => ({
94
+ selectedPath,
95
+ setSelectedPath,
96
+ openTabs,
97
+ openTab,
98
+ closeTab,
99
+ activePanel,
100
+ setActivePanel,
101
+ theme
102
+ }),
103
+ [selectedPath, openTabs, openTab, closeTab, activePanel, theme]
104
+ );
105
+ return /* @__PURE__ */ jsx(ElementsContext.Provider, { value: extras, children });
106
+ }
107
+ function useElementsContext() {
108
+ return useContext(ElementsContext);
109
+ }
110
+ function useElementsContextOrThrow() {
111
+ const ctx = useContext(ElementsContext);
112
+ if (!ctx) {
113
+ throw new Error(
114
+ "useElementsContext() must be called inside <SandboxProvider> from @browsernode/elements."
115
+ );
116
+ }
117
+ return ctx;
118
+ }
119
+
120
+ // src/sandbox-chrome.tsx
121
+ import {
122
+ useState as useState9
123
+ } from "react";
124
+ import { motion as motion2 } from "motion/react";
125
+ import {
126
+ ArrowLeftIcon as ArrowLeftIcon2,
127
+ ArrowRightIcon as ArrowRightIcon2,
128
+ ClockFadingIcon,
129
+ CodeIcon,
130
+ DownloadIcon as DownloadIcon2,
131
+ GlobeIcon,
132
+ MaximizeIcon as MaximizeIcon2,
133
+ MinimizeIcon as MinimizeIcon2,
134
+ MonitorIcon as MonitorIcon2,
135
+ MoreVerticalIcon as MoreVerticalIcon2,
136
+ RefreshCcwIcon as RefreshCcwIcon2,
137
+ SmartphoneIcon as SmartphoneIcon2
138
+ } from "lucide-react";
139
+
140
+ // src/preview/sandbox-preview.tsx
141
+ import {
142
+ createContext as createContext4,
143
+ forwardRef,
144
+ useCallback as useCallback4,
145
+ useContext as useContext4,
146
+ useEffect as useEffect2,
147
+ useImperativeHandle,
148
+ useMemo as useMemo4,
149
+ useRef as useRef2,
150
+ useState as useState4
151
+ } from "react";
152
+ import { motion } from "motion/react";
153
+ import {
154
+ ArrowLeftIcon,
155
+ ArrowRightIcon,
156
+ DownloadIcon,
157
+ MaximizeIcon,
158
+ MinimizeIcon,
159
+ MonitorIcon,
160
+ MoreVerticalIcon,
161
+ RefreshCcwIcon,
162
+ SmartphoneIcon
163
+ } from "lucide-react";
164
+ import {
165
+ SandboxIFrame
166
+ } from "@browsernode/react";
167
+
168
+ // src/primitives/ui/button.tsx
169
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
170
+ import { cva } from "class-variance-authority";
171
+
172
+ // src/primitives/lib/utils.ts
173
+ import { clsx } from "clsx";
174
+ import { twMerge } from "tailwind-merge";
175
+ function cn(...inputs) {
176
+ return twMerge(clsx(inputs));
177
+ }
178
+
179
+ // src/primitives/ui/button.tsx
180
+ import { jsx as jsx2 } from "react/jsx-runtime";
181
+ var buttonVariants = cva(
182
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm 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",
183
+ {
184
+ variants: {
185
+ variant: {
186
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
187
+ 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",
188
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
189
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
190
+ 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",
191
+ link: "text-primary underline-offset-4 hover:underline"
192
+ },
193
+ size: {
194
+ default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
195
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 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",
196
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 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",
197
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
198
+ icon: "size-8",
199
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
200
+ "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
201
+ "icon-lg": "size-9"
202
+ }
203
+ },
204
+ defaultVariants: {
205
+ variant: "default",
206
+ size: "default"
207
+ }
208
+ }
209
+ );
210
+ function Button({
211
+ className,
212
+ variant = "default",
213
+ size = "default",
214
+ ...props
215
+ }) {
216
+ return /* @__PURE__ */ jsx2(
217
+ ButtonPrimitive,
218
+ {
219
+ "data-slot": "button",
220
+ className: cn(buttonVariants({ variant, size, className })),
221
+ ...props
222
+ }
223
+ );
224
+ }
225
+
226
+ // src/primitives/ui/collapsible.tsx
227
+ import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
228
+ import { jsx as jsx3 } from "react/jsx-runtime";
229
+ function Collapsible({ ...props }) {
230
+ return /* @__PURE__ */ jsx3(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
231
+ }
232
+ function CollapsibleTrigger({ ...props }) {
233
+ return /* @__PURE__ */ jsx3(CollapsiblePrimitive.Trigger, { "data-slot": "collapsible-trigger", ...props });
234
+ }
235
+ function CollapsibleContent({ ...props }) {
236
+ return /* @__PURE__ */ jsx3(CollapsiblePrimitive.Panel, { "data-slot": "collapsible-content", ...props });
237
+ }
238
+
239
+ // src/primitives/ui/input.tsx
240
+ import { Input as InputPrimitive } from "@base-ui/react/input";
241
+ import { jsx as jsx4 } from "react/jsx-runtime";
242
+ function Input({ className, type, ...props }) {
243
+ return /* @__PURE__ */ jsx4(
244
+ InputPrimitive,
245
+ {
246
+ type,
247
+ "data-slot": "input",
248
+ className: cn(
249
+ "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",
250
+ className
251
+ ),
252
+ ...props
253
+ }
254
+ );
255
+ }
256
+
257
+ // src/primitives/ui/tooltip.tsx
258
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
259
+ import { jsx as jsx5, jsxs } from "react/jsx-runtime";
260
+ function TooltipProvider({
261
+ delay = 0,
262
+ ...props
263
+ }) {
264
+ return /* @__PURE__ */ jsx5(
265
+ TooltipPrimitive.Provider,
266
+ {
267
+ "data-slot": "tooltip-provider",
268
+ delay,
269
+ ...props
270
+ }
271
+ );
272
+ }
273
+ function Tooltip({ ...props }) {
274
+ return /* @__PURE__ */ jsx5(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props });
275
+ }
276
+ function TooltipTrigger({ ...props }) {
277
+ return /* @__PURE__ */ jsx5(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
278
+ }
279
+ function TooltipContent({
280
+ className,
281
+ side = "top",
282
+ sideOffset = 4,
283
+ align = "center",
284
+ alignOffset = 0,
285
+ children,
286
+ ...props
287
+ }) {
288
+ return /* @__PURE__ */ jsx5(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx5(
289
+ TooltipPrimitive.Positioner,
290
+ {
291
+ align,
292
+ alignOffset,
293
+ side,
294
+ sideOffset,
295
+ className: "isolate z-50",
296
+ children: /* @__PURE__ */ jsxs(
297
+ TooltipPrimitive.Popup,
298
+ {
299
+ "data-slot": "tooltip-content",
300
+ className: cn(
301
+ "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",
302
+ className
303
+ ),
304
+ ...props,
305
+ children: [
306
+ children,
307
+ /* @__PURE__ */ jsx5(TooltipPrimitive.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" })
308
+ ]
309
+ }
310
+ )
311
+ }
312
+ ) });
313
+ }
314
+
315
+ // src/preview/primitives/web-preview.tsx
316
+ import { ChevronDownIcon } from "lucide-react";
317
+ import {
318
+ createContext as createContext2,
319
+ useCallback as useCallback2,
320
+ useContext as useContext2,
321
+ useMemo as useMemo2,
322
+ useState as useState2
323
+ } from "react";
324
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
325
+ var WebPreviewContext = createContext2(null);
326
+ var useWebPreview = () => {
327
+ const context = useContext2(WebPreviewContext);
328
+ if (!context) {
329
+ throw new Error("WebPreview components must be used within a WebPreview");
330
+ }
331
+ return context;
332
+ };
333
+ var WebPreview = ({
334
+ className,
335
+ children,
336
+ defaultUrl = "",
337
+ onUrlChange,
338
+ ...props
339
+ }) => {
340
+ const [url, setUrl] = useState2(defaultUrl);
341
+ const [consoleOpen, setConsoleOpen] = useState2(false);
342
+ const handleUrlChange = useCallback2(
343
+ (newUrl) => {
344
+ setUrl(newUrl);
345
+ onUrlChange?.(newUrl);
346
+ },
347
+ [onUrlChange]
348
+ );
349
+ const contextValue = useMemo2(
350
+ () => ({
351
+ consoleOpen,
352
+ setConsoleOpen,
353
+ setUrl: handleUrlChange,
354
+ url
355
+ }),
356
+ [consoleOpen, handleUrlChange, url]
357
+ );
358
+ return /* @__PURE__ */ jsx6(WebPreviewContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx6(
359
+ "div",
360
+ {
361
+ className: cn(
362
+ "flex size-full flex-col rounded-lg border bg-card",
363
+ className
364
+ ),
365
+ ...props,
366
+ children
367
+ }
368
+ ) });
369
+ };
370
+ var WebPreviewNavigation = ({
371
+ className,
372
+ children,
373
+ ...props
374
+ }) => /* @__PURE__ */ jsx6(
375
+ "div",
376
+ {
377
+ className: cn("flex items-center gap-1 border-b p-2", className),
378
+ ...props,
379
+ children
380
+ }
381
+ );
382
+ var WebPreviewNavigationButton = ({
383
+ onClick,
384
+ disabled,
385
+ tooltip,
386
+ children,
387
+ className,
388
+ ...props
389
+ }) => /* @__PURE__ */ jsx6(TooltipProvider, { children: /* @__PURE__ */ jsxs2(Tooltip, { children: [
390
+ /* @__PURE__ */ jsx6(
391
+ TooltipTrigger,
392
+ {
393
+ render: /* @__PURE__ */ jsx6(
394
+ Button,
395
+ {
396
+ className: cn("h-8 w-8 p-0 hover:text-foreground", className),
397
+ disabled,
398
+ onClick,
399
+ size: "sm",
400
+ variant: "ghost",
401
+ ...props
402
+ }
403
+ ),
404
+ children
405
+ }
406
+ ),
407
+ /* @__PURE__ */ jsx6(TooltipContent, { children: /* @__PURE__ */ jsx6("p", { children: tooltip }) })
408
+ ] }) });
409
+ var WebPreviewConsole = ({
410
+ className,
411
+ logs = [],
412
+ children,
413
+ ...props
414
+ }) => {
415
+ const { consoleOpen, setConsoleOpen } = useWebPreview();
416
+ return /* @__PURE__ */ jsxs2(
417
+ Collapsible,
418
+ {
419
+ className: cn("border-t bg-muted/50 font-mono text-sm", className),
420
+ onOpenChange: setConsoleOpen,
421
+ open: consoleOpen,
422
+ ...props,
423
+ children: [
424
+ /* @__PURE__ */ jsxs2(CollapsibleTrigger, { render: /* @__PURE__ */ jsx6(Button, { className: "flex w-full items-center justify-between p-4 text-left font-medium hover:bg-muted/50", variant: "ghost" }), children: [
425
+ "Console",
426
+ /* @__PURE__ */ jsx6(
427
+ ChevronDownIcon,
428
+ {
429
+ className: cn(
430
+ "h-4 w-4 transition-transform duration-200",
431
+ consoleOpen && "rotate-180"
432
+ )
433
+ }
434
+ )
435
+ ] }),
436
+ /* @__PURE__ */ jsx6(
437
+ CollapsibleContent,
438
+ {
439
+ className: cn(
440
+ "px-4 pb-4",
441
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in"
442
+ ),
443
+ children: /* @__PURE__ */ jsxs2("div", { className: "max-h-48 space-y-1 overflow-y-auto", children: [
444
+ logs.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground", children: "No console output" }) : logs.map((log) => /* @__PURE__ */ jsxs2(
445
+ "div",
446
+ {
447
+ className: cn(
448
+ "text-xs",
449
+ log.level === "error" && "text-destructive",
450
+ log.level === "warn" && "text-yellow-600",
451
+ log.level === "log" && "text-foreground"
452
+ ),
453
+ children: [
454
+ /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: log.timestamp.toLocaleTimeString() }),
455
+ " ",
456
+ log.message
457
+ ]
458
+ },
459
+ `${log.timestamp.getTime()}-${log.level}-${log.message}`
460
+ )),
461
+ children
462
+ ] })
463
+ }
464
+ )
465
+ ]
466
+ }
467
+ );
468
+ };
469
+
470
+ // src/primitives/stack-trace.tsx
471
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
472
+ import {
473
+ AlertTriangleIcon,
474
+ CheckIcon,
475
+ ChevronDownIcon as ChevronDownIcon2,
476
+ CopyIcon
477
+ } from "lucide-react";
478
+ import {
479
+ createContext as createContext3,
480
+ memo,
481
+ useCallback as useCallback3,
482
+ useContext as useContext3,
483
+ useEffect,
484
+ useMemo as useMemo3,
485
+ useRef,
486
+ useState as useState3
487
+ } from "react";
488
+ import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
489
+ var STACK_FRAME_WITH_PARENS_REGEX = /^at\s+(.+?)\s+\((.+):(\d+):(\d+)\)$/;
490
+ var STACK_FRAME_WITHOUT_FN_REGEX = /^at\s+(.+):(\d+):(\d+)$/;
491
+ var ERROR_TYPE_REGEX = /^(\w+Error|Error):\s*(.*)$/;
492
+ var AT_PREFIX_REGEX = /^at\s+/;
493
+ var StackTraceContext = createContext3(null);
494
+ var useStackTrace = () => {
495
+ const context = useContext3(StackTraceContext);
496
+ if (!context) {
497
+ throw new Error("StackTrace components must be used within StackTrace");
498
+ }
499
+ return context;
500
+ };
501
+ var parseStackFrame = (line) => {
502
+ const trimmed = line.trim();
503
+ const withParensMatch = trimmed.match(STACK_FRAME_WITH_PARENS_REGEX);
504
+ if (withParensMatch) {
505
+ const [, functionName, filePath, lineNum, colNum] = withParensMatch;
506
+ const isInternal = filePath.includes("node_modules") || filePath.startsWith("node:") || filePath.includes("internal/");
507
+ return {
508
+ columnNumber: colNum ? Number.parseInt(colNum, 10) : null,
509
+ filePath: filePath ?? null,
510
+ functionName: functionName ?? null,
511
+ isInternal,
512
+ lineNumber: lineNum ? Number.parseInt(lineNum, 10) : null,
513
+ raw: trimmed
514
+ };
515
+ }
516
+ const withoutFnMatch = trimmed.match(STACK_FRAME_WITHOUT_FN_REGEX);
517
+ if (withoutFnMatch) {
518
+ const [, filePath, lineNum, colNum] = withoutFnMatch;
519
+ const isInternal = (filePath?.includes("node_modules") ?? false) || (filePath?.startsWith("node:") ?? false) || (filePath?.includes("internal/") ?? false);
520
+ return {
521
+ columnNumber: colNum ? Number.parseInt(colNum, 10) : null,
522
+ filePath: filePath ?? null,
523
+ functionName: null,
524
+ isInternal,
525
+ lineNumber: lineNum ? Number.parseInt(lineNum, 10) : null,
526
+ raw: trimmed
527
+ };
528
+ }
529
+ return {
530
+ columnNumber: null,
531
+ filePath: null,
532
+ functionName: null,
533
+ isInternal: trimmed.includes("node_modules") || trimmed.includes("node:"),
534
+ lineNumber: null,
535
+ raw: trimmed
536
+ };
537
+ };
538
+ var parseStackTrace = (trace) => {
539
+ const lines = trace.split("\n").filter((line) => line.trim());
540
+ if (lines.length === 0) {
541
+ return {
542
+ errorMessage: trace,
543
+ errorType: null,
544
+ frames: [],
545
+ raw: trace
546
+ };
547
+ }
548
+ const firstLine = lines[0].trim();
549
+ let errorType = null;
550
+ let errorMessage = firstLine;
551
+ const errorMatch = firstLine.match(ERROR_TYPE_REGEX);
552
+ if (errorMatch) {
553
+ const [, type, msg] = errorMatch;
554
+ errorType = type;
555
+ errorMessage = msg || "";
556
+ }
557
+ const frames = lines.slice(1).filter((line) => line.trim().startsWith("at ")).map(parseStackFrame);
558
+ return {
559
+ errorMessage,
560
+ errorType,
561
+ frames,
562
+ raw: trace
563
+ };
564
+ };
565
+ var StackTrace = memo(
566
+ ({
567
+ trace,
568
+ className,
569
+ open,
570
+ defaultOpen = false,
571
+ onOpenChange,
572
+ onFilePathClick,
573
+ children,
574
+ ...props
575
+ }) => {
576
+ const [isOpen, setIsOpen] = useControllableState({
577
+ defaultProp: defaultOpen,
578
+ onChange: onOpenChange,
579
+ prop: open
580
+ });
581
+ const parsedTrace = useMemo3(() => parseStackTrace(trace), [trace]);
582
+ const contextValue = useMemo3(
583
+ () => ({
584
+ isOpen,
585
+ onFilePathClick,
586
+ raw: trace,
587
+ setIsOpen,
588
+ trace: parsedTrace
589
+ }),
590
+ [parsedTrace, trace, isOpen, setIsOpen, onFilePathClick]
591
+ );
592
+ return /* @__PURE__ */ jsx7(StackTraceContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx7(
593
+ "div",
594
+ {
595
+ className: cn(
596
+ "not-prose w-full overflow-hidden rounded-lg border bg-background font-mono text-sm",
597
+ className
598
+ ),
599
+ ...props,
600
+ children
601
+ }
602
+ ) });
603
+ }
604
+ );
605
+ var StackTraceHeader = memo(
606
+ ({ className, children, ...props }) => {
607
+ const { isOpen, setIsOpen } = useStackTrace();
608
+ return /* @__PURE__ */ jsx7(Collapsible, { onOpenChange: setIsOpen, open: isOpen, children: /* @__PURE__ */ jsx7(CollapsibleTrigger, { ...props, render: /* @__PURE__ */ jsx7("div", { className: cn(
609
+ "flex w-full cursor-pointer items-center gap-3 p-3 text-left transition-colors hover:bg-muted/50",
610
+ className
611
+ ) }), children }) });
612
+ }
613
+ );
614
+ var StackTraceError = memo(
615
+ ({ className, children, ...props }) => /* @__PURE__ */ jsxs3(
616
+ "div",
617
+ {
618
+ className: cn(
619
+ "flex flex-1 items-center gap-2 overflow-hidden",
620
+ className
621
+ ),
622
+ ...props,
623
+ children: [
624
+ /* @__PURE__ */ jsx7(AlertTriangleIcon, { className: "size-4 shrink-0 text-destructive" }),
625
+ children
626
+ ]
627
+ }
628
+ )
629
+ );
630
+ var StackTraceErrorType = memo(
631
+ ({ className, children, ...props }) => {
632
+ const { trace } = useStackTrace();
633
+ return /* @__PURE__ */ jsx7(
634
+ "span",
635
+ {
636
+ className: cn("shrink-0 font-semibold text-destructive", className),
637
+ ...props,
638
+ children: children ?? trace.errorType
639
+ }
640
+ );
641
+ }
642
+ );
643
+ var StackTraceErrorMessage = memo(
644
+ ({ className, children, ...props }) => {
645
+ const { trace } = useStackTrace();
646
+ return /* @__PURE__ */ jsx7("span", { className: cn("truncate text-foreground", className), ...props, children: children ?? trace.errorMessage });
647
+ }
648
+ );
649
+ var handleActionsClick = (e) => e.stopPropagation();
650
+ var handleActionsKeyDown = (e) => {
651
+ if (e.key === "Enter" || e.key === " ") {
652
+ e.stopPropagation();
653
+ }
654
+ };
655
+ var StackTraceActions = memo(
656
+ ({ className, children, ...props }) => /* @__PURE__ */ jsx7(
657
+ "div",
658
+ {
659
+ className: cn("flex shrink-0 items-center gap-1", className),
660
+ onClick: handleActionsClick,
661
+ onKeyDown: handleActionsKeyDown,
662
+ role: "group",
663
+ ...props,
664
+ children
665
+ }
666
+ )
667
+ );
668
+ var StackTraceCopyButton = memo(
669
+ ({
670
+ onCopy,
671
+ onError,
672
+ timeout = 2e3,
673
+ className,
674
+ children,
675
+ ...props
676
+ }) => {
677
+ const [isCopied, setIsCopied] = useState3(false);
678
+ const timeoutRef = useRef(0);
679
+ const { raw } = useStackTrace();
680
+ const copyToClipboard = useCallback3(async () => {
681
+ if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
682
+ onError?.(new Error("Clipboard API not available"));
683
+ return;
684
+ }
685
+ try {
686
+ await navigator.clipboard.writeText(raw);
687
+ setIsCopied(true);
688
+ onCopy?.();
689
+ timeoutRef.current = window.setTimeout(
690
+ () => setIsCopied(false),
691
+ timeout
692
+ );
693
+ } catch (error) {
694
+ onError?.(error);
695
+ }
696
+ }, [raw, onCopy, onError, timeout]);
697
+ useEffect(
698
+ () => () => {
699
+ window.clearTimeout(timeoutRef.current);
700
+ },
701
+ []
702
+ );
703
+ const Icon = isCopied ? CheckIcon : CopyIcon;
704
+ return /* @__PURE__ */ jsx7(
705
+ Button,
706
+ {
707
+ className: cn("size-7", className),
708
+ onClick: copyToClipboard,
709
+ size: "icon",
710
+ variant: "ghost",
711
+ ...props,
712
+ children: children ?? /* @__PURE__ */ jsx7(Icon, { size: 14 })
713
+ }
714
+ );
715
+ }
716
+ );
717
+ var StackTraceExpandButton = memo(
718
+ ({ className, ...props }) => {
719
+ const { isOpen } = useStackTrace();
720
+ return /* @__PURE__ */ jsx7(
721
+ "div",
722
+ {
723
+ className: cn("flex size-7 items-center justify-center", className),
724
+ ...props,
725
+ children: /* @__PURE__ */ jsx7(
726
+ ChevronDownIcon2,
727
+ {
728
+ className: cn(
729
+ "size-4 text-muted-foreground transition-transform",
730
+ isOpen ? "rotate-180" : "rotate-0"
731
+ )
732
+ }
733
+ )
734
+ }
735
+ );
736
+ }
737
+ );
738
+ var StackTraceContent = memo(
739
+ ({
740
+ className,
741
+ maxHeight = 400,
742
+ children,
743
+ ...props
744
+ }) => {
745
+ const { isOpen } = useStackTrace();
746
+ return /* @__PURE__ */ jsx7(Collapsible, { open: isOpen, children: /* @__PURE__ */ jsx7(
747
+ CollapsibleContent,
748
+ {
749
+ className: cn(
750
+ "overflow-auto border-t bg-muted/30",
751
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=open]:animate-in",
752
+ className
753
+ ),
754
+ style: { maxHeight },
755
+ ...props,
756
+ children
757
+ }
758
+ ) });
759
+ }
760
+ );
761
+ var FilePathButton = memo(
762
+ ({ frame, onFilePathClick }) => {
763
+ const handleClick = useCallback3(() => {
764
+ if (frame.filePath) {
765
+ onFilePathClick?.(
766
+ frame.filePath,
767
+ frame.lineNumber ?? void 0,
768
+ frame.columnNumber ?? void 0
769
+ );
770
+ }
771
+ }, [frame, onFilePathClick]);
772
+ return /* @__PURE__ */ jsxs3(
773
+ "button",
774
+ {
775
+ className: cn(
776
+ "underline decoration-dotted hover:text-primary",
777
+ onFilePathClick && "cursor-pointer"
778
+ ),
779
+ disabled: !onFilePathClick,
780
+ onClick: handleClick,
781
+ type: "button",
782
+ children: [
783
+ frame.filePath,
784
+ frame.lineNumber !== null && `:${frame.lineNumber}`,
785
+ frame.columnNumber !== null && `:${frame.columnNumber}`
786
+ ]
787
+ }
788
+ );
789
+ }
790
+ );
791
+ FilePathButton.displayName = "FilePathButton";
792
+ var StackTraceFrames = memo(
793
+ ({
794
+ className,
795
+ showInternalFrames = true,
796
+ ...props
797
+ }) => {
798
+ const { trace, onFilePathClick } = useStackTrace();
799
+ const framesToShow = showInternalFrames ? trace.frames : trace.frames.filter((f) => !f.isInternal);
800
+ return /* @__PURE__ */ jsxs3("div", { className: cn("space-y-1 p-3", className), ...props, children: [
801
+ framesToShow.map((frame) => /* @__PURE__ */ jsxs3(
802
+ "div",
803
+ {
804
+ className: cn(
805
+ "text-xs",
806
+ frame.isInternal ? "text-muted-foreground/50" : "text-foreground/90"
807
+ ),
808
+ children: [
809
+ /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: "at " }),
810
+ frame.functionName && /* @__PURE__ */ jsxs3("span", { className: frame.isInternal ? "" : "text-foreground", children: [
811
+ frame.functionName,
812
+ " "
813
+ ] }),
814
+ frame.filePath && /* @__PURE__ */ jsxs3(Fragment, { children: [
815
+ /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: "(" }),
816
+ /* @__PURE__ */ jsx7(
817
+ FilePathButton,
818
+ {
819
+ frame,
820
+ onFilePathClick
821
+ }
822
+ ),
823
+ /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: ")" })
824
+ ] }),
825
+ !(frame.filePath || frame.functionName) && /* @__PURE__ */ jsx7("span", { children: frame.raw.replace(AT_PREFIX_REGEX, "") })
826
+ ]
827
+ },
828
+ frame.raw
829
+ )),
830
+ framesToShow.length === 0 && /* @__PURE__ */ jsx7("div", { className: "text-muted-foreground text-xs", children: "No stack frames" })
831
+ ] });
832
+ }
833
+ );
834
+ StackTrace.displayName = "StackTrace";
835
+ StackTraceHeader.displayName = "StackTraceHeader";
836
+ StackTraceError.displayName = "StackTraceError";
837
+ StackTraceErrorType.displayName = "StackTraceErrorType";
838
+ StackTraceErrorMessage.displayName = "StackTraceErrorMessage";
839
+ StackTraceActions.displayName = "StackTraceActions";
840
+ StackTraceCopyButton.displayName = "StackTraceCopyButton";
841
+ StackTraceExpandButton.displayName = "StackTraceExpandButton";
842
+ StackTraceContent.displayName = "StackTraceContent";
843
+ StackTraceFrames.displayName = "StackTraceFrames";
844
+
845
+ // src/primitives/ui/dropdown-menu.tsx
846
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
847
+ import { ChevronRightIcon, CheckIcon as CheckIcon2 } from "lucide-react";
848
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
849
+ function DropdownMenu({ ...props }) {
850
+ return /* @__PURE__ */ jsx8(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
851
+ }
852
+ function DropdownMenuTrigger({ ...props }) {
853
+ return /* @__PURE__ */ jsx8(MenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
854
+ }
855
+ function DropdownMenuContent({
856
+ align = "start",
857
+ alignOffset = 0,
858
+ side = "bottom",
859
+ sideOffset = 4,
860
+ className,
861
+ ...props
862
+ }) {
863
+ return /* @__PURE__ */ jsx8(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx8(
864
+ MenuPrimitive.Positioner,
865
+ {
866
+ className: "isolate z-50 outline-none",
867
+ align,
868
+ alignOffset,
869
+ side,
870
+ sideOffset,
871
+ children: /* @__PURE__ */ jsx8(
872
+ MenuPrimitive.Popup,
873
+ {
874
+ "data-slot": "dropdown-menu-content",
875
+ 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),
876
+ ...props
877
+ }
878
+ )
879
+ }
880
+ ) });
881
+ }
882
+ function DropdownMenuItem({
883
+ className,
884
+ inset,
885
+ variant = "default",
886
+ ...props
887
+ }) {
888
+ return /* @__PURE__ */ jsx8(
889
+ MenuPrimitive.Item,
890
+ {
891
+ "data-slot": "dropdown-menu-item",
892
+ "data-inset": inset,
893
+ "data-variant": variant,
894
+ className: cn(
895
+ "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",
896
+ className
897
+ ),
898
+ ...props
899
+ }
900
+ );
901
+ }
902
+
903
+ // src/preview/sandbox-preview.tsx
904
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
905
+ var LEVEL_MAP = {
906
+ log: "log",
907
+ info: "log",
908
+ debug: "log",
909
+ warn: "warn",
910
+ error: "error"
911
+ };
912
+ var SandboxPreviewCtx = createContext4(null);
913
+ function useSandboxPreviewCtx() {
914
+ const ctx = useContext4(SandboxPreviewCtx);
915
+ if (!ctx) {
916
+ throw new Error(
917
+ "<SandboxPreviewToolbar>/<SandboxPreviewWeb> must be rendered inside <SandboxPreviewProvider>"
918
+ );
919
+ }
920
+ return ctx;
921
+ }
922
+ var SandboxPreviewProvider = forwardRef(function SandboxPreviewProvider2({
923
+ sandbox: sandboxProp,
924
+ defaultUrl = "/",
925
+ onNavigate,
926
+ iframeProps,
927
+ children,
928
+ ...rest
929
+ }, ref) {
930
+ const { sandbox: ctxSandbox } = useSandbox();
931
+ const sandbox = sandboxProp ?? ctxSandbox ?? null;
932
+ const innerRef = useRef2(null);
933
+ const attachHandle = useCallback4((node) => {
934
+ innerRef.current = node;
935
+ if (typeof ref === "function") ref(node);
936
+ else if (ref) ref.current = node;
937
+ }, [ref]);
938
+ const [currentUrl, setCurrentUrl] = useState4(defaultUrl);
939
+ const [inputValue, setInputValue] = useState4(defaultUrl);
940
+ const [history, setHistory] = useState4([defaultUrl]);
941
+ const [historyIndex, setHistoryIndex] = useState4(0);
942
+ const [events, setEvents] = useState4(
943
+ () => sandbox ? [...sandbox.console.history()] : []
944
+ );
945
+ const [buildError, setBuildError] = useState4(null);
946
+ const [sandboxError, setSandboxError] = useState4(null);
947
+ const [runtimeError, setRuntimeError] = useState4(null);
948
+ const [viewport, setViewport] = useState4("desktop");
949
+ const containerRef = useRef2(null);
950
+ const skipNextEcho = useRef2(null);
951
+ useEffect2(() => {
952
+ if (!sandbox) return;
953
+ setEvents([...sandbox.console.history()]);
954
+ const unsubNav = sandbox.on("navigate", (url) => {
955
+ setCurrentUrl(url);
956
+ setInputValue(url);
957
+ setRuntimeError(null);
958
+ if (skipNextEcho.current === url) {
959
+ skipNextEcho.current = null;
960
+ return;
961
+ }
962
+ setHistory((prev) => {
963
+ const truncated = prev.slice(0, historyIndex + 1);
964
+ if (truncated[truncated.length - 1] === url) return truncated;
965
+ return [...truncated, url];
966
+ });
967
+ setHistoryIndex((i) => i + 1);
968
+ });
969
+ const unsubLog = sandbox.on("console", (level, args) => {
970
+ setEvents((prev) => [...prev, { level, args, ts: Date.now() }]);
971
+ });
972
+ const unsubBuild = sandbox.on("build", (result) => {
973
+ setBuildError(result.ok ? null : result.errors);
974
+ if (result.ok) setSandboxError(null);
975
+ });
976
+ const unsubErr = sandbox.on("error", (err) => setSandboxError(err));
977
+ return () => {
978
+ unsubNav();
979
+ unsubLog();
980
+ unsubBuild();
981
+ unsubErr();
982
+ };
983
+ }, [sandbox, historyIndex]);
984
+ const goBack = useCallback4(() => {
985
+ if (historyIndex <= 0) return;
986
+ const target = history[historyIndex - 1];
987
+ skipNextEcho.current = target;
988
+ setHistoryIndex(historyIndex - 1);
989
+ innerRef.current?.navigate(target);
990
+ }, [history, historyIndex]);
991
+ const goForward = useCallback4(() => {
992
+ if (historyIndex >= history.length - 1) return;
993
+ const target = history[historyIndex + 1];
994
+ skipNextEcho.current = target;
995
+ setHistoryIndex(historyIndex + 1);
996
+ innerRef.current?.navigate(target);
997
+ }, [history, historyIndex]);
998
+ const [spinCount, setSpinCount] = useState4(0);
999
+ const reload = useCallback4(() => {
1000
+ setSpinCount((n) => n + 1);
1001
+ if (sandbox) {
1002
+ sandbox.rebuild().catch(() => {
1003
+ });
1004
+ } else {
1005
+ innerRef.current?.reload();
1006
+ }
1007
+ }, [sandbox]);
1008
+ const [overlay, setOverlay] = useState4(false);
1009
+ const toggleOverlay = useCallback4(() => setOverlay((v) => !v), []);
1010
+ useEffect2(() => {
1011
+ if (!overlay) return;
1012
+ const onKey = (e) => {
1013
+ if (e.key === "Escape") setOverlay(false);
1014
+ };
1015
+ window.addEventListener("keydown", onKey);
1016
+ return () => window.removeEventListener("keydown", onKey);
1017
+ }, [overlay]);
1018
+ const submitUrl = useCallback4(() => {
1019
+ const target = inputValue.trim() || "/";
1020
+ innerRef.current?.navigate(target);
1021
+ }, [inputValue]);
1022
+ const download = useCallback4(async () => {
1023
+ if (!sandbox) return;
1024
+ const blob = await sandbox.download();
1025
+ const url = URL.createObjectURL(blob);
1026
+ const a = document.createElement("a");
1027
+ a.href = url;
1028
+ a.download = `${sandbox.sandboxId ?? "sandbox"}.zip`;
1029
+ document.body.appendChild(a);
1030
+ a.click();
1031
+ a.remove();
1032
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
1033
+ }, [sandbox]);
1034
+ const logs = useMemo4(
1035
+ () => events.map((e) => ({
1036
+ level: LEVEL_MAP[e.level],
1037
+ message: formatArgs(e.args),
1038
+ timestamp: new Date(e.ts)
1039
+ })),
1040
+ [events]
1041
+ );
1042
+ const overlayTrace = useMemo4(() => {
1043
+ if (buildError && buildError.length > 0) {
1044
+ const head = buildError[0];
1045
+ const frames = buildError.map((d) => {
1046
+ const file = d.file ?? "<unknown>";
1047
+ const line = d.line ?? 0;
1048
+ const col = d.column ?? 0;
1049
+ return ` at build (${file}:${line}:${col})`;
1050
+ });
1051
+ return [`BuildError: ${head.message}`, ...frames].join("\n");
1052
+ }
1053
+ if (runtimeError) {
1054
+ if (runtimeError.stack) return runtimeError.stack;
1055
+ const errType = runtimeError.source === "unhandledrejection" ? "UnhandledRejection" : "RuntimeError";
1056
+ const frame = ` at <iframe> (${runtimeError.filename ?? "<unknown>"}:${runtimeError.lineno ?? 0}:${runtimeError.colno ?? 0})`;
1057
+ return `${errType}: ${runtimeError.message}
1058
+ ${frame}`;
1059
+ }
1060
+ if (sandboxError) {
1061
+ return sandboxError.stack ?? `SandboxError: ${sandboxError.message}`;
1062
+ }
1063
+ return "";
1064
+ }, [buildError, runtimeError, sandboxError]);
1065
+ const showOverlay = Boolean(buildError && buildError.length > 0) || Boolean(runtimeError) || Boolean(sandboxError);
1066
+ useImperativeHandle(ref, () => ({
1067
+ rebuild: () => innerRef.current?.rebuild(),
1068
+ reload: () => innerRef.current?.reload(),
1069
+ navigate: (u) => innerRef.current?.navigate(u),
1070
+ send: (message) => innerRef.current?.send(message)
1071
+ }), []);
1072
+ const value = useMemo4(() => ({
1073
+ sandbox,
1074
+ iframeProps,
1075
+ rest,
1076
+ onNavigate,
1077
+ innerRef,
1078
+ containerRef,
1079
+ attachHandle,
1080
+ inputValue,
1081
+ setInputValue,
1082
+ currentUrl,
1083
+ history,
1084
+ historyIndex,
1085
+ viewport,
1086
+ setViewport,
1087
+ spinCount,
1088
+ logs,
1089
+ showOverlay,
1090
+ overlayTrace,
1091
+ setRuntimeError,
1092
+ goBack,
1093
+ goForward,
1094
+ reload,
1095
+ overlay,
1096
+ toggleOverlay,
1097
+ submitUrl,
1098
+ download
1099
+ }), [
1100
+ sandbox,
1101
+ iframeProps,
1102
+ rest,
1103
+ onNavigate,
1104
+ attachHandle,
1105
+ inputValue,
1106
+ currentUrl,
1107
+ history,
1108
+ historyIndex,
1109
+ viewport,
1110
+ spinCount,
1111
+ logs,
1112
+ showOverlay,
1113
+ overlayTrace,
1114
+ goBack,
1115
+ goForward,
1116
+ reload,
1117
+ overlay,
1118
+ toggleOverlay,
1119
+ submitUrl,
1120
+ download
1121
+ ]);
1122
+ return /* @__PURE__ */ jsx9(SandboxPreviewCtx.Provider, { value, children });
1123
+ });
1124
+ function SandboxPreviewWeb() {
1125
+ const {
1126
+ sandbox,
1127
+ iframeProps,
1128
+ rest,
1129
+ onNavigate,
1130
+ containerRef,
1131
+ attachHandle,
1132
+ viewport,
1133
+ showOverlay,
1134
+ overlayTrace,
1135
+ setRuntimeError
1136
+ } = useSandboxPreviewCtx();
1137
+ return /* @__PURE__ */ jsxs5("div", { ref: containerRef, className: "flex-1 min-h-0 flex justify-center bg-muted/30 relative h-full", children: [
1138
+ /* @__PURE__ */ jsx9(
1139
+ motion.div,
1140
+ {
1141
+ initial: false,
1142
+ animate: { width: viewport === "mobile" ? 390 : "100%" },
1143
+ transition: { type: "spring", stiffness: 320, damping: 32, mass: 0.7 },
1144
+ className: "h-full",
1145
+ children: /* @__PURE__ */ jsx9(
1146
+ SandboxIFrame,
1147
+ {
1148
+ ref: attachHandle,
1149
+ sandbox,
1150
+ iframeProps: {
1151
+ style: {
1152
+ width: "100%",
1153
+ height: "100%",
1154
+ border: 0,
1155
+ ...iframeProps?.style
1156
+ },
1157
+ ...iframeProps
1158
+ },
1159
+ onNavigate,
1160
+ onRuntimeError: setRuntimeError,
1161
+ ...rest
1162
+ }
1163
+ )
1164
+ }
1165
+ ),
1166
+ showOverlay && /* @__PURE__ */ jsx9("div", { className: "absolute inset-0 overflow-auto bg-background p-3", children: /* @__PURE__ */ jsxs5(StackTrace, { defaultOpen: true, trace: overlayTrace, children: [
1167
+ /* @__PURE__ */ jsxs5(StackTraceHeader, { children: [
1168
+ /* @__PURE__ */ jsxs5(StackTraceError, { children: [
1169
+ /* @__PURE__ */ jsx9(StackTraceErrorType, {}),
1170
+ /* @__PURE__ */ jsx9(StackTraceErrorMessage, {})
1171
+ ] }),
1172
+ /* @__PURE__ */ jsxs5(StackTraceActions, { children: [
1173
+ /* @__PURE__ */ jsx9(StackTraceCopyButton, {}),
1174
+ /* @__PURE__ */ jsx9(StackTraceExpandButton, {})
1175
+ ] })
1176
+ ] }),
1177
+ /* @__PURE__ */ jsx9(StackTraceContent, { children: /* @__PURE__ */ jsx9(StackTraceFrames, {}) })
1178
+ ] }) })
1179
+ ] });
1180
+ }
1181
+ function SandboxPreviewConsole() {
1182
+ const { logs } = useSandboxPreviewCtx();
1183
+ return /* @__PURE__ */ jsx9(WebPreviewConsole, { logs });
1184
+ }
1185
+ function formatArgs(args) {
1186
+ return args.map(formatOne).join(" ");
1187
+ }
1188
+ function formatOne(v) {
1189
+ if (typeof v === "string") return v;
1190
+ if (v === null) return "null";
1191
+ if (v === void 0) return "undefined";
1192
+ if (typeof v === "function") return "[Function]";
1193
+ if (typeof v === "object") {
1194
+ try {
1195
+ return JSON.stringify(v);
1196
+ } catch {
1197
+ return String(v);
1198
+ }
1199
+ }
1200
+ return String(v);
1201
+ }
1202
+
1203
+ // src/editor/sandbox-file-tree.tsx
1204
+ import { useEffect as useEffect3, useMemo as useMemo6, useState as useState6 } from "react";
1205
+ import { MenuIcon } from "lucide-react";
1206
+
1207
+ // src/editor/primitives/file-tree.tsx
1208
+ import {
1209
+ ChevronRightIcon as ChevronRightIcon2,
1210
+ FileIcon,
1211
+ FolderIcon,
1212
+ FolderOpenIcon
1213
+ } from "lucide-react";
1214
+ import {
1215
+ createContext as createContext5,
1216
+ useCallback as useCallback5,
1217
+ useContext as useContext5,
1218
+ useMemo as useMemo5,
1219
+ useState as useState5
1220
+ } from "react";
1221
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1222
+ var noop = () => {
1223
+ };
1224
+ var FileTreeContext = createContext5({
1225
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-new-builtin)
1226
+ expandedPaths: /* @__PURE__ */ new Set(),
1227
+ togglePath: noop
1228
+ });
1229
+ var FileTree = ({
1230
+ expanded: controlledExpanded,
1231
+ defaultExpanded = /* @__PURE__ */ new Set(),
1232
+ selectedPath,
1233
+ onSelect,
1234
+ onExpandedChange,
1235
+ className,
1236
+ children,
1237
+ ...props
1238
+ }) => {
1239
+ const [internalExpanded, setInternalExpanded] = useState5(defaultExpanded);
1240
+ const expandedPaths = controlledExpanded ?? internalExpanded;
1241
+ const togglePath = useCallback5(
1242
+ (path) => {
1243
+ const newExpanded = new Set(expandedPaths);
1244
+ if (newExpanded.has(path)) {
1245
+ newExpanded.delete(path);
1246
+ } else {
1247
+ newExpanded.add(path);
1248
+ }
1249
+ setInternalExpanded(newExpanded);
1250
+ onExpandedChange?.(newExpanded);
1251
+ },
1252
+ [expandedPaths, onExpandedChange]
1253
+ );
1254
+ const contextValue = useMemo5(
1255
+ () => ({ expandedPaths, onSelect, selectedPath, togglePath }),
1256
+ [expandedPaths, onSelect, selectedPath, togglePath]
1257
+ );
1258
+ return /* @__PURE__ */ jsx10(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx10(
1259
+ "div",
1260
+ {
1261
+ className: cn(
1262
+ "rounded-lg border bg-background font-mono text-sm",
1263
+ className
1264
+ ),
1265
+ role: "tree",
1266
+ ...props,
1267
+ children: /* @__PURE__ */ jsx10("div", { className: "p-2", children })
1268
+ }
1269
+ ) });
1270
+ };
1271
+ var FileTreeIcon = ({
1272
+ className,
1273
+ children,
1274
+ ...props
1275
+ }) => /* @__PURE__ */ jsx10("span", { className: cn("shrink-0", className), ...props, children });
1276
+ var FileTreeName = ({
1277
+ className,
1278
+ children,
1279
+ ...props
1280
+ }) => /* @__PURE__ */ jsx10("span", { className: cn("truncate", className), ...props, children });
1281
+ var FileTreeFolderContext = createContext5({
1282
+ isExpanded: false,
1283
+ name: "",
1284
+ path: ""
1285
+ });
1286
+ var FileTreeFolder = ({
1287
+ path,
1288
+ name,
1289
+ className,
1290
+ children,
1291
+ ...props
1292
+ }) => {
1293
+ const { expandedPaths, togglePath, selectedPath, onSelect } = useContext5(FileTreeContext);
1294
+ const isExpanded = expandedPaths.has(path);
1295
+ const isSelected = selectedPath === path;
1296
+ const handleOpenChange = useCallback5(() => {
1297
+ togglePath(path);
1298
+ }, [togglePath, path]);
1299
+ const handleSelect = useCallback5(() => {
1300
+ onSelect?.(path);
1301
+ }, [onSelect, path]);
1302
+ const folderContextValue = useMemo5(
1303
+ () => ({ isExpanded, name, path }),
1304
+ [isExpanded, name, path]
1305
+ );
1306
+ return /* @__PURE__ */ jsx10(FileTreeFolderContext.Provider, { value: folderContextValue, children: /* @__PURE__ */ jsx10(Collapsible, { onOpenChange: handleOpenChange, open: isExpanded, children: /* @__PURE__ */ jsxs6(
1307
+ "div",
1308
+ {
1309
+ className: cn("", className),
1310
+ role: "treeitem",
1311
+ tabIndex: 0,
1312
+ ...props,
1313
+ children: [
1314
+ /* @__PURE__ */ jsxs6(
1315
+ "div",
1316
+ {
1317
+ className: cn(
1318
+ "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
1319
+ isSelected && "bg-muted"
1320
+ ),
1321
+ children: [
1322
+ /* @__PURE__ */ jsx10(CollapsibleTrigger, { render: /* @__PURE__ */ jsx10("button", { className: "flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0", type: "button" }), children: /* @__PURE__ */ jsx10(
1323
+ ChevronRightIcon2,
1324
+ {
1325
+ className: cn(
1326
+ "size-4 shrink-0 text-muted-foreground transition-transform",
1327
+ isExpanded && "rotate-90"
1328
+ )
1329
+ }
1330
+ ) }),
1331
+ /* @__PURE__ */ jsxs6(
1332
+ "button",
1333
+ {
1334
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left",
1335
+ onClick: () => {
1336
+ handleSelect();
1337
+ togglePath(path);
1338
+ },
1339
+ type: "button",
1340
+ children: [
1341
+ /* @__PURE__ */ jsx10(FileTreeIcon, { children: isExpanded ? /* @__PURE__ */ jsx10(FolderOpenIcon, { className: "size-4 text-blue-500" }) : /* @__PURE__ */ jsx10(FolderIcon, { className: "size-4 text-blue-500" }) }),
1342
+ /* @__PURE__ */ jsx10(FileTreeName, { children: name })
1343
+ ]
1344
+ }
1345
+ )
1346
+ ]
1347
+ }
1348
+ ),
1349
+ /* @__PURE__ */ jsx10(CollapsibleContent, { children: /* @__PURE__ */ jsx10("div", { className: "ml-4 border-l pl-2", children }) })
1350
+ ]
1351
+ }
1352
+ ) }) });
1353
+ };
1354
+ var FileTreeFileContext = createContext5({
1355
+ name: "",
1356
+ path: ""
1357
+ });
1358
+ var FileTreeFile = ({
1359
+ path,
1360
+ name,
1361
+ icon,
1362
+ className,
1363
+ children,
1364
+ ...props
1365
+ }) => {
1366
+ const { selectedPath, onSelect } = useContext5(FileTreeContext);
1367
+ const isSelected = selectedPath === path;
1368
+ const handleClick = useCallback5(() => {
1369
+ onSelect?.(path);
1370
+ }, [onSelect, path]);
1371
+ const handleKeyDown = useCallback5(
1372
+ (e) => {
1373
+ if (e.key === "Enter" || e.key === " ") {
1374
+ onSelect?.(path);
1375
+ }
1376
+ },
1377
+ [onSelect, path]
1378
+ );
1379
+ const fileContextValue = useMemo5(() => ({ name, path }), [name, path]);
1380
+ return /* @__PURE__ */ jsx10(FileTreeFileContext.Provider, { value: fileContextValue, children: /* @__PURE__ */ jsx10(
1381
+ "div",
1382
+ {
1383
+ className: cn(
1384
+ "flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
1385
+ isSelected && "bg-muted",
1386
+ className
1387
+ ),
1388
+ onClick: handleClick,
1389
+ onKeyDown: handleKeyDown,
1390
+ role: "treeitem",
1391
+ tabIndex: 0,
1392
+ ...props,
1393
+ children: children ?? /* @__PURE__ */ jsxs6(Fragment2, { children: [
1394
+ /* @__PURE__ */ jsx10("span", { className: "size-4 shrink-0" }),
1395
+ /* @__PURE__ */ jsx10(FileTreeIcon, { children: icon ?? /* @__PURE__ */ jsx10(FileIcon, { className: "size-4 text-muted-foreground" }) }),
1396
+ /* @__PURE__ */ jsx10(FileTreeName, { children: name })
1397
+ ] })
1398
+ }
1399
+ ) });
1400
+ };
1401
+
1402
+ // src/primitives/ui/popover.tsx
1403
+ import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
1404
+ import { jsx as jsx11 } from "react/jsx-runtime";
1405
+ function Popover({ ...props }) {
1406
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
1407
+ }
1408
+ function PopoverTrigger({ ...props }) {
1409
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
1410
+ }
1411
+ function PopoverContent({
1412
+ className,
1413
+ align = "center",
1414
+ alignOffset = 0,
1415
+ side = "bottom",
1416
+ sideOffset = 4,
1417
+ ...props
1418
+ }) {
1419
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx11(
1420
+ PopoverPrimitive.Positioner,
1421
+ {
1422
+ align,
1423
+ alignOffset,
1424
+ side,
1425
+ sideOffset,
1426
+ className: "isolate z-50",
1427
+ children: /* @__PURE__ */ jsx11(
1428
+ PopoverPrimitive.Popup,
1429
+ {
1430
+ "data-slot": "popover-content",
1431
+ className: cn(
1432
+ "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",
1433
+ className
1434
+ ),
1435
+ ...props
1436
+ }
1437
+ )
1438
+ }
1439
+ ) });
1440
+ }
1441
+
1442
+ // src/editor/internal/tree.tsx
1443
+ import { jsx as jsx12 } from "react/jsx-runtime";
1444
+ function buildTree(paths) {
1445
+ const root = { name: "", path: "", children: /* @__PURE__ */ new Map() };
1446
+ for (const path of paths) {
1447
+ const parts = path.split("/").filter(Boolean);
1448
+ let cur = root;
1449
+ for (let i = 0; i < parts.length; i++) {
1450
+ const name = parts[i];
1451
+ const isLeaf = i === parts.length - 1;
1452
+ const childPath = "/" + parts.slice(0, i + 1).join("/");
1453
+ if (!cur.children) break;
1454
+ let child = cur.children.get(name);
1455
+ if (!child) {
1456
+ child = { name, path: childPath, children: isLeaf ? null : /* @__PURE__ */ new Map() };
1457
+ cur.children.set(name, child);
1458
+ }
1459
+ if (!isLeaf && child.children) cur = child;
1460
+ else if (!isLeaf && !child.children) break;
1461
+ }
1462
+ }
1463
+ return root;
1464
+ }
1465
+ function renderTree(node) {
1466
+ if (!node.children) return null;
1467
+ const entries = Array.from(node.children.values()).sort((a, b) => {
1468
+ const aFolder = a.children !== null;
1469
+ const bFolder = b.children !== null;
1470
+ if (aFolder !== bFolder) return aFolder ? -1 : 1;
1471
+ return a.name.localeCompare(b.name);
1472
+ });
1473
+ return entries.map(
1474
+ (entry) => entry.children ? /* @__PURE__ */ jsx12(FileTreeFolder, { name: entry.name, path: entry.path, children: renderTree(entry) }, entry.path) : /* @__PURE__ */ jsx12(FileTreeFile, { name: entry.name, path: entry.path }, entry.path)
1475
+ );
1476
+ }
1477
+ function isFile(path, files) {
1478
+ return files.includes(path);
1479
+ }
1480
+ function defaultExpansion(files) {
1481
+ const roots = ["src", "app", "pages", "components"];
1482
+ const expanded = /* @__PURE__ */ new Set();
1483
+ for (const root of roots) {
1484
+ const abs = "/" + root;
1485
+ if (files.some((f) => f === abs || f.startsWith(`${abs}/`))) {
1486
+ expanded.add(abs);
1487
+ }
1488
+ }
1489
+ return expanded;
1490
+ }
1491
+
1492
+ // src/editor/sandbox-file-tree.tsx
1493
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1494
+ function SandboxFileTree({
1495
+ sandbox: sandboxProp,
1496
+ defaultExpanded,
1497
+ onSelect,
1498
+ display = "tree",
1499
+ triggerLabel,
1500
+ className
1501
+ }) {
1502
+ const elementsCtx = useElementsContext();
1503
+ const sandboxState = useSandbox();
1504
+ const sandbox = sandboxProp ?? sandboxState.sandbox;
1505
+ const [files, setFiles] = useState6(
1506
+ () => sandbox ? sandbox.fs.list() : []
1507
+ );
1508
+ const [open, setOpen] = useState6(false);
1509
+ const selectedPath = elementsCtx?.selectedPath ?? null;
1510
+ const setSelectedPath = elementsCtx?.setSelectedPath;
1511
+ useEffect3(() => {
1512
+ if (!sandbox) return;
1513
+ setFiles(sandbox.fs.list());
1514
+ return sandbox.fs.on("change", () => setFiles(sandbox.fs.list()));
1515
+ }, [sandbox]);
1516
+ const tree = useMemo6(() => buildTree(files), [files]);
1517
+ const handleSelect = (path) => {
1518
+ if (!isFile(path, files)) return;
1519
+ setSelectedPath?.(path);
1520
+ onSelect?.(path);
1521
+ if (display === "dropdown") setOpen(false);
1522
+ };
1523
+ const treeNode = /* @__PURE__ */ jsx13(
1524
+ FileTree,
1525
+ {
1526
+ defaultExpanded: defaultExpanded ?? defaultExpansion(files),
1527
+ onSelect: handleSelect,
1528
+ selectedPath: selectedPath ?? void 0,
1529
+ className: `h-full w-full overflow-auto rounded-none border-0 bg-transparent ${className ?? ""}`.trim(),
1530
+ children: renderTree(tree)
1531
+ }
1532
+ );
1533
+ if (display === "dropdown") {
1534
+ const defaultLabel = selectedPath ? /* @__PURE__ */ jsx13(MenuIcon, { className: "size-4" }) : /* @__PURE__ */ jsxs7(Fragment3, { children: [
1535
+ /* @__PURE__ */ jsx13(MenuIcon, { className: "size-4" }),
1536
+ /* @__PURE__ */ jsx13("span", { children: "Select a file" })
1537
+ ] });
1538
+ return /* @__PURE__ */ jsxs7(Popover, { open, onOpenChange: setOpen, children: [
1539
+ /* @__PURE__ */ jsx13(
1540
+ PopoverTrigger,
1541
+ {
1542
+ render: /* @__PURE__ */ jsx13(
1543
+ Button,
1544
+ {
1545
+ size: "sm",
1546
+ variant: "ghost",
1547
+ "aria-label": "Select file",
1548
+ className: `h-8 gap-2 px-2 text-xs font-normal ${className ?? ""}`.trim()
1549
+ }
1550
+ ),
1551
+ children: triggerLabel ?? defaultLabel
1552
+ }
1553
+ ),
1554
+ /* @__PURE__ */ jsx13(
1555
+ PopoverContent,
1556
+ {
1557
+ align: "start",
1558
+ className: "w-72 p-0 max-h-[26rem] flex flex-col overflow-hidden",
1559
+ children: treeNode
1560
+ }
1561
+ )
1562
+ ] });
1563
+ }
1564
+ return treeNode;
1565
+ }
1566
+
1567
+ // src/editor/sandbox-code-editor.tsx
1568
+ import {
1569
+ useCallback as useCallback6,
1570
+ useEffect as useEffect4,
1571
+ useMemo as useMemo8,
1572
+ useRef as useRef3,
1573
+ useState as useState7
1574
+ } from "react";
1575
+ import { MoreHorizontalIcon } from "lucide-react";
1576
+ import { keymap } from "@codemirror/view";
1577
+
1578
+ // src/editor/primitives/code-editor.tsx
1579
+ import { useMemo as useMemo7 } from "react";
1580
+ import CodeMirror, {
1581
+ EditorView
1582
+ } from "@uiw/react-codemirror";
1583
+ import { javascript } from "@codemirror/lang-javascript";
1584
+ import { css } from "@codemirror/lang-css";
1585
+ import { html } from "@codemirror/lang-html";
1586
+ import { json } from "@codemirror/lang-json";
1587
+ import { markdown } from "@codemirror/lang-markdown";
1588
+ import { jsx as jsx14 } from "react/jsx-runtime";
1589
+ function CodeEditor({
1590
+ value,
1591
+ onChange,
1592
+ language = "plaintext",
1593
+ theme,
1594
+ extensions: extraExtensions,
1595
+ className,
1596
+ basicSetup,
1597
+ ...rest
1598
+ }) {
1599
+ const ctx = useElementsContext();
1600
+ const resolvedTheme = theme ?? ctx?.theme ?? "light";
1601
+ const extensions = useMemo7(() => {
1602
+ const langExt = languageExtension(language);
1603
+ const wrap = EditorView.lineWrapping;
1604
+ const base = [wrap];
1605
+ if (langExt) base.push(langExt);
1606
+ return extraExtensions ? [...base, ...extraExtensions] : base;
1607
+ }, [language, extraExtensions]);
1608
+ return /* @__PURE__ */ jsx14(
1609
+ CodeMirror,
1610
+ {
1611
+ value,
1612
+ onChange,
1613
+ extensions,
1614
+ theme: resolvedTheme,
1615
+ basicSetup: basicSetup ?? {
1616
+ lineNumbers: true,
1617
+ highlightActiveLine: true,
1618
+ highlightActiveLineGutter: true,
1619
+ foldGutter: true,
1620
+ autocompletion: true,
1621
+ bracketMatching: true,
1622
+ closeBrackets: true,
1623
+ indentOnInput: true,
1624
+ searchKeymap: true
1625
+ },
1626
+ className,
1627
+ ...rest
1628
+ }
1629
+ );
1630
+ }
1631
+ function languageExtension(lang) {
1632
+ switch (lang) {
1633
+ case "typescript":
1634
+ return javascript({ typescript: true });
1635
+ case "tsx":
1636
+ return javascript({ typescript: true, jsx: true });
1637
+ case "javascript":
1638
+ return javascript();
1639
+ case "jsx":
1640
+ return javascript({ jsx: true });
1641
+ case "css":
1642
+ return css();
1643
+ case "html":
1644
+ return html();
1645
+ case "json":
1646
+ return json();
1647
+ case "markdown":
1648
+ return markdown();
1649
+ case "plaintext":
1650
+ default:
1651
+ return null;
1652
+ }
1653
+ }
1654
+
1655
+ // src/primitives/ui/loading.tsx
1656
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
1657
+ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs8(
1658
+ "svg",
1659
+ {
1660
+ height: size,
1661
+ strokeLinejoin: "round",
1662
+ style: { color: "currentcolor" },
1663
+ viewBox: "0 0 16 16",
1664
+ width: size,
1665
+ children: [
1666
+ /* @__PURE__ */ jsx15("title", { children: "Loader" }),
1667
+ /* @__PURE__ */ jsxs8("g", { clipPath: "url(#clip0_2393_1490)", children: [
1668
+ /* @__PURE__ */ jsx15("path", { d: "M8 0V4", stroke: "currentColor", strokeWidth: "1.5" }),
1669
+ /* @__PURE__ */ jsx15(
1670
+ "path",
1671
+ {
1672
+ d: "M8 16V12",
1673
+ opacity: "0.5",
1674
+ stroke: "currentColor",
1675
+ strokeWidth: "1.5"
1676
+ }
1677
+ ),
1678
+ /* @__PURE__ */ jsx15(
1679
+ "path",
1680
+ {
1681
+ d: "M3.29773 1.52783L5.64887 4.7639",
1682
+ opacity: "0.9",
1683
+ stroke: "currentColor",
1684
+ strokeWidth: "1.5"
1685
+ }
1686
+ ),
1687
+ /* @__PURE__ */ jsx15(
1688
+ "path",
1689
+ {
1690
+ d: "M12.7023 1.52783L10.3511 4.7639",
1691
+ opacity: "0.1",
1692
+ stroke: "currentColor",
1693
+ strokeWidth: "1.5"
1694
+ }
1695
+ ),
1696
+ /* @__PURE__ */ jsx15(
1697
+ "path",
1698
+ {
1699
+ d: "M12.7023 14.472L10.3511 11.236",
1700
+ opacity: "0.4",
1701
+ stroke: "currentColor",
1702
+ strokeWidth: "1.5"
1703
+ }
1704
+ ),
1705
+ /* @__PURE__ */ jsx15(
1706
+ "path",
1707
+ {
1708
+ d: "M3.29773 14.472L5.64887 11.236",
1709
+ opacity: "0.6",
1710
+ stroke: "currentColor",
1711
+ strokeWidth: "1.5"
1712
+ }
1713
+ ),
1714
+ /* @__PURE__ */ jsx15(
1715
+ "path",
1716
+ {
1717
+ d: "M15.6085 5.52783L11.8043 6.7639",
1718
+ opacity: "0.2",
1719
+ stroke: "currentColor",
1720
+ strokeWidth: "1.5"
1721
+ }
1722
+ ),
1723
+ /* @__PURE__ */ jsx15(
1724
+ "path",
1725
+ {
1726
+ d: "M0.391602 10.472L4.19583 9.23598",
1727
+ opacity: "0.7",
1728
+ stroke: "currentColor",
1729
+ strokeWidth: "1.5"
1730
+ }
1731
+ ),
1732
+ /* @__PURE__ */ jsx15(
1733
+ "path",
1734
+ {
1735
+ d: "M15.6085 10.4722L11.8043 9.2361",
1736
+ opacity: "0.3",
1737
+ stroke: "currentColor",
1738
+ strokeWidth: "1.5"
1739
+ }
1740
+ ),
1741
+ /* @__PURE__ */ jsx15(
1742
+ "path",
1743
+ {
1744
+ d: "M0.391602 5.52783L4.19583 6.7639",
1745
+ opacity: "0.8",
1746
+ stroke: "currentColor",
1747
+ strokeWidth: "1.5"
1748
+ }
1749
+ )
1750
+ ] }),
1751
+ /* @__PURE__ */ jsx15("defs", { children: /* @__PURE__ */ jsx15("clipPath", { id: "clip0_2393_1490", children: /* @__PURE__ */ jsx15("rect", { fill: "white", height: "16", width: "16" }) }) })
1752
+ ]
1753
+ }
1754
+ );
1755
+ var Loader = ({ className, size = 16, ...props }) => /* @__PURE__ */ jsx15(
1756
+ "div",
1757
+ {
1758
+ className: cn(
1759
+ "inline-flex animate-spin items-center justify-center",
1760
+ className
1761
+ ),
1762
+ ...props,
1763
+ children: /* @__PURE__ */ jsx15(LoaderIcon, { size })
1764
+ }
1765
+ );
1766
+
1767
+ // src/editor/internal/language.ts
1768
+ var EXT_TO_LANG = {
1769
+ ts: "typescript",
1770
+ tsx: "tsx",
1771
+ js: "javascript",
1772
+ jsx: "jsx",
1773
+ mjs: "javascript",
1774
+ cjs: "javascript",
1775
+ json: "json",
1776
+ md: "markdown",
1777
+ mdx: "markdown",
1778
+ css: "css",
1779
+ scss: "css",
1780
+ html: "html",
1781
+ htm: "html"
1782
+ };
1783
+ function guessLanguage(path) {
1784
+ const m = path.match(/\.([^.]+)$/);
1785
+ if (!m) return "plaintext";
1786
+ const ext = m[1].toLowerCase();
1787
+ return EXT_TO_LANG[ext] ?? "plaintext";
1788
+ }
1789
+
1790
+ // src/editor/sandbox-code-editor.tsx
1791
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
1792
+ function SandboxCodeEditor({
1793
+ sandbox: sandboxProp,
1794
+ readOnly = false,
1795
+ theme,
1796
+ basicSetup,
1797
+ extensions,
1798
+ language: languageProp,
1799
+ hideHeader = false,
1800
+ emptyState,
1801
+ headerStartSlot,
1802
+ className
1803
+ }) {
1804
+ const elementsCtx = useElementsContext();
1805
+ const sandboxState = useSandbox();
1806
+ const sandbox = sandboxProp ?? sandboxState.sandbox;
1807
+ const selectedPath = elementsCtx?.selectedPath ?? null;
1808
+ const setSelectedPath = elementsCtx?.setSelectedPath;
1809
+ const [savedText, setSavedText] = useState7(null);
1810
+ const [buffer, setBuffer] = useState7("");
1811
+ const [loadingPath, setLoadingPath] = useState7(null);
1812
+ const [saveStatus, setSaveStatus] = useState7("idle");
1813
+ const loadedPathRef = useRef3(null);
1814
+ const savedTimerRef = useRef3(null);
1815
+ const sandboxRef = useRef3(sandbox);
1816
+ const selectedPathRef = useRef3(selectedPath);
1817
+ const bufferRef = useRef3(buffer);
1818
+ const dirtyRef = useRef3(false);
1819
+ sandboxRef.current = sandbox;
1820
+ selectedPathRef.current = selectedPath;
1821
+ bufferRef.current = buffer;
1822
+ useEffect4(() => {
1823
+ if (!sandbox || !selectedPath) {
1824
+ setSavedText(null);
1825
+ setBuffer("");
1826
+ loadedPathRef.current = null;
1827
+ return;
1828
+ }
1829
+ setLoadingPath(selectedPath);
1830
+ setSaveStatus("idle");
1831
+ let cancelled = false;
1832
+ sandbox.fs.readFile(selectedPath).then((c) => {
1833
+ if (cancelled) return;
1834
+ setSavedText(c);
1835
+ setBuffer(c);
1836
+ loadedPathRef.current = selectedPath;
1837
+ setLoadingPath(null);
1838
+ }).catch(() => {
1839
+ if (cancelled) return;
1840
+ const fallback = `// Could not read ${selectedPath}`;
1841
+ setSavedText(fallback);
1842
+ setBuffer(fallback);
1843
+ loadedPathRef.current = null;
1844
+ setLoadingPath(null);
1845
+ });
1846
+ return () => {
1847
+ cancelled = true;
1848
+ };
1849
+ }, [sandbox, selectedPath]);
1850
+ useEffect4(() => {
1851
+ return () => {
1852
+ if (savedTimerRef.current) {
1853
+ clearTimeout(savedTimerRef.current);
1854
+ savedTimerRef.current = null;
1855
+ }
1856
+ };
1857
+ }, [selectedPath]);
1858
+ const handleEditorChange = useCallback6(
1859
+ (next) => {
1860
+ setBuffer(next);
1861
+ if (readOnly) return;
1862
+ if (!selectedPath) return;
1863
+ if (loadedPathRef.current !== selectedPath) return;
1864
+ },
1865
+ [readOnly, selectedPath]
1866
+ );
1867
+ const dirty = !readOnly && savedText !== null && buffer !== savedText;
1868
+ dirtyRef.current = dirty;
1869
+ const deleteFile = useCallback6(async () => {
1870
+ if (readOnly) return;
1871
+ const sb = sandboxRef.current;
1872
+ const path = selectedPathRef.current;
1873
+ if (!sb || !path) return;
1874
+ try {
1875
+ await sb.fs.rm(path);
1876
+ setSelectedPath?.(null);
1877
+ } catch {
1878
+ }
1879
+ }, [readOnly, setSelectedPath]);
1880
+ const save = useCallback6(async () => {
1881
+ if (readOnly) return;
1882
+ const sb = sandboxRef.current;
1883
+ const path = selectedPathRef.current;
1884
+ const text = bufferRef.current;
1885
+ if (!sb || !path) return;
1886
+ if (!dirtyRef.current) return;
1887
+ setSaveStatus("saving");
1888
+ try {
1889
+ await sb.fs.writeFile(path, text);
1890
+ await new Promise((r) => setTimeout(r, 500));
1891
+ setSavedText(text);
1892
+ setSaveStatus("idle");
1893
+ } catch {
1894
+ setSaveStatus("error");
1895
+ }
1896
+ }, [readOnly]);
1897
+ const editorExtensions = useMemo8(
1898
+ () => {
1899
+ const base = [
1900
+ keymap.of([
1901
+ {
1902
+ key: "Mod-s",
1903
+ preventDefault: true,
1904
+ run: () => {
1905
+ void save();
1906
+ return true;
1907
+ }
1908
+ }
1909
+ ])
1910
+ ];
1911
+ return extensions ? [...base, ...extensions] : base;
1912
+ },
1913
+ [save, extensions]
1914
+ );
1915
+ const lang = languageProp ? languageProp : selectedPath ? guessLanguage(selectedPath) : "plaintext";
1916
+ const filename = selectedPath ? selectedPath.split("/").filter(Boolean).pop() ?? selectedPath : null;
1917
+ const headerLabel = selectedPath ? /* @__PURE__ */ jsx16("span", { className: "font-mono", title: selectedPath, children: filename }) : headerStartSlot ? null : /* @__PURE__ */ jsx16("span", { children: "Select a file" });
1918
+ return /* @__PURE__ */ jsxs9("div", { className: `flex h-full w-full flex-col ${className ?? ""}`.trim(), children: [
1919
+ !hideHeader && /* @__PURE__ */ jsxs9("div", { className: "flex flex-none items-center justify-between border-b px-3 py-2 text-xs", children: [
1920
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5 text-muted-foreground", children: [
1921
+ headerStartSlot,
1922
+ headerLabel
1923
+ ] }),
1924
+ selectedPath && !readOnly && /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1", children: [
1925
+ /* @__PURE__ */ jsx16(
1926
+ Button,
1927
+ {
1928
+ size: "sm",
1929
+ onClick: () => void save(),
1930
+ className: "h-9 px-4 text-sm md:h-8 md:px-3 md:text-xs",
1931
+ children: saveStatus === "saving" ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
1932
+ /* @__PURE__ */ jsx16(Loader, { size: 14 }),
1933
+ "Saving..."
1934
+ ] }) : "Save"
1935
+ }
1936
+ ),
1937
+ /* @__PURE__ */ jsxs9(DropdownMenu, { children: [
1938
+ /* @__PURE__ */ jsx16(
1939
+ DropdownMenuTrigger,
1940
+ {
1941
+ render: /* @__PURE__ */ jsx16(
1942
+ Button,
1943
+ {
1944
+ size: "sm",
1945
+ variant: "ghost",
1946
+ "aria-label": "File actions",
1947
+ className: "size-10 p-0 md:size-8"
1948
+ }
1949
+ ),
1950
+ children: /* @__PURE__ */ jsx16(MoreHorizontalIcon, { className: "size-4" })
1951
+ }
1952
+ ),
1953
+ /* @__PURE__ */ jsx16(DropdownMenuContent, { align: "end", children: /* @__PURE__ */ jsx16(
1954
+ DropdownMenuItem,
1955
+ {
1956
+ onClick: () => void deleteFile(),
1957
+ className: "text-destructive focus:text-destructive",
1958
+ children: "Delete"
1959
+ }
1960
+ ) })
1961
+ ] })
1962
+ ] })
1963
+ ] }),
1964
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 min-h-0 overflow-auto", children: selectedPath ? /* @__PURE__ */ jsx16(
1965
+ CodeEditor,
1966
+ {
1967
+ value: buffer,
1968
+ onChange: handleEditorChange,
1969
+ language: lang,
1970
+ theme,
1971
+ basicSetup,
1972
+ readOnly: readOnly || loadingPath === selectedPath,
1973
+ extensions: editorExtensions,
1974
+ height: "100%",
1975
+ className: "h-full text-sm"
1976
+ },
1977
+ selectedPath
1978
+ ) : emptyState ?? /* @__PURE__ */ jsx16("div", { className: "flex h-full w-full items-center justify-center text-muted-foreground text-sm", children: "Select a file from the menu" }) })
1979
+ ] });
1980
+ }
1981
+
1982
+ // src/editor/sandbox-ide.tsx
1983
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1984
+ function SandboxIDE({
1985
+ sandbox,
1986
+ defaultExpanded,
1987
+ hideTree = false,
1988
+ hideViewer = false,
1989
+ hideBorder = false,
1990
+ className,
1991
+ style,
1992
+ ...codeProps
1993
+ }) {
1994
+ if (hideViewer) {
1995
+ return /* @__PURE__ */ jsx17(
1996
+ SandboxFileTree,
1997
+ {
1998
+ sandbox,
1999
+ defaultExpanded,
2000
+ className
2001
+ }
2002
+ );
2003
+ }
2004
+ if (hideTree) {
2005
+ return /* @__PURE__ */ jsx17(
2006
+ SandboxCodeEditor,
2007
+ {
2008
+ sandbox,
2009
+ className,
2010
+ ...codeProps
2011
+ }
2012
+ );
2013
+ }
2014
+ const frameClasses = hideBorder ? "flex h-full w-full flex-col md:flex-row overflow-hidden bg-background" : "flex h-full w-full flex-col md:flex-row overflow-hidden rounded-lg border bg-background";
2015
+ const mobileTrigger = /* @__PURE__ */ jsx17(
2016
+ SandboxFileTree,
2017
+ {
2018
+ sandbox,
2019
+ defaultExpanded,
2020
+ display: "dropdown",
2021
+ className: "md:hidden -ml-1"
2022
+ }
2023
+ );
2024
+ return /* @__PURE__ */ jsxs10("div", { className: `${frameClasses} ${className ?? ""}`.trim(), style, children: [
2025
+ /* @__PURE__ */ jsx17("div", { className: "hidden md:block w-64 shrink-0 overflow-auto border-r", children: /* @__PURE__ */ jsx17(SandboxFileTree, { sandbox, defaultExpanded }) }),
2026
+ /* @__PURE__ */ jsx17("div", { className: "flex-1 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx17(
2027
+ SandboxCodeEditor,
2028
+ {
2029
+ sandbox,
2030
+ headerStartSlot: mobileTrigger,
2031
+ ...codeProps
2032
+ }
2033
+ ) })
2034
+ ] });
2035
+ }
2036
+
2037
+ // src/snapshots/sandbox-snapshots.tsx
2038
+ import { useCallback as useCallback7, useEffect as useEffect5, useState as useState8 } from "react";
2039
+ import { MoreHorizontalIcon as MoreHorizontalIcon2 } from "lucide-react";
2040
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2041
+ function SandboxSnapshots({
2042
+ sandbox: sandboxProp,
2043
+ className,
2044
+ title = "Snapshots"
2045
+ }) {
2046
+ const { sandbox: ctxSandbox } = useSandbox();
2047
+ const sandbox = sandboxProp ?? ctxSandbox ?? null;
2048
+ const [items, setItems] = useState8([]);
2049
+ const [loading, setLoading] = useState8(false);
2050
+ const [creating, setCreating] = useState8(false);
2051
+ const [restoringId, setRestoringId] = useState8(null);
2052
+ const refresh = useCallback7(async () => {
2053
+ if (!sandbox) return;
2054
+ setLoading(true);
2055
+ try {
2056
+ const list = await sandbox.snapshots.list();
2057
+ setItems(list);
2058
+ } finally {
2059
+ setLoading(false);
2060
+ }
2061
+ }, [sandbox]);
2062
+ useEffect5(() => {
2063
+ void refresh();
2064
+ }, [refresh]);
2065
+ const create = useCallback7(async () => {
2066
+ if (!sandbox || creating) return;
2067
+ setCreating(true);
2068
+ try {
2069
+ await sandbox.snapshots.create();
2070
+ await refresh();
2071
+ } catch (err) {
2072
+ console.error("Failed to create snapshot", err);
2073
+ } finally {
2074
+ setCreating(false);
2075
+ }
2076
+ }, [sandbox, creating, refresh]);
2077
+ const restore = useCallback7(
2078
+ async (id) => {
2079
+ if (!sandbox) return;
2080
+ setRestoringId(id);
2081
+ try {
2082
+ await sandbox.snapshots.restore(id);
2083
+ } catch (err) {
2084
+ console.error("Failed to restore snapshot", err);
2085
+ } finally {
2086
+ setRestoringId(null);
2087
+ }
2088
+ },
2089
+ [sandbox]
2090
+ );
2091
+ const [confirmingDeleteId, setConfirmingDeleteId] = useState8(
2092
+ null
2093
+ );
2094
+ const remove = useCallback7(
2095
+ async (id) => {
2096
+ if (!sandbox) return;
2097
+ try {
2098
+ await sandbox.snapshots.delete(id);
2099
+ setConfirmingDeleteId(null);
2100
+ await refresh();
2101
+ } catch (err) {
2102
+ console.error("Failed to delete snapshot", err);
2103
+ }
2104
+ },
2105
+ [sandbox, refresh]
2106
+ );
2107
+ return /* @__PURE__ */ jsxs11(
2108
+ "div",
2109
+ {
2110
+ className: `flex min-h-0 flex-1 w-full flex-col ${className ?? ""}`.trim(),
2111
+ children: [
2112
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-none items-center justify-between border-b px-3 py-2", children: [
2113
+ title !== null ? /* @__PURE__ */ jsx18("span", { className: "text-sm font-medium", children: title }) : /* @__PURE__ */ jsx18("span", {}),
2114
+ /* @__PURE__ */ jsx18(
2115
+ Button,
2116
+ {
2117
+ size: "sm",
2118
+ onClick: () => void create(),
2119
+ disabled: !sandbox || creating,
2120
+ children: creating ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
2121
+ /* @__PURE__ */ jsx18(Loader, { size: 14 }),
2122
+ "Saving..."
2123
+ ] }) : "New snapshot"
2124
+ }
2125
+ )
2126
+ ] }),
2127
+ /* @__PURE__ */ jsx18(
2128
+ "div",
2129
+ {
2130
+ className: "flex-1 min-h-0 overflow-y-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden",
2131
+ children: loading && items.length === 0 ? /* @__PURE__ */ jsx18("div", { className: "flex h-full items-center justify-center text-xs text-muted-foreground", children: "Loading\u2026" }) : items.length === 0 ? /* @__PURE__ */ jsx18("div", { className: "flex items-center justify-center px-4 py-10 text-center text-xs text-muted-foreground", children: "No snapshots yet. Capture the current state to create one." }) : /* @__PURE__ */ jsx18("ul", { className: "divide-y", children: items.map((s) => /* @__PURE__ */ jsxs11(
2132
+ "li",
2133
+ {
2134
+ className: "group flex cursor-pointer items-center justify-between gap-2 px-3 py-2 text-sm transition-colors hover:bg-muted/50",
2135
+ children: [
2136
+ /* @__PURE__ */ jsxs11(
2137
+ "button",
2138
+ {
2139
+ type: "button",
2140
+ onClick: () => void restore(s.id),
2141
+ disabled: restoringId !== null,
2142
+ className: "flex min-w-0 flex-1 cursor-pointer flex-col items-start text-left disabled:cursor-not-allowed disabled:opacity-50",
2143
+ children: [
2144
+ /* @__PURE__ */ jsx18("span", { className: "truncate font-medium", children: s.name ?? `v${s.version}` }),
2145
+ /* @__PURE__ */ jsx18("span", { className: "text-xs text-muted-foreground", children: formatTime(s.createdAt) })
2146
+ ]
2147
+ }
2148
+ ),
2149
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1", children: [
2150
+ restoringId === s.id ? /* @__PURE__ */ jsx18(Loader, { size: 14 }) : null,
2151
+ /* @__PURE__ */ jsxs11(
2152
+ DropdownMenu,
2153
+ {
2154
+ onOpenChange: (open) => {
2155
+ if (!open) setConfirmingDeleteId(null);
2156
+ },
2157
+ children: [
2158
+ /* @__PURE__ */ jsx18(
2159
+ DropdownMenuTrigger,
2160
+ {
2161
+ render: /* @__PURE__ */ jsx18(
2162
+ Button,
2163
+ {
2164
+ size: "sm",
2165
+ variant: "ghost",
2166
+ "aria-label": "Snapshot actions",
2167
+ className: "size-8 p-0"
2168
+ }
2169
+ ),
2170
+ children: /* @__PURE__ */ jsx18(MoreHorizontalIcon2, { className: "size-4" })
2171
+ }
2172
+ ),
2173
+ /* @__PURE__ */ jsxs11(DropdownMenuContent, { align: "end", children: [
2174
+ /* @__PURE__ */ jsx18(DropdownMenuItem, { onClick: () => void restore(s.id), children: "Restore" }),
2175
+ /* @__PURE__ */ jsx18(
2176
+ DropdownMenuItem,
2177
+ {
2178
+ closeOnClick: confirmingDeleteId === s.id,
2179
+ onClick: (e) => {
2180
+ if (confirmingDeleteId !== s.id) {
2181
+ e.preventDefault();
2182
+ setConfirmingDeleteId(s.id);
2183
+ return;
2184
+ }
2185
+ void remove(s.id);
2186
+ },
2187
+ className: "text-destructive focus:text-destructive",
2188
+ children: confirmingDeleteId === s.id ? "Confirm delete" : "Delete"
2189
+ }
2190
+ )
2191
+ ] })
2192
+ ]
2193
+ }
2194
+ )
2195
+ ] })
2196
+ ]
2197
+ },
2198
+ s.id
2199
+ )) })
2200
+ }
2201
+ )
2202
+ ]
2203
+ }
2204
+ );
2205
+ }
2206
+ function formatTime(ts) {
2207
+ const d = new Date(ts);
2208
+ return d.toLocaleString(void 0, {
2209
+ month: "short",
2210
+ day: "numeric",
2211
+ hour: "numeric",
2212
+ minute: "2-digit"
2213
+ });
2214
+ }
2215
+
2216
+ // src/sandbox-attribution.tsx
2217
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2218
+ var POSITION = {
2219
+ position: "absolute",
2220
+ bottom: 14,
2221
+ right: 14,
2222
+ zIndex: 10,
2223
+ pointerEvents: "auto"
2224
+ };
2225
+ function SandboxAttribution({
2226
+ className,
2227
+ style,
2228
+ href = "https://browsernode.dev"
2229
+ }) {
2230
+ return /* @__PURE__ */ jsxs12(
2231
+ "a",
2232
+ {
2233
+ href,
2234
+ target: "_blank",
2235
+ rel: "noopener noreferrer",
2236
+ "aria-label": "BrowserNode \u2014 visit browsernode.dev",
2237
+ style: { ...POSITION, ...style },
2238
+ className: cn(
2239
+ // Solid pill — guaranteed legibility over any iframe content.
2240
+ // Matches the canonical BrowserNode wordmark (white "Browser" +
2241
+ // violet "Node") on a near-black surface.
2242
+ "inline-flex items-center rounded-full",
2243
+ "px-2.5 pt-1 pb-1.5 text-[11px] font-semibold tracking-tight leading-none",
2244
+ "bg-zinc-950 ring-1 ring-white/10",
2245
+ "shadow-lg shadow-black/30",
2246
+ "transition-all duration-200 ease-out",
2247
+ "hover:bg-zinc-900 hover:ring-white/20 hover:scale-[1.04]",
2248
+ "select-none",
2249
+ className
2250
+ ),
2251
+ children: [
2252
+ /* @__PURE__ */ jsx19("span", { className: "text-white", children: "Browser" }),
2253
+ /* @__PURE__ */ jsx19("span", { className: "text-violet-400", children: "Node" })
2254
+ ]
2255
+ }
2256
+ );
2257
+ }
2258
+
2259
+ // src/primitives/ui/tabs.tsx
2260
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
2261
+ import { cva as cva2 } from "class-variance-authority";
2262
+ import { jsx as jsx20 } from "react/jsx-runtime";
2263
+ function Tabs({
2264
+ className,
2265
+ orientation = "horizontal",
2266
+ ...props
2267
+ }) {
2268
+ return /* @__PURE__ */ jsx20(
2269
+ TabsPrimitive.Root,
2270
+ {
2271
+ "data-slot": "tabs",
2272
+ "data-orientation": orientation,
2273
+ className: cn(
2274
+ "group/tabs flex gap-2 data-horizontal:flex-col",
2275
+ className
2276
+ ),
2277
+ ...props
2278
+ }
2279
+ );
2280
+ }
2281
+ var tabsListVariants = cva2(
2282
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
2283
+ {
2284
+ variants: {
2285
+ variant: {
2286
+ default: "bg-muted",
2287
+ line: "gap-1 bg-transparent"
2288
+ }
2289
+ },
2290
+ defaultVariants: {
2291
+ variant: "default"
2292
+ }
2293
+ }
2294
+ );
2295
+ function TabsList({
2296
+ className,
2297
+ variant = "default",
2298
+ ...props
2299
+ }) {
2300
+ return /* @__PURE__ */ jsx20(
2301
+ TabsPrimitive.List,
2302
+ {
2303
+ "data-slot": "tabs-list",
2304
+ "data-variant": variant,
2305
+ className: cn(tabsListVariants({ variant }), className),
2306
+ ...props
2307
+ }
2308
+ );
2309
+ }
2310
+ function TabsTrigger({ className, ...props }) {
2311
+ return /* @__PURE__ */ jsx20(
2312
+ TabsPrimitive.Tab,
2313
+ {
2314
+ "data-slot": "tabs-trigger",
2315
+ className: cn(
2316
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
2317
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
2318
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
2319
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
2320
+ className
2321
+ ),
2322
+ ...props
2323
+ }
2324
+ );
2325
+ }
2326
+
2327
+ // src/sandbox-chrome.tsx
2328
+ import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
2329
+ var DEFAULT_UI = {
2330
+ showCodeEditor: true,
2331
+ showNavigation: true,
2332
+ showReload: true,
2333
+ showDeviceToggle: true,
2334
+ showFullScreenToggle: true,
2335
+ showSnapshots: true,
2336
+ showExportFiles: true,
2337
+ showSettings: false,
2338
+ showConsole: true,
2339
+ showAttribution: true
2340
+ };
2341
+ var SLIDE_TRANSITION = {
2342
+ type: "spring",
2343
+ stiffness: 320,
2344
+ damping: 32,
2345
+ mass: 0.7
2346
+ };
2347
+ function SandboxChrome({ ui, className, style }) {
2348
+ const flags = { ...DEFAULT_UI, ...ui };
2349
+ const [view, setView] = useState9("web");
2350
+ const isCode = view === "code";
2351
+ return /* @__PURE__ */ jsx21(SandboxPreviewProvider, { children: /* @__PURE__ */ jsxs13(SandboxOverlay, { className, style, children: [
2352
+ /* @__PURE__ */ jsx21(
2353
+ SandboxToolbar,
2354
+ {
2355
+ flags,
2356
+ view,
2357
+ onViewChange: setView
2358
+ }
2359
+ ),
2360
+ /* @__PURE__ */ jsxs13("div", { className: "relative flex-1 min-h-0 overflow-hidden", children: [
2361
+ /* @__PURE__ */ jsx21(
2362
+ motion2.div,
2363
+ {
2364
+ className: "absolute inset-0",
2365
+ animate: { x: isCode ? "-100%" : "0%" },
2366
+ transition: SLIDE_TRANSITION,
2367
+ children: /* @__PURE__ */ jsx21(SandboxPreviewWeb, {})
2368
+ }
2369
+ ),
2370
+ flags.showCodeEditor && /* @__PURE__ */ jsx21(
2371
+ motion2.div,
2372
+ {
2373
+ className: "absolute inset-0",
2374
+ initial: { x: "100%" },
2375
+ animate: { x: isCode ? "0%" : "100%" },
2376
+ transition: SLIDE_TRANSITION,
2377
+ children: /* @__PURE__ */ jsx21(SandboxIDE, { hideBorder: true })
2378
+ }
2379
+ ),
2380
+ flags.showAttribution && !isCode && /* @__PURE__ */ jsx21(SandboxAttribution, {})
2381
+ ] }),
2382
+ flags.showConsole && /* @__PURE__ */ jsx21(SandboxPreviewConsole, {})
2383
+ ] }) });
2384
+ }
2385
+ function SandboxOverlay({
2386
+ className,
2387
+ style,
2388
+ children
2389
+ }) {
2390
+ const { overlay } = useSandboxPreviewCtx();
2391
+ return /* @__PURE__ */ jsx21(
2392
+ WebPreview,
2393
+ {
2394
+ defaultUrl: "",
2395
+ className: cn(
2396
+ "flex h-full w-full flex-col",
2397
+ overlay && "fixed inset-0 z-50 bg-background",
2398
+ className
2399
+ ),
2400
+ style,
2401
+ children
2402
+ }
2403
+ );
2404
+ }
2405
+ function SandboxToolbar({ flags, view, onViewChange }) {
2406
+ const {
2407
+ inputValue,
2408
+ setInputValue,
2409
+ currentUrl,
2410
+ history,
2411
+ historyIndex,
2412
+ viewport,
2413
+ setViewport,
2414
+ spinCount,
2415
+ goBack,
2416
+ goForward,
2417
+ reload,
2418
+ overlay,
2419
+ toggleOverlay,
2420
+ submitUrl,
2421
+ download
2422
+ } = useSandboxPreviewCtx();
2423
+ const navItems = flags.showNavigation || flags.showReload ? /* @__PURE__ */ jsxs13(Fragment6, { children: [
2424
+ flags.showNavigation && /* @__PURE__ */ jsxs13(Fragment6, { children: [
2425
+ /* @__PURE__ */ jsx21(
2426
+ WebPreviewNavigationButton,
2427
+ {
2428
+ tooltip: "Back",
2429
+ disabled: historyIndex <= 0,
2430
+ onClick: goBack,
2431
+ children: /* @__PURE__ */ jsx21(ArrowLeftIcon2, { className: "size-4" })
2432
+ }
2433
+ ),
2434
+ /* @__PURE__ */ jsx21(
2435
+ WebPreviewNavigationButton,
2436
+ {
2437
+ tooltip: "Forward",
2438
+ disabled: historyIndex >= history.length - 1,
2439
+ onClick: goForward,
2440
+ children: /* @__PURE__ */ jsx21(ArrowRightIcon2, { className: "size-4" })
2441
+ }
2442
+ ),
2443
+ /* @__PURE__ */ jsx21(
2444
+ Input,
2445
+ {
2446
+ className: "h-8 flex-1 text-sm",
2447
+ placeholder: "Enter URL...",
2448
+ value: inputValue,
2449
+ onChange: (e) => setInputValue(e.target.value),
2450
+ onKeyDown: (e) => {
2451
+ if (e.key === "Enter") {
2452
+ e.preventDefault();
2453
+ submitUrl();
2454
+ } else if (e.key === "Escape") {
2455
+ setInputValue(currentUrl);
2456
+ e.target.blur();
2457
+ }
2458
+ }
2459
+ }
2460
+ )
2461
+ ] }),
2462
+ flags.showReload && /* @__PURE__ */ jsx21(WebPreviewNavigationButton, { tooltip: "Reload", onClick: reload, children: /* @__PURE__ */ jsx21(
2463
+ RefreshCcwIcon2,
2464
+ {
2465
+ className: "size-4 motion-safe:animate-[spin_0.6s_linear_reverse_1]"
2466
+ },
2467
+ spinCount
2468
+ ) })
2469
+ ] }) : null;
2470
+ return /* @__PURE__ */ jsxs13("div", { className: "flex flex-col", children: [
2471
+ navItems && /* @__PURE__ */ jsx21("div", { className: "md:hidden", children: /* @__PURE__ */ jsx21(WebPreviewNavigation, { children: navItems }) }),
2472
+ /* @__PURE__ */ jsxs13(WebPreviewNavigation, { children: [
2473
+ flags.showCodeEditor && /* @__PURE__ */ jsx21(
2474
+ Tabs,
2475
+ {
2476
+ value: view,
2477
+ onValueChange: (v) => onViewChange(v),
2478
+ className: "mr-1",
2479
+ children: /* @__PURE__ */ jsxs13(TabsList, { className: "h-8", children: [
2480
+ /* @__PURE__ */ jsx21(TabsTrigger, { value: "web", "aria-label": "Web", className: "size-7 p-0", children: /* @__PURE__ */ jsx21(GlobeIcon, { className: "size-4" }) }),
2481
+ /* @__PURE__ */ jsx21(TabsTrigger, { value: "code", "aria-label": "Code", className: "size-7 p-0", children: /* @__PURE__ */ jsx21(CodeIcon, { className: "size-4" }) })
2482
+ ] })
2483
+ }
2484
+ ),
2485
+ navItems && /* @__PURE__ */ jsx21("div", { className: "hidden md:contents", children: navItems }),
2486
+ /* @__PURE__ */ jsx21("div", { className: "flex-1 md:hidden" }),
2487
+ flags.showDeviceToggle && /* @__PURE__ */ jsx21(
2488
+ WebPreviewNavigationButton,
2489
+ {
2490
+ tooltip: viewport === "desktop" ? "Mobile view" : "Desktop view",
2491
+ onClick: () => setViewport(viewport === "desktop" ? "mobile" : "desktop"),
2492
+ className: "hidden md:inline-flex",
2493
+ children: viewport === "desktop" ? /* @__PURE__ */ jsx21(SmartphoneIcon2, { className: "size-4" }) : /* @__PURE__ */ jsx21(MonitorIcon2, { className: "size-4" })
2494
+ }
2495
+ ),
2496
+ flags.showFullScreenToggle && /* @__PURE__ */ jsx21(
2497
+ WebPreviewNavigationButton,
2498
+ {
2499
+ tooltip: overlay ? "Exit fullscreen" : "Fullscreen",
2500
+ onClick: toggleOverlay,
2501
+ children: overlay ? /* @__PURE__ */ jsx21(MinimizeIcon2, { className: "size-4" }) : /* @__PURE__ */ jsx21(MaximizeIcon2, { className: "size-4" })
2502
+ }
2503
+ ),
2504
+ flags.showSnapshots && /* @__PURE__ */ jsxs13(Popover, { children: [
2505
+ /* @__PURE__ */ jsx21(
2506
+ PopoverTrigger,
2507
+ {
2508
+ render: /* @__PURE__ */ jsx21(WebPreviewNavigationButton, { tooltip: "Snapshots" }),
2509
+ children: /* @__PURE__ */ jsx21(ClockFadingIcon, { className: "size-4" })
2510
+ }
2511
+ ),
2512
+ /* @__PURE__ */ jsx21(
2513
+ PopoverContent,
2514
+ {
2515
+ align: "end",
2516
+ className: "w-80 p-0 max-h-[26rem] flex flex-col overflow-hidden",
2517
+ children: /* @__PURE__ */ jsx21(SandboxSnapshots, {})
2518
+ }
2519
+ )
2520
+ ] }),
2521
+ (flags.showExportFiles || flags.showSettings) && /* @__PURE__ */ jsxs13(DropdownMenu, { children: [
2522
+ /* @__PURE__ */ jsx21(
2523
+ DropdownMenuTrigger,
2524
+ {
2525
+ render: /* @__PURE__ */ jsx21(WebPreviewNavigationButton, { tooltip: "More" }),
2526
+ children: /* @__PURE__ */ jsx21(MoreVerticalIcon2, { className: "size-4" })
2527
+ }
2528
+ ),
2529
+ /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", className: "min-w-40", children: flags.showExportFiles && /* @__PURE__ */ jsxs13(DropdownMenuItem, { onClick: download, children: [
2530
+ /* @__PURE__ */ jsx21(DownloadIcon2, { className: "size-4" }),
2531
+ "Download .zip"
2532
+ ] }) })
2533
+ ] })
2534
+ ] })
2535
+ ] });
2536
+ }
2537
+
2538
+ // src/sandbox.tsx
2539
+ import { jsx as jsx22 } from "react/jsx-runtime";
2540
+ function Sandbox({ ui, className, style, ...providerProps }) {
2541
+ return /* @__PURE__ */ jsx22(SandboxProvider, { ...providerProps, children: /* @__PURE__ */ jsx22(SandboxChrome, { ui, className, style }) });
2542
+ }
2543
+ export {
2544
+ Sandbox,
2545
+ SandboxAttribution,
2546
+ SandboxChrome,
2547
+ SandboxCodeEditor,
2548
+ WebPreviewConsole as SandboxConsole,
2549
+ SandboxFileTree,
2550
+ SandboxIDE,
2551
+ SandboxProvider,
2552
+ useElementsContext,
2553
+ useElementsContextOrThrow,
2554
+ useSandbox
2555
+ };
2556
+ //# sourceMappingURL=index.js.map