@joycostudio/studio 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js ADDED
@@ -0,0 +1,1527 @@
1
+ 'use client'
2
+
3
+ // src/react/studio.tsx
4
+ import {
5
+ ChevronLeft,
6
+ ChevronRight,
7
+ LoaderCircle,
8
+ PanelLeftClose,
9
+ PanelLeftOpen,
10
+ Pin as Pin2,
11
+ PlugZap,
12
+ Unplug
13
+ } from "lucide-react";
14
+ import { useEffect as useEffect4, useRef as useRef3, useState as useState7 } from "react";
15
+
16
+ // package.json
17
+ var version = "0.1.0";
18
+
19
+ // src/client/studio.ts
20
+ import { button } from "@joycostudio/xyz/debug";
21
+ var COMPARE_MESSAGE = "joyco-studio:compare";
22
+ function postCompare(target, tool, showSaved) {
23
+ const message = { type: COMPARE_MESSAGE, tool, showSaved };
24
+ target?.postMessage(message, location.origin);
25
+ }
26
+ var instances = globalThis.__joycoStudio ??= /* @__PURE__ */ new Map();
27
+
28
+ // src/react/hooks.ts
29
+ import { useSyncExternalStore } from "react";
30
+ function useStudio(studio) {
31
+ useSyncExternalStore(
32
+ studio.subscribe,
33
+ () => studio.version,
34
+ () => 0
35
+ );
36
+ return studio;
37
+ }
38
+ function useToolConfig(handle) {
39
+ useSyncExternalStore(
40
+ handle.subscribe,
41
+ () => handle.version,
42
+ () => 0
43
+ );
44
+ return handle.config;
45
+ }
46
+
47
+ // src/react/inspector.tsx
48
+ import { bindDebugSchema } from "@joycostudio/xyz/debug";
49
+ import { SquareSplitHorizontal } from "lucide-react";
50
+ import { useEffect, useRef, useState } from "react";
51
+
52
+ // src/core/diff.ts
53
+ function changedPaths(before, after, path = "") {
54
+ const nested = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
55
+ if (nested(before) && nested(after)) {
56
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
57
+ return [...keys].flatMap((key) => changedPaths(before[key], after[key], path ? `${path}.${key}` : key));
58
+ }
59
+ return JSON.stringify(before) === JSON.stringify(after) ? [] : [path];
60
+ }
61
+
62
+ // src/react/theme.ts
63
+ var tweakpaneTheme = {
64
+ "--tp-base-background-color": "transparent",
65
+ "--tp-base-shadow-color": "transparent",
66
+ "--tp-base-border-radius": "0px",
67
+ "--tp-base-font-family": "var(--studio-font)",
68
+ "--tp-blade-border-radius": "var(--radius)",
69
+ "--tp-blade-horizontal-padding": "4px",
70
+ "--tp-blade-value-width": "168px",
71
+ "--tp-container-horizontal-padding": "8px",
72
+ "--tp-container-vertical-padding": "6px",
73
+ "--tp-container-unit-size": "22px",
74
+ "--tp-container-unit-spacing": "6px",
75
+ "--tp-container-background-color": "var(--card)",
76
+ "--tp-container-background-color-hover": "var(--field)",
77
+ "--tp-container-background-color-focus": "var(--field-hover)",
78
+ "--tp-container-background-color-active": "var(--field-active)",
79
+ "--tp-container-foreground-color": "var(--muted-foreground)",
80
+ "--tp-input-background-color": "var(--field)",
81
+ "--tp-input-background-color-hover": "var(--field-hover)",
82
+ "--tp-input-background-color-focus": "var(--field-active)",
83
+ "--tp-input-background-color-active": "var(--field-active)",
84
+ "--tp-input-foreground-color": "var(--foreground)",
85
+ // Buttons and slider knobs: lighter than fields so the knob reads against the track.
86
+ "--tp-button-background-color": "var(--knob)",
87
+ "--tp-button-background-color-hover": "var(--knob-hover)",
88
+ "--tp-button-background-color-focus": "var(--knob-hover)",
89
+ "--tp-button-background-color-active": "var(--muted-foreground)",
90
+ "--tp-button-foreground-color": "var(--foreground)",
91
+ "--tp-groove-foreground-color": "var(--border)",
92
+ "--tp-label-foreground-color": "var(--muted-foreground)",
93
+ "--tp-monitor-background-color": "var(--card)",
94
+ "--tp-monitor-foreground-color": "var(--muted-foreground)"
95
+ };
96
+
97
+ // src/react/tool-icon.tsx
98
+ import { SlidersHorizontal } from "lucide";
99
+ import { jsx } from "react/jsx-runtime";
100
+ var camel = (name) => name.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
101
+ function ToolIcon({ icon = SlidersHorizontal, ...props }) {
102
+ return /* @__PURE__ */ jsx(
103
+ "svg",
104
+ {
105
+ viewBox: "0 0 24 24",
106
+ fill: "none",
107
+ stroke: "currentColor",
108
+ strokeWidth: 2,
109
+ strokeLinecap: "round",
110
+ strokeLinejoin: "round",
111
+ "aria-hidden": true,
112
+ ...props,
113
+ children: icon.map(([Tag, attrs], index) => {
114
+ const Element2 = Tag;
115
+ return /* @__PURE__ */ jsx(
116
+ Element2,
117
+ {
118
+ ...Object.fromEntries(Object.entries(attrs).map(([name, value]) => [camel(name), value]))
119
+ },
120
+ index
121
+ );
122
+ })
123
+ }
124
+ );
125
+ }
126
+
127
+ // src/react/ui/button.tsx
128
+ import { cva } from "class-variance-authority";
129
+
130
+ // src/react/ui/utils.ts
131
+ import { clsx } from "clsx";
132
+ import { twMerge } from "tailwind-merge";
133
+ function cn(...inputs) {
134
+ return twMerge(clsx(inputs));
135
+ }
136
+
137
+ // src/react/ui/button.tsx
138
+ import { Slot } from "radix-ui";
139
+ import { jsx as jsx2 } from "react/jsx-runtime";
140
+ var buttonVariants = cva(
141
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
142
+ {
143
+ variants: {
144
+ variant: {
145
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
146
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
147
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
148
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
149
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
150
+ link: "text-primary underline-offset-4 hover:underline"
151
+ },
152
+ size: {
153
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
154
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
155
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
156
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
157
+ icon: "size-9",
158
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
159
+ "icon-sm": "size-8",
160
+ "icon-lg": "size-10"
161
+ }
162
+ },
163
+ defaultVariants: {
164
+ variant: "default",
165
+ size: "default"
166
+ }
167
+ }
168
+ );
169
+ function Button({
170
+ className,
171
+ variant = "default",
172
+ size = "default",
173
+ asChild = false,
174
+ ...props
175
+ }) {
176
+ const Comp = asChild ? Slot.Root : "button";
177
+ return /* @__PURE__ */ jsx2(
178
+ Comp,
179
+ {
180
+ "data-slot": "button",
181
+ "data-variant": variant,
182
+ "data-size": size,
183
+ className: cn(buttonVariants({ variant, size, className })),
184
+ ...props
185
+ }
186
+ );
187
+ }
188
+
189
+ // src/react/ui/kbd.tsx
190
+ import { jsx as jsx3 } from "react/jsx-runtime";
191
+ function Kbd({ className, ...props }) {
192
+ return /* @__PURE__ */ jsx3(
193
+ "kbd",
194
+ {
195
+ "data-slot": "kbd",
196
+ className: cn(
197
+ "bg-muted text-muted-foreground border-border pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm border border-b-2 px-1 font-mono text-xs font-normal select-none",
198
+ "[&_svg:not([class*='size-'])]:size-3",
199
+ "[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
200
+ className
201
+ ),
202
+ ...props
203
+ }
204
+ );
205
+ }
206
+ function KbdGroup({ className, ...props }) {
207
+ return /* @__PURE__ */ jsx3("span", { "data-slot": "kbd-group", className: cn("inline-flex items-center gap-1", className), ...props });
208
+ }
209
+
210
+ // src/react/ui/tooltip.tsx
211
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
212
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
213
+ function TooltipProvider({ delayDuration = 0, ...props }) {
214
+ return /* @__PURE__ */ jsx4(TooltipPrimitive.Provider, { "data-slot": "tooltip-provider", delayDuration, ...props });
215
+ }
216
+ function Tooltip({ ...props }) {
217
+ return /* @__PURE__ */ jsx4(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props });
218
+ }
219
+ function TooltipTrigger({ ...props }) {
220
+ return /* @__PURE__ */ jsx4(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
221
+ }
222
+ function TooltipContent({
223
+ className,
224
+ sideOffset = 0,
225
+ children,
226
+ ...props
227
+ }) {
228
+ return /* @__PURE__ */ jsx4(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
229
+ TooltipPrimitive.Content,
230
+ {
231
+ "data-slot": "tooltip-content",
232
+ sideOffset,
233
+ className: cn(
234
+ "z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
235
+ className
236
+ ),
237
+ ...props,
238
+ children: [
239
+ children,
240
+ /* @__PURE__ */ jsx4(TooltipPrimitive.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
241
+ ]
242
+ }
243
+ ) });
244
+ }
245
+ function Hint({
246
+ children,
247
+ content,
248
+ ...props
249
+ }) {
250
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
251
+ /* @__PURE__ */ jsx4(TooltipTrigger, { asChild: true, children }),
252
+ /* @__PURE__ */ jsx4(TooltipContent, { ...props, children: content })
253
+ ] });
254
+ }
255
+
256
+ // src/react/ui/panel.tsx
257
+ import { Slot as Slot3 } from "radix-ui";
258
+ import { forwardRef as forwardRef2 } from "react";
259
+
260
+ // src/react/surface.tsx
261
+ import { Slot as Slot2 } from "radix-ui";
262
+ import { forwardRef } from "react";
263
+ import { jsx as jsx5 } from "react/jsx-runtime";
264
+ var StudioSurface = forwardRef(
265
+ function StudioSurface2({ asChild = false, className, ...props }, ref) {
266
+ const Comp = asChild ? Slot2.Root : "div";
267
+ return /* @__PURE__ */ jsx5(Comp, { ref, "data-slot": "studio-surface", className: cn("studio-surface", className), ...props });
268
+ }
269
+ );
270
+ var StudioToolbar = forwardRef(function StudioToolbar2({ className, ...props }, ref) {
271
+ return /* @__PURE__ */ jsx5(StudioSurface, { ref, role: "group", className: cn("studio-toolbar", className), ...props });
272
+ });
273
+
274
+ // src/react/ui/panel.tsx
275
+ import { jsx as jsx6 } from "react/jsx-runtime";
276
+ var Panel = forwardRef2(function Panel2({ className, asChild = false, ...props }, ref) {
277
+ const Comp = asChild ? Slot3.Root : "div";
278
+ return /* @__PURE__ */ jsx6(
279
+ Comp,
280
+ {
281
+ ref,
282
+ "data-slot": "panel",
283
+ className: cn(
284
+ "rounded-lg border border-sidebar-border bg-sidebar/85 shadow-xl shadow-black/40 backdrop-blur-xl",
285
+ className
286
+ ),
287
+ ...props
288
+ }
289
+ );
290
+ });
291
+ function PanelHeader({ className, ...props }) {
292
+ return /* @__PURE__ */ jsx6(StudioSurface, { asChild: true, className: cn("px-3 pt-2.5 pb-2", className), children: /* @__PURE__ */ jsx6("header", { "data-slot": "panel-header", ...props }) });
293
+ }
294
+ var PanelContent = forwardRef2(function PanelContent2({ className, ...props }, ref) {
295
+ return /* @__PURE__ */ jsx6(
296
+ "div",
297
+ {
298
+ ref,
299
+ "data-slot": "panel-content",
300
+ className: cn("min-h-0 flex-1 overflow-auto px-1 py-1.5", className),
301
+ ...props
302
+ }
303
+ );
304
+ });
305
+ function PanelFooter({ className, ...props }) {
306
+ return /* @__PURE__ */ jsx6(StudioSurface, { asChild: true, className: cn("grid gap-2 p-2", className), children: /* @__PURE__ */ jsx6("footer", { "data-slot": "panel-footer", ...props }) });
307
+ }
308
+
309
+ // src/react/inspector.tsx
310
+ import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
311
+ function describe(studio, handle) {
312
+ const { state } = handle;
313
+ if (studio.connection.phase !== "connected") return { tone: "text-faint", text: "Not persisting" };
314
+ if (!state) return { tone: "text-destructive", text: "Restart studio dev" };
315
+ if (handle.comparing) return { tone: "text-sky-400", text: "Showing saved" };
316
+ if (state.conflict) return { tone: "text-destructive", text: "Changed on disk" };
317
+ if (state.dirty) return { tone: "text-warning", text: "Unsaved" };
318
+ return { tone: "text-success", text: state.saved.hash ? "Saved" : "Not on disk yet" };
319
+ }
320
+ function Inspector({
321
+ studio,
322
+ handle,
323
+ onCompare,
324
+ fileHref
325
+ }) {
326
+ useStudio(studio);
327
+ const container = useRef(null);
328
+ const pane = useRef(null);
329
+ const [failure, setFailure] = useState(null);
330
+ const run = (action) => void action.then((result) => setFailure(result.ok ? null : result.message ?? result.error));
331
+ const save = (force = false) => run(handle.save({ force }));
332
+ useEffect(() => {
333
+ let disposed = false;
334
+ let teardown = () => {
335
+ };
336
+ void import("tweakpane").then(({ Pane }) => {
337
+ if (disposed || !container.current) return;
338
+ const instance = new Pane({ container: container.current });
339
+ pane.current = instance;
340
+ const bound = bindDebugSchema(instance, { schema: () => handle.controls() });
341
+ const unsubscribe = handle.subscribe(() => bound.refresh());
342
+ teardown = () => {
343
+ unsubscribe();
344
+ bound.dispose();
345
+ instance.dispose();
346
+ pane.current = null;
347
+ };
348
+ });
349
+ return () => {
350
+ disposed = true;
351
+ teardown();
352
+ };
353
+ }, [handle]);
354
+ useEffect(() => {
355
+ const onKeyDown = (event) => {
356
+ if (event.key !== "s" || !(event.metaKey || event.ctrlKey)) return;
357
+ event.preventDefault();
358
+ if (handle.state?.dirty) save();
359
+ };
360
+ addEventListener("keydown", onKeyDown);
361
+ return () => removeEventListener("keydown", onKeyDown);
362
+ });
363
+ useEffect(() => {
364
+ if (pane.current) pane.current.disabled = handle.comparing;
365
+ });
366
+ const { definition, state } = handle;
367
+ const ConfigFile = fileHref ? "a" : "span";
368
+ const status = describe(studio, handle);
369
+ const editable2 = studio.connection.phase === "connected" && !!state?.dirty && !handle.comparing;
370
+ const changes = (state?.dirty ? changedPaths(state.saved.value, state.draft.value) : []).map(
371
+ (path) => path.split(".").map((key) => key.charAt(0).toUpperCase() + key.slice(1)).join(" \u203A ")
372
+ );
373
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
374
+ /* @__PURE__ */ jsxs2(PanelHeader, { children: [
375
+ /* @__PURE__ */ jsxs2("strong", { className: "flex min-w-0 items-center gap-2 text-foreground", children: [
376
+ /* @__PURE__ */ jsx7(ToolIcon, { icon: definition.icon, className: "size-3.5 shrink-0" }),
377
+ /* @__PURE__ */ jsx7("span", { className: "truncate", title: definition.title ?? definition.id, children: definition.title ?? definition.id })
378
+ ] }),
379
+ /* @__PURE__ */ jsxs2("div", { className: "mt-1 flex items-center gap-3 pl-5.5 text-[11px]", children: [
380
+ /* @__PURE__ */ jsx7(
381
+ ConfigFile,
382
+ {
383
+ "data-slot": "studio-config-file",
384
+ className: "min-w-0 flex-1 truncate text-muted-foreground",
385
+ href: fileHref,
386
+ title: fileHref ? `Open ${definition.file}` : definition.file,
387
+ children: definition.file
388
+ }
389
+ ),
390
+ /* @__PURE__ */ jsxs2(
391
+ "span",
392
+ {
393
+ className: `flex shrink-0 items-center gap-1.5 ${status.tone}`,
394
+ title: state ? `Draft revision ${state.draft.revision}` : void 0,
395
+ children: [
396
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "size-1.5 rounded-full bg-current" }),
397
+ status.text
398
+ ]
399
+ }
400
+ )
401
+ ] })
402
+ ] }),
403
+ /* @__PURE__ */ jsx7(PanelContent, { ref: container, "data-slot": "studio-controls", style: tweakpaneTheme }),
404
+ /* @__PURE__ */ jsxs2(PanelFooter, { children: [
405
+ failure && /* @__PURE__ */ jsx7("div", { className: "text-destructive", children: failure }),
406
+ changes.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "truncate text-muted-foreground", title: changes.join(", "), children: [
407
+ changes.length,
408
+ " changed: ",
409
+ /* @__PURE__ */ jsx7("span", { className: "text-foreground", children: changes.join(", ") })
410
+ ] }),
411
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
412
+ /* @__PURE__ */ jsx7(
413
+ Hint,
414
+ {
415
+ className: "flex items-center gap-2",
416
+ content: /* @__PURE__ */ jsxs2(Fragment, { children: [
417
+ "Hold to compare with saved ",
418
+ /* @__PURE__ */ jsx7(Kbd, { children: "\\" })
419
+ ] }),
420
+ children: /* @__PURE__ */ jsx7(
421
+ Button,
422
+ {
423
+ variant: "secondary",
424
+ size: "icon-sm",
425
+ "aria-label": "Hold to compare with saved",
426
+ "aria-pressed": handle.comparing,
427
+ disabled: !state?.dirty,
428
+ className: "shrink-0 aria-pressed:bg-primary aria-pressed:text-primary-foreground",
429
+ onPointerDown: (event) => {
430
+ event.currentTarget.setPointerCapture(event.pointerId);
431
+ onCompare(true);
432
+ },
433
+ onPointerUp: () => onCompare(false),
434
+ onPointerCancel: () => onCompare(false),
435
+ children: /* @__PURE__ */ jsx7(SquareSplitHorizontal, {})
436
+ }
437
+ )
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx7(
441
+ Button,
442
+ {
443
+ variant: "secondary",
444
+ size: "sm",
445
+ className: "flex-1",
446
+ disabled: !editable2,
447
+ onClick: () => run(handle.discard()),
448
+ children: "Discard"
449
+ }
450
+ ),
451
+ state?.conflict ? /* @__PURE__ */ jsx7(Button, { variant: "destructive", size: "sm", className: "flex-1", onClick: () => save(true), children: "Overwrite file" }) : /* @__PURE__ */ jsxs2(Button, { size: "sm", className: "flex-1", disabled: !editable2, onClick: () => save(), children: [
452
+ "Save",
453
+ /* @__PURE__ */ jsxs2(KbdGroup, { children: [
454
+ /* @__PURE__ */ jsx7(Kbd, { children: "\u2318" }),
455
+ /* @__PURE__ */ jsx7(Kbd, { children: "S" })
456
+ ] })
457
+ ] })
458
+ ] })
459
+ ] })
460
+ ] });
461
+ }
462
+
463
+ // src/react/logo.tsx
464
+ import { jsx as jsx8 } from "react/jsx-runtime";
465
+ function Logo(props) {
466
+ return /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 352 144", fill: "currentColor", "aria-label": "JOYCO", role: "img", ...props, children: /* @__PURE__ */ jsx8("path", { d: "M328 96L280 144H0V40H176V112H192V24H0V0H352V24H328V96Z" }) });
467
+ }
468
+
469
+ // src/react/pinned-tools.ts
470
+ import { useEffect as useEffect2, useState as useState2 } from "react";
471
+ var STORAGE_KEY = "joyco-studio:pinned-tools";
472
+ var CHANGE_EVENT = "joyco-studio:pins-changed";
473
+ function readPins() {
474
+ try {
475
+ const value = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
476
+ return Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
477
+ } catch {
478
+ return [];
479
+ }
480
+ }
481
+ function usePinnedTools(studio) {
482
+ const [pins, setPins] = useState2([]);
483
+ useEffect2(() => {
484
+ setPins(readPins());
485
+ const onChange = (event) => setPins(event.detail);
486
+ const onStorage = (event) => {
487
+ if (event.key === STORAGE_KEY || event.key === null) setPins(readPins());
488
+ };
489
+ window.addEventListener(CHANGE_EVENT, onChange);
490
+ window.addEventListener("storage", onStorage);
491
+ return () => {
492
+ window.removeEventListener(CHANGE_EVENT, onChange);
493
+ window.removeEventListener("storage", onStorage);
494
+ };
495
+ }, []);
496
+ const pinned = new Set(pins);
497
+ const tools = [...studio.tools].sort(
498
+ (a, b) => Number(pinned.has(b.definition.id)) - Number(pinned.has(a.definition.id))
499
+ );
500
+ const togglePin = (id) => {
501
+ const next = pinned.has(id) ? pins.filter((pin) => pin !== id) : [...pins, id];
502
+ try {
503
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
504
+ } catch {
505
+ }
506
+ window.dispatchEvent(new CustomEvent(CHANGE_EVENT, { detail: next }));
507
+ };
508
+ return { tools, pinned, togglePin };
509
+ }
510
+
511
+ // src/react/preview-canvas.tsx
512
+ import { RotateCw } from "lucide-react";
513
+ import { forwardRef as forwardRef3, useImperativeHandle, useRef as useRef2, useState as useState4 } from "react";
514
+
515
+ // src/react/canvas-toolbar.tsx
516
+ import { Maximize, Minimize, Pin, PinOff } from "lucide-react";
517
+ import { useState as useState3 } from "react";
518
+ import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
519
+ function CanvasToolbar({ label, expanded = false, onToggle, children }) {
520
+ const [ghost, setGhost] = useState3(false);
521
+ const ExpandIcon = expanded ? Minimize : Maximize;
522
+ const PinIcon = ghost ? PinOff : Pin;
523
+ return /* @__PURE__ */ jsx9(
524
+ "div",
525
+ {
526
+ className: "studio-canvas-toolbar",
527
+ "data-slot": "canvas-toolbar",
528
+ "data-expanded": expanded || void 0,
529
+ "data-ghost": !expanded && ghost || void 0,
530
+ role: "group",
531
+ "aria-label": label,
532
+ children: /* @__PURE__ */ jsxs3(StudioToolbar, { className: "studio-canvas-actions", children: [
533
+ children,
534
+ onToggle && /* @__PURE__ */ jsx9("div", { className: "studio-canvas-toggle", children: /* @__PURE__ */ jsxs3("button", { "data-slot": "toolbar-action", type: "button", "aria-pressed": expanded, onClick: onToggle, children: [
535
+ /* @__PURE__ */ jsx9(ExpandIcon, { size: 14, strokeWidth: 1.5, "aria-hidden": true }),
536
+ expanded ? "Contain canvas" : "Expand"
537
+ ] }) }),
538
+ !expanded && /* @__PURE__ */ jsx9(
539
+ "button",
540
+ {
541
+ type: "button",
542
+ "aria-label": `Auto-hide ${label.toLowerCase()} header`,
543
+ "aria-pressed": ghost,
544
+ title: ghost ? "Pin header" : "Auto-hide header",
545
+ onClick: () => setGhost(!ghost),
546
+ children: /* @__PURE__ */ jsx9(PinIcon, { size: 14, strokeWidth: 1.5, "aria-hidden": true })
547
+ }
548
+ )
549
+ ] })
550
+ }
551
+ );
552
+ }
553
+
554
+ // src/react/preview-canvas.tsx
555
+ import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
556
+ var PreviewCanvas = forwardRef3(function PreviewCanvas2({ title = "Preview", expanded: controlled, onExpandedChange, onReload, toolbar, ...props }, ref) {
557
+ const [localExpanded, setExpanded] = useState4(false);
558
+ const expanded = controlled ?? localExpanded;
559
+ const frame = useRef2(null);
560
+ useImperativeHandle(ref, () => frame.current, []);
561
+ return /* @__PURE__ */ jsxs4(
562
+ "section",
563
+ {
564
+ className: "studio-preview-canvas",
565
+ "data-slot": "preview-canvas",
566
+ "data-expanded": expanded || void 0,
567
+ "aria-label": `${title} canvas`,
568
+ children: [
569
+ /* @__PURE__ */ jsxs4(
570
+ CanvasToolbar,
571
+ {
572
+ label: "Preview canvas",
573
+ expanded,
574
+ onToggle: () => {
575
+ setExpanded(!expanded);
576
+ onExpandedChange?.(!expanded);
577
+ },
578
+ children: [
579
+ /* @__PURE__ */ jsx10(
580
+ "button",
581
+ {
582
+ type: "button",
583
+ "aria-label": "Reload preview",
584
+ title: "Reload preview",
585
+ onClick: () => {
586
+ onReload?.();
587
+ if (frame.current) frame.current.src = frame.current.src;
588
+ },
589
+ children: /* @__PURE__ */ jsx10(RotateCw, { size: 14, strokeWidth: 1.5, "aria-hidden": true })
590
+ }
591
+ ),
592
+ /* @__PURE__ */ jsx10("div", { className: "studio-preview-source", children: toolbar ?? /* @__PURE__ */ jsx10("span", { className: "studio-preview-title", children: title }) })
593
+ ]
594
+ }
595
+ ),
596
+ /* @__PURE__ */ jsx10("iframe", { ...props, ref: frame, title })
597
+ ]
598
+ }
599
+ );
600
+ });
601
+
602
+ // src/react/ui/sidebar.tsx
603
+ import * as React2 from "react";
604
+ import { cva as cva2 } from "class-variance-authority";
605
+ import { Slot as Slot4 } from "radix-ui";
606
+
607
+ // src/react/ui/hooks/use-mobile.ts
608
+ import * as React from "react";
609
+ var MOBILE_BREAKPOINT = 768;
610
+ function useIsMobile() {
611
+ const [isMobile, setIsMobile] = React.useState(void 0);
612
+ React.useEffect(() => {
613
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
614
+ const onChange = () => {
615
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
616
+ };
617
+ mql.addEventListener("change", onChange);
618
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
619
+ return () => mql.removeEventListener("change", onChange);
620
+ }, []);
621
+ return !!isMobile;
622
+ }
623
+
624
+ // src/react/ui/sheet.tsx
625
+ import { XIcon } from "lucide-react";
626
+ import { Dialog as SheetPrimitive } from "radix-ui";
627
+ import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
628
+ function Sheet({ ...props }) {
629
+ return /* @__PURE__ */ jsx11(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
630
+ }
631
+ function SheetPortal({ ...props }) {
632
+ return /* @__PURE__ */ jsx11(SheetPrimitive.Portal, { "data-slot": "sheet-portal", ...props });
633
+ }
634
+ function SheetOverlay({ className, ...props }) {
635
+ return /* @__PURE__ */ jsx11(
636
+ SheetPrimitive.Overlay,
637
+ {
638
+ "data-slot": "sheet-overlay",
639
+ className: cn(
640
+ "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
641
+ className
642
+ ),
643
+ ...props
644
+ }
645
+ );
646
+ }
647
+ function SheetContent({
648
+ className,
649
+ children,
650
+ side = "right",
651
+ showCloseButton = true,
652
+ ...props
653
+ }) {
654
+ return /* @__PURE__ */ jsxs5(SheetPortal, { children: [
655
+ /* @__PURE__ */ jsx11(SheetOverlay, {}),
656
+ /* @__PURE__ */ jsxs5(
657
+ SheetPrimitive.Content,
658
+ {
659
+ "data-slot": "sheet-content",
660
+ className: cn(
661
+ "fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
662
+ side === "right" && "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
663
+ side === "left" && "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
664
+ side === "top" && "inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
665
+ side === "bottom" && "inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
666
+ className
667
+ ),
668
+ ...props,
669
+ children: [
670
+ children,
671
+ showCloseButton && /* @__PURE__ */ jsxs5(SheetPrimitive.Close, { className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary", children: [
672
+ /* @__PURE__ */ jsx11(XIcon, { className: "size-4" }),
673
+ /* @__PURE__ */ jsx11("span", { className: "sr-only", children: "Close" })
674
+ ] })
675
+ ]
676
+ }
677
+ )
678
+ ] });
679
+ }
680
+ function SheetHeader({ className, ...props }) {
681
+ return /* @__PURE__ */ jsx11("div", { "data-slot": "sheet-header", className: cn("flex flex-col gap-1.5 p-4", className), ...props });
682
+ }
683
+ function SheetTitle({ className, ...props }) {
684
+ return /* @__PURE__ */ jsx11(
685
+ SheetPrimitive.Title,
686
+ {
687
+ "data-slot": "sheet-title",
688
+ className: cn("font-semibold text-foreground", className),
689
+ ...props
690
+ }
691
+ );
692
+ }
693
+ function SheetDescription({ className, ...props }) {
694
+ return /* @__PURE__ */ jsx11(
695
+ SheetPrimitive.Description,
696
+ {
697
+ "data-slot": "sheet-description",
698
+ className: cn("text-sm text-muted-foreground", className),
699
+ ...props
700
+ }
701
+ );
702
+ }
703
+
704
+ // src/react/ui/sidebar.tsx
705
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
706
+ var SIDEBAR_COOKIE_NAME = "sidebar_state";
707
+ var SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
708
+ var SIDEBAR_WIDTH = "16rem";
709
+ var SIDEBAR_WIDTH_MOBILE = "18rem";
710
+ var SIDEBAR_WIDTH_ICON = "3rem";
711
+ var SidebarContext = React2.createContext(null);
712
+ function useSidebar() {
713
+ const context = React2.useContext(SidebarContext);
714
+ if (!context) {
715
+ throw new Error("useSidebar must be used within a SidebarProvider.");
716
+ }
717
+ return context;
718
+ }
719
+ function SidebarProvider({
720
+ defaultOpen = true,
721
+ open: openProp,
722
+ onOpenChange: setOpenProp,
723
+ className,
724
+ style,
725
+ children,
726
+ ...props
727
+ }) {
728
+ const isMobile = useIsMobile();
729
+ const [openMobile, setOpenMobile] = React2.useState(false);
730
+ const [_open, _setOpen] = React2.useState(defaultOpen);
731
+ const open = openProp ?? _open;
732
+ const setOpen = React2.useCallback(
733
+ (value) => {
734
+ const openState = typeof value === "function" ? value(open) : value;
735
+ if (setOpenProp) {
736
+ setOpenProp(openState);
737
+ } else {
738
+ _setOpen(openState);
739
+ }
740
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
741
+ },
742
+ [setOpenProp, open]
743
+ );
744
+ const toggleSidebar = React2.useCallback(() => {
745
+ return isMobile ? setOpenMobile((open2) => !open2) : setOpen((open2) => !open2);
746
+ }, [isMobile, setOpen, setOpenMobile]);
747
+ const state = open ? "expanded" : "collapsed";
748
+ const contextValue = React2.useMemo(
749
+ () => ({
750
+ state,
751
+ open,
752
+ setOpen,
753
+ isMobile,
754
+ openMobile,
755
+ setOpenMobile,
756
+ toggleSidebar
757
+ }),
758
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
759
+ );
760
+ return /* @__PURE__ */ jsx12(SidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx12(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx12(
761
+ "div",
762
+ {
763
+ "data-slot": "sidebar-wrapper",
764
+ style: {
765
+ "--sidebar-width": SIDEBAR_WIDTH,
766
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
767
+ ...style
768
+ },
769
+ className: cn("group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar", className),
770
+ ...props,
771
+ children
772
+ }
773
+ ) }) });
774
+ }
775
+ function Sidebar({
776
+ side = "left",
777
+ variant = "sidebar",
778
+ collapsible = "offcanvas",
779
+ className,
780
+ children,
781
+ ...props
782
+ }) {
783
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
784
+ const Surface = variant === "floating" ? Panel : "div";
785
+ if (collapsible === "none") {
786
+ return /* @__PURE__ */ jsx12(
787
+ "div",
788
+ {
789
+ "data-slot": "sidebar",
790
+ className: cn("flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground", className),
791
+ ...props,
792
+ children
793
+ }
794
+ );
795
+ }
796
+ if (isMobile) {
797
+ return /* @__PURE__ */ jsx12(Sheet, { open: openMobile, onOpenChange: setOpenMobile, ...props, children: /* @__PURE__ */ jsxs6(
798
+ SheetContent,
799
+ {
800
+ "data-sidebar": "sidebar",
801
+ "data-slot": "sidebar",
802
+ "data-mobile": "true",
803
+ className: "w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden",
804
+ style: {
805
+ "--sidebar-width": SIDEBAR_WIDTH_MOBILE
806
+ },
807
+ side,
808
+ children: [
809
+ /* @__PURE__ */ jsxs6(SheetHeader, { className: "sr-only", children: [
810
+ /* @__PURE__ */ jsx12(SheetTitle, { children: "Sidebar" }),
811
+ /* @__PURE__ */ jsx12(SheetDescription, { children: "Displays the mobile sidebar." })
812
+ ] }),
813
+ /* @__PURE__ */ jsx12("div", { className: "flex h-full w-full flex-col", children })
814
+ ]
815
+ }
816
+ ) });
817
+ }
818
+ return /* @__PURE__ */ jsxs6(
819
+ "div",
820
+ {
821
+ className: "group peer hidden text-sidebar-foreground md:block",
822
+ "data-state": state,
823
+ "data-collapsible": state === "collapsed" ? collapsible : "",
824
+ "data-variant": variant,
825
+ "data-side": side,
826
+ "data-slot": "sidebar",
827
+ children: [
828
+ /* @__PURE__ */ jsx12(
829
+ "div",
830
+ {
831
+ "data-slot": "sidebar-gap",
832
+ className: cn(
833
+ "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
834
+ "group-data-[collapsible=offcanvas]:w-0",
835
+ "group-data-[side=right]:rotate-180",
836
+ variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
837
+ )
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsx12(
841
+ "div",
842
+ {
843
+ "data-slot": "sidebar-container",
844
+ className: cn(
845
+ "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
846
+ side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
847
+ // Adjust the padding for floating and inset variants.
848
+ variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
849
+ className
850
+ ),
851
+ ...props,
852
+ children: /* @__PURE__ */ jsx12(
853
+ Surface,
854
+ {
855
+ "data-sidebar": "sidebar",
856
+ "data-slot": "sidebar-inner",
857
+ className: cn("flex h-full w-full flex-col", variant !== "floating" && "bg-sidebar"),
858
+ children
859
+ }
860
+ )
861
+ }
862
+ )
863
+ ]
864
+ }
865
+ );
866
+ }
867
+ function SidebarHeader({ className, ...props }) {
868
+ return /* @__PURE__ */ jsx12(
869
+ "div",
870
+ {
871
+ "data-slot": "sidebar-header",
872
+ "data-sidebar": "header",
873
+ className: cn("flex flex-col gap-2 p-2", className),
874
+ ...props
875
+ }
876
+ );
877
+ }
878
+ function SidebarFooter({ className, ...props }) {
879
+ return /* @__PURE__ */ jsx12(
880
+ "div",
881
+ {
882
+ "data-slot": "sidebar-footer",
883
+ "data-sidebar": "footer",
884
+ className: cn("flex flex-col gap-2 p-2", className),
885
+ ...props
886
+ }
887
+ );
888
+ }
889
+ function SidebarContent({ className, ...props }) {
890
+ return /* @__PURE__ */ jsx12(
891
+ "div",
892
+ {
893
+ "data-slot": "sidebar-content",
894
+ "data-sidebar": "content",
895
+ className: cn(
896
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
897
+ className
898
+ ),
899
+ ...props
900
+ }
901
+ );
902
+ }
903
+ function SidebarGroup({ className, ...props }) {
904
+ return /* @__PURE__ */ jsx12(
905
+ "div",
906
+ {
907
+ "data-slot": "sidebar-group",
908
+ "data-sidebar": "group",
909
+ className: cn("relative flex w-full min-w-0 flex-col p-2", className),
910
+ ...props
911
+ }
912
+ );
913
+ }
914
+ function SidebarMenu({ className, ...props }) {
915
+ return /* @__PURE__ */ jsx12(
916
+ "ul",
917
+ {
918
+ "data-slot": "sidebar-menu",
919
+ "data-sidebar": "menu",
920
+ className: cn("flex w-full min-w-0 flex-col gap-1", className),
921
+ ...props
922
+ }
923
+ );
924
+ }
925
+ function SidebarMenuItem({ className, ...props }) {
926
+ return /* @__PURE__ */ jsx12(
927
+ "li",
928
+ {
929
+ "data-slot": "sidebar-menu-item",
930
+ "data-sidebar": "menu-item",
931
+ className: cn("group/menu-item relative", className),
932
+ ...props
933
+ }
934
+ );
935
+ }
936
+ var sidebarMenuButtonVariants = cva2(
937
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
938
+ {
939
+ variants: {
940
+ variant: {
941
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
942
+ outline: "bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]"
943
+ },
944
+ size: {
945
+ default: "h-8 text-sm",
946
+ sm: "h-7 text-xs",
947
+ lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!"
948
+ }
949
+ },
950
+ defaultVariants: {
951
+ variant: "default",
952
+ size: "default"
953
+ }
954
+ }
955
+ );
956
+ function SidebarMenuButton({
957
+ asChild = false,
958
+ isActive = false,
959
+ variant = "default",
960
+ size = "default",
961
+ tooltip,
962
+ className,
963
+ ...props
964
+ }) {
965
+ const Comp = asChild ? Slot4.Root : "button";
966
+ const { isMobile, state } = useSidebar();
967
+ const button2 = /* @__PURE__ */ jsx12(
968
+ Comp,
969
+ {
970
+ "data-slot": "sidebar-menu-button",
971
+ "data-sidebar": "menu-button",
972
+ "data-size": size,
973
+ "data-active": isActive,
974
+ className: cn(sidebarMenuButtonVariants({ variant, size }), className),
975
+ ...props
976
+ }
977
+ );
978
+ if (!tooltip) return button2;
979
+ const { children, ...hint } = typeof tooltip === "string" ? { children: tooltip } : tooltip;
980
+ return /* @__PURE__ */ jsx12(Hint, { side: "right", align: "center", hidden: state !== "collapsed" || isMobile, ...hint, content: children, children: button2 });
981
+ }
982
+ function SidebarMenuAction({
983
+ className,
984
+ asChild = false,
985
+ showOnHover = false,
986
+ ...props
987
+ }) {
988
+ const Comp = asChild ? Slot4.Root : "button";
989
+ return /* @__PURE__ */ jsx12(
990
+ Comp,
991
+ {
992
+ "data-slot": "sidebar-menu-action",
993
+ "data-sidebar": "menu-action",
994
+ className: cn(
995
+ "absolute top-1/2 right-1 flex aspect-square w-5 -translate-y-1/2 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
996
+ // Increases the hit area of the button on mobile.
997
+ "after:absolute after:-inset-2 md:after:hidden",
998
+ "group-data-[collapsible=icon]:hidden",
999
+ showOnHover && "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:opacity-100 md:opacity-0",
1000
+ className
1001
+ ),
1002
+ ...props
1003
+ }
1004
+ );
1005
+ }
1006
+
1007
+ // src/react/studio.tsx
1008
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1009
+ var CONNECTION = {
1010
+ connected: { icon: PlugZap, tone: "text-success", label: "Runtime connected" },
1011
+ connecting: { icon: LoaderCircle, tone: "text-warning animate-spin", label: "Connecting to runtime" },
1012
+ offline: { icon: Unplug, tone: "text-destructive", label: "Runtime offline" },
1013
+ local: { icon: Unplug, tone: "text-faint", label: "No runtime" }
1014
+ };
1015
+ function StudioFooter({ connection }) {
1016
+ const { state, toggleSidebar } = useSidebar();
1017
+ const { icon: Status, tone, label } = CONNECTION[connection.phase];
1018
+ const Toggle = state === "expanded" ? PanelLeftClose : PanelLeftOpen;
1019
+ return /* @__PURE__ */ jsx13(SidebarFooter, { children: /* @__PURE__ */ jsxs7(SidebarMenu, { children: [
1020
+ /* @__PURE__ */ jsx13(SidebarMenuItem, { children: /* @__PURE__ */ jsxs7(SidebarMenuButton, { tooltip: label, className: "cursor-default hover:bg-transparent active:bg-transparent", children: [
1021
+ /* @__PURE__ */ jsx13(Status, { className: tone }),
1022
+ /* @__PURE__ */ jsx13("span", { className: "text-clip! group-data-[collapsible=icon]:hidden", children: label })
1023
+ ] }) }),
1024
+ /* @__PURE__ */ jsx13(SidebarMenuItem, { children: /* @__PURE__ */ jsxs7(
1025
+ SidebarMenuButton,
1026
+ {
1027
+ "aria-label": `${state === "expanded" ? "Collapse" : "Expand"} sidebar`,
1028
+ tooltip: `${state === "expanded" ? "Collapse" : "Expand"} sidebar`,
1029
+ onClick: toggleSidebar,
1030
+ children: [
1031
+ /* @__PURE__ */ jsx13(Toggle, {}),
1032
+ /* @__PURE__ */ jsx13("span", { className: "text-clip! group-data-[collapsible=icon]:hidden", children: "Collapse" })
1033
+ ]
1034
+ }
1035
+ ) })
1036
+ ] }) });
1037
+ }
1038
+ var SHORTCUTS = /* @__PURE__ */ new Set(["b", "s", "k"]);
1039
+ var COMPARE_KEY = "\\";
1040
+ var editable = (target) => target instanceof Element && target.matches("input, textarea, select, [contenteditable]");
1041
+ var isShortcut = (event) => (event.metaKey || event.ctrlKey) && SHORTCUTS.has(event.key.toLowerCase()) || event.key === COMPARE_KEY && !editable(event.target);
1042
+ function forwardShortcuts(frame) {
1043
+ const forward = (event) => {
1044
+ if (event.defaultPrevented || !isShortcut(event)) return;
1045
+ event.preventDefault();
1046
+ window.dispatchEvent(new KeyboardEvent(event.type, event));
1047
+ };
1048
+ try {
1049
+ frame.contentWindow?.addEventListener("keydown", forward);
1050
+ frame.contentWindow?.addEventListener("keyup", forward);
1051
+ } catch {
1052
+ }
1053
+ }
1054
+ function Studio({ studio, previewComponent, getConfigFileHref }) {
1055
+ useStudio(studio);
1056
+ const { tools, pinned, togglePin } = usePinnedTools(studio);
1057
+ const [mounted, setMounted] = useState7(false);
1058
+ const [panelsHidden, setPanelsHidden] = useState7(false);
1059
+ const [selected, setSelected] = useState7(studio.tools[0]?.definition.id);
1060
+ const preview = useRef3(null);
1061
+ const handle = studio.tools.find((tool) => tool.definition.id === selected);
1062
+ const Preview = previewComponent ?? (handle?.definition.previewLayout === "custom" ? "iframe" : PreviewCanvas);
1063
+ useEffect4(() => {
1064
+ const onKey = (event) => {
1065
+ if (event.defaultPrevented || event.isComposing || event.altKey || !(event.metaKey || event.ctrlKey)) return;
1066
+ if (event.key.toLowerCase() !== "b") return;
1067
+ event.preventDefault();
1068
+ if (event.repeat) return;
1069
+ const active = document.activeElement;
1070
+ if (active instanceof HTMLElement && active.closest('[data-slot="sidebar-container"], [data-slot="studio-inspector"]')) {
1071
+ active.blur();
1072
+ preview.current?.focus();
1073
+ }
1074
+ setPanelsHidden((hidden) => !hidden);
1075
+ };
1076
+ window.addEventListener("keydown", onKey);
1077
+ return () => window.removeEventListener("keydown", onKey);
1078
+ }, []);
1079
+ const compare = (showSaved) => {
1080
+ if (!handle) return;
1081
+ handle.compare(showSaved);
1082
+ postCompare(preview.current?.contentWindow, handle.definition.id, showSaved);
1083
+ };
1084
+ useEffect4(() => () => handle?.compare(false), [handle]);
1085
+ const current = useRef3({ handle, compare });
1086
+ current.current = { handle, compare };
1087
+ useEffect4(() => {
1088
+ const onKey = (event) => {
1089
+ if (event.key !== COMPARE_KEY || editable(event.target) || event.repeat) return;
1090
+ event.preventDefault();
1091
+ const { handle: handle2, compare: compare2 } = current.current;
1092
+ compare2(event.type === "keydown" && !!handle2?.state?.dirty);
1093
+ };
1094
+ const release = () => current.current.compare(false);
1095
+ addEventListener("keydown", onKey);
1096
+ addEventListener("keyup", onKey);
1097
+ addEventListener("blur", release);
1098
+ return () => {
1099
+ release();
1100
+ removeEventListener("keydown", onKey);
1101
+ removeEventListener("keyup", onKey);
1102
+ removeEventListener("blur", release);
1103
+ };
1104
+ }, []);
1105
+ useEffect4(() => {
1106
+ const fromHash = () => {
1107
+ const id = decodeURIComponent(location.hash.slice(1));
1108
+ if (studio.tools.some((handle2) => handle2.definition.id === id)) setSelected(id);
1109
+ };
1110
+ setMounted(true);
1111
+ fromHash();
1112
+ addEventListener("hashchange", fromHash);
1113
+ return () => removeEventListener("hashchange", fromHash);
1114
+ }, [studio]);
1115
+ if (!mounted) return null;
1116
+ const sidebarOpen = !document.cookie.split("; ").includes("sidebar_state=false");
1117
+ return /* @__PURE__ */ jsxs7(
1118
+ SidebarProvider,
1119
+ {
1120
+ defaultOpen: sidebarOpen,
1121
+ "data-preview-layout": previewComponent ? "canvas" : handle?.definition.previewLayout ?? "canvas",
1122
+ className: "studio-workspace relative h-svh min-h-0 overflow-hidden text-xs",
1123
+ children: [
1124
+ handle?.definition.preview ? /* @__PURE__ */ jsx13(
1125
+ Preview,
1126
+ {
1127
+ "data-slot": "studio-preview",
1128
+ ref: preview,
1129
+ src: handle.definition.preview,
1130
+ title: handle.definition.title ?? handle.definition.id,
1131
+ onLoad: (event) => forwardShortcuts(event.currentTarget)
1132
+ },
1133
+ handle.definition.id
1134
+ ) : /* @__PURE__ */ jsx13("div", { className: "absolute inset-0 grid place-items-center text-faint", children: handle ? "This tool has no preview" : "No tools registered" }),
1135
+ panelsHidden && ["left", "right"].map((side) => {
1136
+ const Chevron = side === "left" ? ChevronRight : ChevronLeft;
1137
+ return /* @__PURE__ */ jsx13(
1138
+ "div",
1139
+ {
1140
+ "data-slot": "studio-panel-edge",
1141
+ "data-side": side,
1142
+ className: cn("group/edge absolute inset-y-0 z-20 w-8", side === "left" ? "left-0" : "right-0"),
1143
+ children: /* @__PURE__ */ jsx13(
1144
+ Hint,
1145
+ {
1146
+ side: side === "left" ? "right" : "left",
1147
+ sideOffset: 8,
1148
+ className: "flex items-center gap-2",
1149
+ content: /* @__PURE__ */ jsxs7(Fragment2, { children: [
1150
+ "Show Studio panels",
1151
+ " ",
1152
+ /* @__PURE__ */ jsxs7(KbdGroup, { children: [
1153
+ /* @__PURE__ */ jsx13(Kbd, { children: "\u2318/Ctrl" }),
1154
+ /* @__PURE__ */ jsx13(Kbd, { children: "B" })
1155
+ ] })
1156
+ ] }),
1157
+ children: /* @__PURE__ */ jsx13(Panel, { asChild: true, children: /* @__PURE__ */ jsx13(
1158
+ Button,
1159
+ {
1160
+ "data-slot": "studio-panel-reveal",
1161
+ variant: "ghost",
1162
+ size: "icon",
1163
+ "aria-label": "Show Studio panels",
1164
+ onClick: () => setPanelsHidden(false),
1165
+ className: cn(
1166
+ "absolute top-1/2 h-16 w-6 -translate-y-1/2 cursor-pointer px-0 text-sidebar-foreground opacity-0 transition-[translate,opacity] duration-200 ease-out hover:bg-sidebar/95 hover:text-foreground focus:translate-x-0 focus:opacity-100 group-hover/edge:translate-x-0 group-hover/edge:opacity-100 motion-reduce:transition-none [@media(hover:none)]:translate-x-0 [@media(hover:none)]:opacity-100",
1167
+ side === "left" ? "left-1 -translate-x-[calc(100%+0.25rem)]" : "right-1 translate-x-[calc(100%+0.25rem)]"
1168
+ ),
1169
+ children: /* @__PURE__ */ jsx13(Chevron, { "aria-hidden": true })
1170
+ }
1171
+ ) })
1172
+ }
1173
+ )
1174
+ },
1175
+ side
1176
+ );
1177
+ }),
1178
+ /* @__PURE__ */ jsxs7(
1179
+ Sidebar,
1180
+ {
1181
+ collapsible: "icon",
1182
+ variant: "floating",
1183
+ inert: panelsHidden,
1184
+ "aria-hidden": panelsHidden,
1185
+ className: cn(
1186
+ "transition-[left,right,width,translate] duration-200 ease-out motion-reduce:transition-none",
1187
+ panelsHidden && "-translate-x-[calc(100%+1rem)]"
1188
+ ),
1189
+ children: [
1190
+ /* @__PURE__ */ jsx13(SidebarHeader, { className: "h-10 flex-row items-center overflow-hidden py-0 pr-0 pl-4 transition-[padding] duration-200 ease-out group-data-[collapsible=icon]:pl-[calc(1.5rem-0.875rem*352/144/2)] motion-reduce:transition-none", children: /* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-2.5 text-foreground", children: [
1191
+ /* @__PURE__ */ jsx13(Logo, { className: "h-3.5 w-auto shrink-0" }),
1192
+ /* @__PURE__ */ jsx13("strong", { className: "tracking-[0.08em] whitespace-nowrap", children: "STUDIO" }),
1193
+ /* @__PURE__ */ jsxs7("span", { "data-slot": "studio-version", className: "text-[10px] text-faint group-data-[collapsible=icon]:hidden", children: [
1194
+ "v",
1195
+ version
1196
+ ] })
1197
+ ] }) }),
1198
+ /* @__PURE__ */ jsx13(SidebarContent, { children: /* @__PURE__ */ jsx13(SidebarGroup, { children: /* @__PURE__ */ jsx13(SidebarMenu, { children: tools.map(({ definition, state }) => {
1199
+ const title = definition.title ?? definition.id;
1200
+ const isPinned = pinned.has(definition.id);
1201
+ const pinLabel = `${isPinned ? "Unpin" : "Pin"} ${title}`;
1202
+ return /* @__PURE__ */ jsxs7(SidebarMenuItem, { children: [
1203
+ /* @__PURE__ */ jsxs7(
1204
+ SidebarMenuButton,
1205
+ {
1206
+ isActive: definition.id === selected,
1207
+ tooltip: title,
1208
+ onClick: () => location.hash = definition.id,
1209
+ children: [
1210
+ /* @__PURE__ */ jsxs7("span", { className: "relative", children: [
1211
+ /* @__PURE__ */ jsx13(ToolIcon, { icon: definition.icon, className: "size-4" }),
1212
+ state?.dirty && /* @__PURE__ */ jsx13(
1213
+ "span",
1214
+ {
1215
+ "aria-label": state.conflict ? "Changed on disk" : "Unsaved",
1216
+ className: cn(
1217
+ "absolute -top-0.5 -right-0.5 size-1.5 rounded-full",
1218
+ state.conflict ? "bg-destructive" : "bg-warning"
1219
+ )
1220
+ }
1221
+ )
1222
+ ] }),
1223
+ /* @__PURE__ */ jsx13("span", { className: "text-clip! group-data-[collapsible=icon]:hidden", children: title })
1224
+ ]
1225
+ }
1226
+ ),
1227
+ /* @__PURE__ */ jsx13(Hint, { content: pinLabel, side: "right", children: /* @__PURE__ */ jsx13(
1228
+ SidebarMenuAction,
1229
+ {
1230
+ type: "button",
1231
+ "aria-label": pinLabel,
1232
+ "aria-pressed": isPinned,
1233
+ showOnHover: !isPinned,
1234
+ onClick: () => togglePin(definition.id),
1235
+ className: cn(
1236
+ "cursor-pointer [&>svg]:size-3 [@media(hover:none)]:opacity-100",
1237
+ isPinned && "text-sidebar-primary"
1238
+ ),
1239
+ children: /* @__PURE__ */ jsx13(Pin2, { "aria-hidden": true, fill: isPinned ? "currentColor" : "none" })
1240
+ }
1241
+ ) })
1242
+ ] }, definition.id);
1243
+ }) }) }) }),
1244
+ /* @__PURE__ */ jsx13(StudioFooter, { connection: studio.connection })
1245
+ ]
1246
+ }
1247
+ ),
1248
+ handle && /* @__PURE__ */ jsx13(Panel, { asChild: true, children: /* @__PURE__ */ jsx13(
1249
+ "aside",
1250
+ {
1251
+ "data-slot": "studio-inspector",
1252
+ inert: panelsHidden,
1253
+ "aria-hidden": panelsHidden,
1254
+ className: cn(
1255
+ "absolute inset-y-2 right-2 z-10 flex w-(--studio-inspector-width) flex-col gap-1 p-1 transition-transform duration-200 ease-out motion-reduce:transition-none",
1256
+ panelsHidden && "translate-x-[calc(100%+1rem)]"
1257
+ ),
1258
+ children: /* @__PURE__ */ jsx13(
1259
+ Inspector,
1260
+ {
1261
+ studio,
1262
+ handle,
1263
+ onCompare: compare,
1264
+ fileHref: getConfigFileHref?.(handle.definition.file)
1265
+ },
1266
+ handle.definition.id
1267
+ )
1268
+ }
1269
+ ) })
1270
+ ]
1271
+ }
1272
+ );
1273
+ }
1274
+
1275
+ // src/react/command-menu.tsx
1276
+ import { Pin as Pin3 } from "lucide-react";
1277
+ import { Dialog } from "radix-ui";
1278
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState8 } from "react";
1279
+
1280
+ // src/react/ui/command.tsx
1281
+ import * as React3 from "react";
1282
+ import { Command as CommandPrimitive } from "cmdk";
1283
+ import { SearchIcon } from "lucide-react";
1284
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1285
+ function Command({ className, ...props }) {
1286
+ return /* @__PURE__ */ jsx14(
1287
+ CommandPrimitive,
1288
+ {
1289
+ "data-slot": "command",
1290
+ className: cn(
1291
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
1292
+ className
1293
+ ),
1294
+ ...props
1295
+ }
1296
+ );
1297
+ }
1298
+ var CommandInput = React3.forwardRef(function CommandInput2({ className, ...props }, ref) {
1299
+ return /* @__PURE__ */ jsxs8("div", { "data-slot": "command-input-wrapper", className: "flex h-9 items-center gap-2 border-b px-3", children: [
1300
+ /* @__PURE__ */ jsx14(SearchIcon, { "aria-hidden": true, className: "size-4 shrink-0 opacity-50" }),
1301
+ /* @__PURE__ */ jsx14(
1302
+ CommandPrimitive.Input,
1303
+ {
1304
+ ref,
1305
+ "data-slot": "command-input",
1306
+ className: cn(
1307
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
1308
+ className
1309
+ ),
1310
+ ...props
1311
+ }
1312
+ )
1313
+ ] });
1314
+ });
1315
+ function CommandList({ className, ...props }) {
1316
+ return /* @__PURE__ */ jsx14(
1317
+ CommandPrimitive.List,
1318
+ {
1319
+ "data-slot": "command-list",
1320
+ className: cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className),
1321
+ ...props
1322
+ }
1323
+ );
1324
+ }
1325
+ function CommandEmpty({ ...props }) {
1326
+ return /* @__PURE__ */ jsx14(CommandPrimitive.Empty, { "data-slot": "command-empty", className: "py-6 text-center text-sm", ...props });
1327
+ }
1328
+ function CommandItem({ className, ...props }) {
1329
+ return /* @__PURE__ */ jsx14(
1330
+ CommandPrimitive.Item,
1331
+ {
1332
+ "data-slot": "command-item",
1333
+ className: cn(
1334
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
1335
+ className
1336
+ ),
1337
+ ...props
1338
+ }
1339
+ );
1340
+ }
1341
+
1342
+ // src/react/command-menu.tsx
1343
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1344
+ function StudioCommandMenu({ studio, studioPath = "/studio", navigate }) {
1345
+ const { tools, pinned } = usePinnedTools(studio);
1346
+ const [open, setOpen] = useState8(false);
1347
+ const opener = useRef4(null);
1348
+ const input = useRef4(null);
1349
+ useEffect5(() => {
1350
+ if (window.frameElement?.getAttribute("data-slot") === "studio-preview") return;
1351
+ const onKey = (event) => {
1352
+ if (event.defaultPrevented || event.isComposing || event.altKey || !(event.metaKey || event.ctrlKey)) return;
1353
+ if (event.key.toLowerCase() !== "k") return;
1354
+ event.preventDefault();
1355
+ if (event.repeat) return;
1356
+ setOpen((current) => !current);
1357
+ };
1358
+ window.addEventListener("keydown", onKey);
1359
+ return () => window.removeEventListener("keydown", onKey);
1360
+ }, []);
1361
+ const select = (id) => {
1362
+ setOpen(false);
1363
+ const target = new URL(studioPath, window.location.href);
1364
+ target.hash = id;
1365
+ if (target.pathname === window.location.pathname) {
1366
+ window.location.hash = target.hash;
1367
+ } else if (navigate) {
1368
+ navigate(`${target.pathname}${target.search}${target.hash}`);
1369
+ } else {
1370
+ window.location.assign(target.href);
1371
+ }
1372
+ };
1373
+ return /* @__PURE__ */ jsx15(Dialog.Root, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs9(Dialog.Portal, { children: [
1374
+ /* @__PURE__ */ jsx15(Dialog.Overlay, { "data-slot": "studio-command-overlay" }),
1375
+ /* @__PURE__ */ jsxs9(
1376
+ Dialog.Content,
1377
+ {
1378
+ "data-slot": "studio-command-menu",
1379
+ onOpenAutoFocus: (event) => {
1380
+ event.preventDefault();
1381
+ opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1382
+ input.current?.focus();
1383
+ },
1384
+ onCloseAutoFocus: (event) => {
1385
+ event.preventDefault();
1386
+ opener.current?.focus();
1387
+ },
1388
+ children: [
1389
+ /* @__PURE__ */ jsxs9("div", { "data-slot": "studio-command-heading", children: [
1390
+ /* @__PURE__ */ jsxs9(Dialog.Title, { "aria-label": "Studio tools", children: [
1391
+ /* @__PURE__ */ jsx15(Logo, { "aria-hidden": true }),
1392
+ /* @__PURE__ */ jsx15("strong", { children: "STUDIO" })
1393
+ ] }),
1394
+ /* @__PURE__ */ jsx15(Dialog.Close, { "aria-label": "Close tool menu", children: /* @__PURE__ */ jsx15(Kbd, { children: "esc" }) })
1395
+ ] }),
1396
+ /* @__PURE__ */ jsx15(Dialog.Description, { "data-slot": "studio-command-description", children: "Open a tool in Studio." }),
1397
+ /* @__PURE__ */ jsxs9(Command, { label: "Studio tools", loop: true, vimBindings: false, children: [
1398
+ /* @__PURE__ */ jsx15(CommandInput, { ref: input, placeholder: "Search tools\u2026" }),
1399
+ /* @__PURE__ */ jsxs9(CommandList, { children: [
1400
+ /* @__PURE__ */ jsx15(CommandEmpty, { children: studio.tools.length ? "No tools found." : "No tools registered yet." }),
1401
+ tools.map(({ definition: tool }) => /* @__PURE__ */ jsxs9(
1402
+ CommandItem,
1403
+ {
1404
+ value: tool.id,
1405
+ keywords: [tool.title ?? tool.id],
1406
+ onSelect: () => select(tool.id),
1407
+ children: [
1408
+ /* @__PURE__ */ jsx15(ToolIcon, { icon: tool.icon }),
1409
+ /* @__PURE__ */ jsx15("span", { "data-slot": "studio-command-label", children: tool.title ?? tool.id }),
1410
+ pinned.has(tool.id) && /* @__PURE__ */ jsx15(Pin3, { "aria-hidden": true })
1411
+ ]
1412
+ },
1413
+ tool.id
1414
+ ))
1415
+ ] })
1416
+ ] }),
1417
+ /* @__PURE__ */ jsxs9("div", { "data-slot": "studio-command-footer", "aria-hidden": true, children: [
1418
+ /* @__PURE__ */ jsxs9("span", { children: [
1419
+ /* @__PURE__ */ jsxs9(KbdGroup, { children: [
1420
+ /* @__PURE__ */ jsx15(Kbd, { children: "\u2191" }),
1421
+ /* @__PURE__ */ jsx15(Kbd, { children: "\u2193" })
1422
+ ] }),
1423
+ "navigate"
1424
+ ] }),
1425
+ /* @__PURE__ */ jsxs9("span", { children: [
1426
+ /* @__PURE__ */ jsx15(Kbd, { children: "\u21B5" }),
1427
+ " open tool"
1428
+ ] })
1429
+ ] })
1430
+ ]
1431
+ }
1432
+ )
1433
+ ] }) });
1434
+ }
1435
+
1436
+ // src/react/select.tsx
1437
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
1438
+ import { Select } from "radix-ui";
1439
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1440
+ function StudioSelect({ label, title, className, children, ...props }) {
1441
+ return /* @__PURE__ */ jsxs10(Select.Root, { ...props, children: [
1442
+ /* @__PURE__ */ jsxs10(
1443
+ Select.Trigger,
1444
+ {
1445
+ "data-slot": "select-trigger",
1446
+ "aria-label": label,
1447
+ title,
1448
+ className: cn(
1449
+ "border-input text-foreground focus-visible:border-ring focus-visible:ring-ring/50 flex h-7 max-w-full shrink-0 items-center justify-between gap-2 rounded-md border bg-(--field) px-2 text-xs whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-(--field-hover) focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span:first-child]:truncate",
1450
+ className
1451
+ ),
1452
+ children: [
1453
+ /* @__PURE__ */ jsx16(Select.Value, {}),
1454
+ /* @__PURE__ */ jsx16(Select.Icon, { children: /* @__PURE__ */ jsx16(ChevronDown, { className: "shrink-0 text-muted-foreground", size: 14, strokeWidth: 1.5, "aria-hidden": true }) })
1455
+ ]
1456
+ }
1457
+ ),
1458
+ /* @__PURE__ */ jsx16(Select.Portal, { children: /* @__PURE__ */ jsxs10(
1459
+ Select.Content,
1460
+ {
1461
+ className: "relative z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) overflow-hidden rounded-md border border-border bg-popover font-(family-name:--studio-font) text-xs font-normal text-popover-foreground shadow-md",
1462
+ "data-slot": "select-content",
1463
+ position: "popper",
1464
+ align: "end",
1465
+ sideOffset: 6,
1466
+ children: [
1467
+ /* @__PURE__ */ jsx16(Select.ScrollUpButton, { className: "flex justify-center py-1", children: /* @__PURE__ */ jsx16(ChevronUp, { size: 14, "aria-hidden": true }) }),
1468
+ /* @__PURE__ */ jsx16(Select.Viewport, { className: "p-1", children }),
1469
+ /* @__PURE__ */ jsx16(Select.ScrollDownButton, { className: "flex justify-center py-1", children: /* @__PURE__ */ jsx16(ChevronDown, { size: 14, "aria-hidden": true }) })
1470
+ ]
1471
+ }
1472
+ ) })
1473
+ ] });
1474
+ }
1475
+ function StudioSelectItem({ className, children, ...props }) {
1476
+ return /* @__PURE__ */ jsxs10(
1477
+ Select.Item,
1478
+ {
1479
+ "data-slot": "select-item",
1480
+ className: cn(
1481
+ "relative flex min-h-7 cursor-default items-center rounded-sm py-1.5 pr-8 pl-2 outline-none select-none data-[highlighted]:bg-(--field-hover) data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
1482
+ className
1483
+ ),
1484
+ ...props,
1485
+ children: [
1486
+ /* @__PURE__ */ jsx16(Select.ItemText, { children }),
1487
+ /* @__PURE__ */ jsx16(Select.ItemIndicator, { className: "absolute right-2", children: /* @__PURE__ */ jsx16(Check, { size: 14, strokeWidth: 1.5, "aria-hidden": true }) })
1488
+ ]
1489
+ }
1490
+ );
1491
+ }
1492
+
1493
+ // src/react/ui/input.tsx
1494
+ import { forwardRef as forwardRef5 } from "react";
1495
+ import { jsx as jsx17 } from "react/jsx-runtime";
1496
+ var Input = forwardRef5(function Input2({ className, ...props }, ref) {
1497
+ return /* @__PURE__ */ jsx17(
1498
+ "input",
1499
+ {
1500
+ ref,
1501
+ "data-slot": "input",
1502
+ className: cn(
1503
+ "border-input text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 h-8 w-full min-w-0 rounded-md border bg-transparent py-1 ps-3 pe-3 text-sm transition-[color,box-shadow] outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
1504
+ className
1505
+ ),
1506
+ ...props
1507
+ }
1508
+ );
1509
+ });
1510
+ export {
1511
+ CanvasToolbar,
1512
+ PreviewCanvas,
1513
+ Studio,
1514
+ StudioCommandMenu,
1515
+ Input as StudioInput,
1516
+ Panel as StudioPanel,
1517
+ PanelContent as StudioPanelContent,
1518
+ PanelFooter as StudioPanelFooter,
1519
+ PanelHeader as StudioPanelHeader,
1520
+ StudioSelect,
1521
+ StudioSelectItem,
1522
+ StudioSurface,
1523
+ StudioToolbar,
1524
+ useStudio,
1525
+ useToolConfig
1526
+ };
1527
+ //# sourceMappingURL=react.js.map