@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.
- package/README.md +107 -0
- package/dist/console.d.ts +3 -0
- package/dist/console.d.ts.map +1 -0
- package/dist/console.js +161 -0
- package/dist/console.js.map +1 -0
- package/dist/editor/internal/language.d.ts +3 -0
- package/dist/editor/internal/language.d.ts.map +1 -0
- package/dist/editor/internal/tree.d.ts +13 -0
- package/dist/editor/internal/tree.d.ts.map +1 -0
- package/dist/editor/primitives/code-editor.d.ts +26 -0
- package/dist/editor/primitives/code-editor.d.ts.map +1 -0
- package/dist/editor/primitives/file-tree.d.ts +27 -0
- package/dist/editor/primitives/file-tree.d.ts.map +1 -0
- package/dist/editor/sandbox-code-editor.d.ts +32 -0
- package/dist/editor/sandbox-code-editor.d.ts.map +1 -0
- package/dist/editor/sandbox-file-tree.d.ts +29 -0
- package/dist/editor/sandbox-file-tree.d.ts.map +1 -0
- package/dist/editor/sandbox-ide.d.ts +23 -0
- package/dist/editor/sandbox-ide.d.ts.map +1 -0
- package/dist/editor.d.ts +6 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +990 -0
- package/dist/editor.js.map +1 -0
- package/dist/files/geist-cyrillic-wght-normal.woff2 +0 -0
- package/dist/files/geist-latin-ext-wght-normal.woff2 +0 -0
- package/dist/files/geist-latin-wght-normal.woff2 +0 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2556 -0
- package/dist/index.js.map +1 -0
- package/dist/preview/primitives/web-preview.d.ts +36 -0
- package/dist/preview/primitives/web-preview.d.ts.map +1 -0
- package/dist/preview/sandbox-preview.d.ts +58 -0
- package/dist/preview/sandbox-preview.d.ts.map +1 -0
- package/dist/primitives/lib/utils.d.ts +3 -0
- package/dist/primitives/lib/utils.d.ts.map +1 -0
- package/dist/primitives/stack-trace.d.ts +38 -0
- package/dist/primitives/stack-trace.d.ts.map +1 -0
- package/dist/primitives/ui/avatar.d.ts +12 -0
- package/dist/primitives/ui/avatar.d.ts.map +1 -0
- package/dist/primitives/ui/badge.d.ts +8 -0
- package/dist/primitives/ui/badge.d.ts.map +1 -0
- package/dist/primitives/ui/button-group.d.ts +11 -0
- package/dist/primitives/ui/button-group.d.ts.map +1 -0
- package/dist/primitives/ui/button.d.ts +9 -0
- package/dist/primitives/ui/button.d.ts.map +1 -0
- package/dist/primitives/ui/collapsible.d.ts +6 -0
- package/dist/primitives/ui/collapsible.d.ts.map +1 -0
- package/dist/primitives/ui/dialog.d.ts +18 -0
- package/dist/primitives/ui/dialog.d.ts.map +1 -0
- package/dist/primitives/ui/dropdown-menu.d.ts +30 -0
- package/dist/primitives/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/primitives/ui/empty.d.ts +12 -0
- package/dist/primitives/ui/empty.d.ts.map +1 -0
- package/dist/primitives/ui/hover-card.d.ts +6 -0
- package/dist/primitives/ui/hover-card.d.ts.map +1 -0
- package/dist/primitives/ui/input-group.d.ts +19 -0
- package/dist/primitives/ui/input-group.d.ts.map +1 -0
- package/dist/primitives/ui/input.d.ts +4 -0
- package/dist/primitives/ui/input.d.ts.map +1 -0
- package/dist/primitives/ui/loading-dots.d.ts +7 -0
- package/dist/primitives/ui/loading-dots.d.ts.map +1 -0
- package/dist/primitives/ui/loading.d.ts +6 -0
- package/dist/primitives/ui/loading.d.ts.map +1 -0
- package/dist/primitives/ui/popover.d.ts +10 -0
- package/dist/primitives/ui/popover.d.ts.map +1 -0
- package/dist/primitives/ui/scroll-area.d.ts +5 -0
- package/dist/primitives/ui/scroll-area.d.ts.map +1 -0
- package/dist/primitives/ui/select.d.ts +16 -0
- package/dist/primitives/ui/select.d.ts.map +1 -0
- package/dist/primitives/ui/separator.d.ts +4 -0
- package/dist/primitives/ui/separator.d.ts.map +1 -0
- package/dist/primitives/ui/spinner.d.ts +3 -0
- package/dist/primitives/ui/spinner.d.ts.map +1 -0
- package/dist/primitives/ui/tabs.d.ts +11 -0
- package/dist/primitives/ui/tabs.d.ts.map +1 -0
- package/dist/primitives/ui/textarea.d.ts +4 -0
- package/dist/primitives/ui/textarea.d.ts.map +1 -0
- package/dist/primitives/ui/tooltip.d.ts +7 -0
- package/dist/primitives/ui/tooltip.d.ts.map +1 -0
- package/dist/provider/sandbox-provider.d.ts +44 -0
- package/dist/provider/sandbox-provider.d.ts.map +1 -0
- package/dist/sandbox-attribution.d.ts +9 -0
- package/dist/sandbox-attribution.d.ts.map +1 -0
- package/dist/sandbox-chrome.d.ts +38 -0
- package/dist/sandbox-chrome.d.ts.map +1 -0
- package/dist/sandbox.d.ts +10 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/snapshots/sandbox-snapshots.d.ts +14 -0
- package/dist/snapshots/sandbox-snapshots.d.ts.map +1 -0
- package/dist/styles.css +2 -0
- 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
|