@anvilkit/puck-studio 0.0.1 → 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.
@@ -0,0 +1,2057 @@
1
+ import { Search, Info, Plus, Bold, Italic, Link, ChevronRightIcon, Sparkles, ChevronDownIcon, CheckIcon, GripVertical, Copy, Trash2, SearchIcon, ChevronUpIcon } from 'lucide-react';
2
+ import { useStore } from 'zustand';
3
+ import * as React6 from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { ScrollArea as ScrollArea$1 } from '@base-ui/react/scroll-area';
6
+ import { clsx } from 'clsx';
7
+ import { twMerge } from 'tailwind-merge';
8
+ import { Input as Input$1 } from '@base-ui/react/input';
9
+ import { usePuck, useGetPuck, AutoField } from '@puckeditor/core';
10
+ import { Separator as Separator$1 } from '@base-ui/react/separator';
11
+ import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
12
+ import { mergeProps } from '@base-ui/react/merge-props';
13
+ import { useRender } from '@base-ui/react/use-render';
14
+ import { Button as Button$1 } from '@base-ui/react/button';
15
+ import { cva } from 'class-variance-authority';
16
+ import { Select as Select$1 } from '@base-ui/react/select';
17
+ import { useSensors, useSensor, PointerSensor, DndContext, closestCenter } from '@dnd-kit/core';
18
+ import { SortableContext, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
19
+ import { CSS } from '@dnd-kit/utilities';
20
+ import { Popover as Popover$1 } from '@base-ui/react/popover';
21
+ import { AnimatePresence, motion } from 'motion/react';
22
+ import { Toggle as Toggle$1 } from '@base-ui/react/toggle';
23
+ import { Command as Command$1 } from 'cmdk';
24
+
25
+ // src/core/overrides/layout/EditorDrawer.tsx
26
+ function getStrictContext(name) {
27
+ const Context = React6.createContext(void 0);
28
+ const Provider = ({
29
+ value,
30
+ children
31
+ }) => /* @__PURE__ */ jsx(Context.Provider, { value, children });
32
+ const useSafeContext = () => {
33
+ const ctx = React6.useContext(Context);
34
+ if (ctx === void 0) {
35
+ throw new Error(`useContext must be used within ${name ?? "a Provider"}`);
36
+ }
37
+ return ctx;
38
+ };
39
+ return [Provider, useSafeContext];
40
+ }
41
+
42
+ // src/store/ui-context.ts
43
+ var [EditorUiStoreProvider, useEditorUiStoreApi] = getStrictContext("EditorUiStoreProvider");
44
+
45
+ // src/store/i18n-context.ts
46
+ var [EditorI18nStoreProvider, useEditorI18nStoreApi] = getStrictContext("EditorI18nStoreProvider");
47
+
48
+ // src/store/hooks.ts
49
+ function useDrawerSearch() {
50
+ return useStore(useEditorUiStoreApi(), (s) => s.drawerSearch);
51
+ }
52
+ function useSetDrawerSearch() {
53
+ return useStore(useEditorUiStoreApi(), (s) => s.setDrawerSearch);
54
+ }
55
+ function useTheme() {
56
+ return useStore(useEditorUiStoreApi(), (s) => s.theme);
57
+ }
58
+ function useMsg(key) {
59
+ return useStore(useEditorI18nStoreApi(), (s) => s.messages[key] ?? key);
60
+ }
61
+ function cn(...inputs) {
62
+ return twMerge(clsx(inputs));
63
+ }
64
+ function ScrollArea({
65
+ className,
66
+ children,
67
+ ...props
68
+ }) {
69
+ return /* @__PURE__ */ jsxs(
70
+ ScrollArea$1.Root,
71
+ {
72
+ "data-slot": "scroll-area",
73
+ className: cn("relative", className),
74
+ ...props,
75
+ children: [
76
+ /* @__PURE__ */ jsx(
77
+ ScrollArea$1.Viewport,
78
+ {
79
+ "data-slot": "scroll-area-viewport",
80
+ className: "size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
81
+ children
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx(ScrollBar, {}),
85
+ /* @__PURE__ */ jsx(ScrollArea$1.Corner, {})
86
+ ]
87
+ }
88
+ );
89
+ }
90
+ function ScrollBar({
91
+ className,
92
+ orientation = "vertical",
93
+ ...props
94
+ }) {
95
+ return /* @__PURE__ */ jsx(
96
+ ScrollArea$1.Scrollbar,
97
+ {
98
+ "data-slot": "scroll-area-scrollbar",
99
+ "data-orientation": orientation,
100
+ orientation,
101
+ className: cn(
102
+ "flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
103
+ className
104
+ ),
105
+ ...props,
106
+ children: /* @__PURE__ */ jsx(
107
+ ScrollArea$1.Thumb,
108
+ {
109
+ "data-slot": "scroll-area-thumb",
110
+ className: "relative flex-1 rounded-full bg-border"
111
+ }
112
+ )
113
+ }
114
+ );
115
+ }
116
+ function Input({ className, type, ...props }) {
117
+ return /* @__PURE__ */ jsx(
118
+ Input$1,
119
+ {
120
+ type,
121
+ "data-slot": "input",
122
+ className: cn(
123
+ "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 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:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
124
+ className
125
+ ),
126
+ ...props
127
+ }
128
+ );
129
+ }
130
+ function EditorDrawer({
131
+ children
132
+ }) {
133
+ const search = useDrawerSearch();
134
+ const setSearch = useSetDrawerSearch();
135
+ const drawerTitle = useMsg("drawer.title");
136
+ const searchPlaceholder = useMsg("drawer.search.placeholder");
137
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
138
+ /* @__PURE__ */ jsx("div", { className: "px-3 pt-3 pb-1 text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: drawerTitle }),
139
+ /* @__PURE__ */ jsx("div", { className: "p-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
140
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
141
+ /* @__PURE__ */ jsx(
142
+ Input,
143
+ {
144
+ className: "pl-8",
145
+ placeholder: searchPlaceholder,
146
+ value: search,
147
+ onChange: (e) => setSearch(e.target.value)
148
+ }
149
+ )
150
+ ] }) }),
151
+ /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children })
152
+ ] });
153
+ }
154
+ function EditorComponents({
155
+ children
156
+ }) {
157
+ return /* @__PURE__ */ jsxs("div", { "data-puck-components-grid": true, className: "p-2", children: [
158
+ /* @__PURE__ */ jsx("style", { children: `
159
+ [data-puck-components-grid] [data-puck-drawer] {
160
+ display: grid;
161
+ grid-template-columns: repeat(3, minmax(0, 1fr));
162
+ gap: 6px;
163
+ }
164
+ /* flatten item wrapper and draggable wrapper */
165
+ [data-puck-components-grid] [data-puck-drawer] > div,
166
+ [data-puck-components-grid] [data-puck-drawer] > div > div {
167
+ display: contents;
168
+ }
169
+ /* hide the ghost/bg copy */
170
+ [data-puck-components-grid] [data-puck-drawer] > div > div > div:first-child {
171
+ display: none;
172
+ }
173
+ /* flatten the real draggable wrapper */
174
+ [data-puck-components-grid] [data-puck-drawer] > div > div > div:last-child {
175
+ display: contents;
176
+ }
177
+ ` }),
178
+ children
179
+ ] });
180
+ }
181
+ function getPlaceholderUrl(name) {
182
+ return `https://picsum.photos/seed/${encodeURIComponent(name)}/120/80`;
183
+ }
184
+ function DrawerItem({
185
+ children,
186
+ name
187
+ }) {
188
+ const { config } = usePuck();
189
+ const componentConfig = config.components?.[name];
190
+ const thumbnail = typeof componentConfig?.metadata?.thumbnail === "string" ? componentConfig.metadata.thumbnail : void 0;
191
+ const src = thumbnail ?? getPlaceholderUrl(name);
192
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col rounded-md border border-border bg-muted/40 cursor-grab select-none transition-colors hover:bg-muted active:cursor-grabbing overflow-hidden", children: [
193
+ /* @__PURE__ */ jsx("div", { className: "w-full h-16 bg-muted overflow-hidden", children: /* @__PURE__ */ jsx(
194
+ "img",
195
+ {
196
+ src,
197
+ alt: name,
198
+ className: "w-full h-full object-cover",
199
+ onError: (e) => {
200
+ e.currentTarget.style.display = "none";
201
+ }
202
+ }
203
+ ) }),
204
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs font-medium truncate", children: name ?? children })
205
+ ] });
206
+ }
207
+ function Separator({
208
+ className,
209
+ orientation = "horizontal",
210
+ ...props
211
+ }) {
212
+ return /* @__PURE__ */ jsx(
213
+ Separator$1,
214
+ {
215
+ "data-slot": "separator",
216
+ orientation,
217
+ className: cn(
218
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
219
+ className
220
+ ),
221
+ ...props
222
+ }
223
+ );
224
+ }
225
+ function EditorOutline({
226
+ children
227
+ }) {
228
+ const { selectedItem } = usePuck();
229
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
230
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2 border-b", children: /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide", children: "Outline" }) }),
231
+ selectedItem && /* @__PURE__ */ jsxs(Fragment, { children: [
232
+ /* @__PURE__ */ jsxs("div", { className: "px-3 py-1.5 text-xs text-muted-foreground truncate", children: [
233
+ "Selected:",
234
+ " ",
235
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: typeof selectedItem.type === "string" ? selectedItem.type : String(selectedItem.type) })
236
+ ] }),
237
+ /* @__PURE__ */ jsx(Separator, {})
238
+ ] }),
239
+ /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx("div", { className: "p-2 text-sm", children }) })
240
+ ] });
241
+ }
242
+ var CANVAS_CSS = `
243
+ *, *::before, *::after { box-sizing: border-box; }
244
+ :root {
245
+ --background: 0 0% 100%;
246
+ --foreground: 222.2 84% 4.9%;
247
+ --primary: 222.2 47.4% 11.2%;
248
+ --primary-foreground: 210 40% 98%;
249
+ --secondary: 210 40% 96.1%;
250
+ --muted: 210 40% 96.1%;
251
+ --muted-foreground: 215.4 16.3% 46.9%;
252
+ --border: 214.3 31.8% 91.4%;
253
+ --radius: 0.5rem;
254
+ }
255
+ .dark {
256
+ --background: 222.2 84% 4.9%;
257
+ --foreground: 210 40% 98%;
258
+ --primary: 210 40% 98%;
259
+ --primary-foreground: 222.2 47.4% 11.2%;
260
+ --secondary: 217.2 32.6% 17.5%;
261
+ --muted: 217.2 32.6% 17.5%;
262
+ --muted-foreground: 215 20.2% 65.1%;
263
+ --border: 217.2 32.6% 17.5%;
264
+ }
265
+ body { margin: 0; font-family: system-ui, sans-serif; }
266
+ `;
267
+ var CANVAS_STYLE_ID = "__anvilkit_styles__";
268
+ function resolveDocument(target) {
269
+ if (target) return target;
270
+ if (typeof document === "undefined") return void 0;
271
+ return document;
272
+ }
273
+ function useThemeSync({
274
+ document: targetDocument,
275
+ injectCanvasCss = false
276
+ } = {}) {
277
+ const theme = useTheme();
278
+ React6.useEffect(() => {
279
+ const resolvedDocument = resolveDocument(targetDocument);
280
+ if (!resolvedDocument || !injectCanvasCss) return;
281
+ const existing = resolvedDocument.getElementById(CANVAS_STYLE_ID);
282
+ if (existing) existing.remove();
283
+ const style = resolvedDocument.createElement("style");
284
+ style.id = CANVAS_STYLE_ID;
285
+ style.textContent = CANVAS_CSS;
286
+ resolvedDocument.head.appendChild(style);
287
+ }, [targetDocument, injectCanvasCss]);
288
+ React6.useEffect(() => {
289
+ const resolvedDocument = resolveDocument(targetDocument);
290
+ if (!resolvedDocument) return;
291
+ resolvedDocument.documentElement.classList.toggle("dark", theme === "dark");
292
+ }, [targetDocument, theme]);
293
+ }
294
+
295
+ // src/features/library-dnd/drop-contract.ts
296
+ var LIBRARY_DRAG_START = "anvilkit:librarydragstart";
297
+ var IMAGE_DROP = "anvilkit:imagedrop";
298
+ var TEXT_DROP = "anvilkit:textdrop";
299
+ function addLibraryDragEventListener(type, listener, target = window) {
300
+ const wrapped = (event) => {
301
+ listener(event);
302
+ };
303
+ target.addEventListener(type, wrapped);
304
+ return () => {
305
+ target.removeEventListener(type, wrapped);
306
+ };
307
+ }
308
+
309
+ // src/features/library-dnd/replace-props.ts
310
+ function isImageUrl(val) {
311
+ return /\.(jpg|jpeg|png|gif|webp|svg|avif)(\?.*)?$/i.test(val) || val.includes("picsum.photos") || val.includes("unsplash.com") || val.includes("images.") || val.startsWith("data:image/");
312
+ }
313
+ function replaceImageInProps(props, newSrc) {
314
+ const result = {};
315
+ for (const key of Object.keys(props)) {
316
+ const val = props[key];
317
+ if (typeof val === "string" && isImageUrl(val)) {
318
+ result[key] = newSrc;
319
+ } else if (Array.isArray(val)) {
320
+ result[key] = val.map(
321
+ (item) => item && typeof item === "object" ? replaceImageInProps(item, newSrc) : item
322
+ );
323
+ } else if (val && typeof val === "object") {
324
+ result[key] = replaceImageInProps(val, newSrc);
325
+ } else {
326
+ result[key] = val;
327
+ }
328
+ }
329
+ return result;
330
+ }
331
+ function replaceTextInProps(props, newText, targetText) {
332
+ for (const key of Object.keys(props)) {
333
+ if (key === "id") continue;
334
+ const val = props[key];
335
+ if (typeof val === "string" && val === targetText && !isImageUrl(val)) {
336
+ return { result: { ...props, [key]: newText }, replaced: true };
337
+ }
338
+ }
339
+ for (const key of Object.keys(props)) {
340
+ if (key === "id") continue;
341
+ const val = props[key];
342
+ if (typeof val === "string" && !isImageUrl(val) && val.length > 0) {
343
+ return { result: { ...props, [key]: newText }, replaced: true };
344
+ }
345
+ }
346
+ return { result: props, replaced: false };
347
+ }
348
+
349
+ // src/core/overrides/canvas/drop-targets.ts
350
+ var TEXT_TAGS = /* @__PURE__ */ new Set([
351
+ "P",
352
+ "H1",
353
+ "H2",
354
+ "H3",
355
+ "H4",
356
+ "H5",
357
+ "H6",
358
+ "SPAN",
359
+ "A",
360
+ "LI",
361
+ "BUTTON",
362
+ "LABEL"
363
+ ]);
364
+ function getIframeCoords(iframeEl, clientX, clientY) {
365
+ const rect = iframeEl.getBoundingClientRect();
366
+ const x = clientX - rect.left;
367
+ const y = clientY - rect.top;
368
+ if (x < 0 || y < 0 || x > rect.width || y > rect.height) {
369
+ return null;
370
+ }
371
+ return { x, y };
372
+ }
373
+ function getComponentElAt(iframeDoc, iframeEl, clientX, clientY) {
374
+ const coords = getIframeCoords(iframeEl, clientX, clientY);
375
+ if (!coords) return null;
376
+ const element = iframeDoc.elementFromPoint(coords.x, coords.y);
377
+ if (!element) return null;
378
+ return element.closest("[data-puck-component]");
379
+ }
380
+ function getImageElInComponent(compEl, iframeEl, clientX, clientY) {
381
+ const images = Array.from(compEl.querySelectorAll("img"));
382
+ if (!images.length) return null;
383
+ if (images.length === 1) return images[0];
384
+ const rect = iframeEl.getBoundingClientRect();
385
+ const x = clientX - rect.left;
386
+ const y = clientY - rect.top;
387
+ let closest = null;
388
+ let minDistance = Infinity;
389
+ for (const image of images) {
390
+ const imageRect = image.getBoundingClientRect();
391
+ const centerX = imageRect.left + imageRect.width / 2;
392
+ const centerY = imageRect.top + imageRect.height / 2;
393
+ const distance = Math.hypot(centerX - x, centerY - y);
394
+ if (distance < minDistance) {
395
+ minDistance = distance;
396
+ closest = image;
397
+ }
398
+ }
399
+ return closest;
400
+ }
401
+ function getTextElInComponent(iframeDoc, iframeEl, compEl, clientX, clientY) {
402
+ const coords = getIframeCoords(iframeEl, clientX, clientY);
403
+ if (!coords) return null;
404
+ const element = iframeDoc.elementFromPoint(coords.x, coords.y);
405
+ if (element && compEl.contains(element)) {
406
+ let current = element;
407
+ while (current && current !== iframeDoc.body) {
408
+ if (TEXT_TAGS.has(current.tagName) && current.textContent?.trim()) {
409
+ return current;
410
+ }
411
+ current = current.parentElement;
412
+ }
413
+ }
414
+ for (const tag of TEXT_TAGS) {
415
+ const candidate = compEl.querySelector(tag.toLowerCase());
416
+ if (candidate?.textContent?.trim()) {
417
+ return candidate;
418
+ }
419
+ }
420
+ return null;
421
+ }
422
+
423
+ // src/core/overrides/canvas/highlight.ts
424
+ function createElementHighlighter() {
425
+ let highlightedEl = null;
426
+ function set(el, color) {
427
+ if (highlightedEl && highlightedEl !== el) {
428
+ highlightedEl.style.outline = "";
429
+ highlightedEl.style.outlineOffset = "";
430
+ }
431
+ if (el) {
432
+ el.style.outline = `2px solid ${color}`;
433
+ el.style.outlineOffset = "2px";
434
+ }
435
+ highlightedEl = el;
436
+ }
437
+ function clear() {
438
+ set(null, "");
439
+ }
440
+ return { set, clear };
441
+ }
442
+
443
+ // src/core/overrides/canvas/useLibraryDropBridge.ts
444
+ function useLibraryDropBridge(iframeDoc) {
445
+ const getPuck = useGetPuck();
446
+ React6.useEffect(() => {
447
+ if (!iframeDoc) return;
448
+ const iframeEl = iframeDoc.defaultView?.frameElement;
449
+ if (!iframeEl) return;
450
+ const iframeDocument = iframeDoc;
451
+ const frameElement = iframeEl;
452
+ let activeLibrary = null;
453
+ const highlighter = createElementHighlighter();
454
+ function dispatchReplace(componentId, updatedProps) {
455
+ const { dispatch, getItemById, getSelectorForId } = getPuck();
456
+ const item = getItemById(componentId);
457
+ const selector = getSelectorForId(componentId);
458
+ if (!item || !selector) return false;
459
+ dispatch({
460
+ type: "replace",
461
+ destinationIndex: selector.index,
462
+ destinationZone: selector.zone,
463
+ data: {
464
+ ...item,
465
+ props: { ...item.props, ...updatedProps }
466
+ }
467
+ });
468
+ return true;
469
+ }
470
+ function onLibraryDragStart(type) {
471
+ activeLibrary = type;
472
+ }
473
+ function onPointerMove(e) {
474
+ if (!activeLibrary) return;
475
+ const compEl = getComponentElAt(iframeDocument, frameElement, e.clientX, e.clientY);
476
+ if (!compEl) {
477
+ highlighter.clear();
478
+ return;
479
+ }
480
+ if (activeLibrary === "image") {
481
+ highlighter.set(
482
+ getImageElInComponent(compEl, frameElement, e.clientX, e.clientY),
483
+ "#6366f1"
484
+ );
485
+ } else {
486
+ highlighter.set(
487
+ getTextElInComponent(iframeDocument, frameElement, compEl, e.clientX, e.clientY),
488
+ "#f59e0b"
489
+ );
490
+ }
491
+ }
492
+ function onPointerUp() {
493
+ activeLibrary = null;
494
+ highlighter.clear();
495
+ }
496
+ function onImageDrop(src, clientX, clientY) {
497
+ highlighter.clear();
498
+ activeLibrary = null;
499
+ if (!src) return;
500
+ const compEl = getComponentElAt(iframeDocument, frameElement, clientX, clientY);
501
+ if (!compEl) return;
502
+ const componentId = compEl.dataset.puckComponent;
503
+ if (!componentId) return;
504
+ const item = getPuck().getItemById(componentId);
505
+ if (!item) return;
506
+ const updatedProps = replaceImageInProps(
507
+ item.props,
508
+ src
509
+ );
510
+ dispatchReplace(componentId, updatedProps);
511
+ }
512
+ function onTextDrop(text, clientX, clientY) {
513
+ highlighter.clear();
514
+ activeLibrary = null;
515
+ if (!text) return;
516
+ const compEl = getComponentElAt(iframeDocument, frameElement, clientX, clientY);
517
+ if (!compEl) return;
518
+ const componentId = compEl.dataset.puckComponent;
519
+ if (!componentId) return;
520
+ const textEl = getTextElInComponent(
521
+ iframeDocument,
522
+ frameElement,
523
+ compEl,
524
+ clientX,
525
+ clientY
526
+ );
527
+ const targetText = textEl?.textContent?.trim() ?? "";
528
+ const item = getPuck().getItemById(componentId);
529
+ if (!item) return;
530
+ const { result: updatedProps, replaced } = replaceTextInProps(
531
+ item.props,
532
+ text,
533
+ targetText
534
+ );
535
+ if (replaced) dispatchReplace(componentId, updatedProps);
536
+ }
537
+ const removeLibraryDragStart = addLibraryDragEventListener(
538
+ LIBRARY_DRAG_START,
539
+ (event) => {
540
+ onLibraryDragStart(event.detail.type);
541
+ }
542
+ );
543
+ window.addEventListener("pointermove", onPointerMove);
544
+ window.addEventListener("pointerup", onPointerUp);
545
+ const removeImageDrop = addLibraryDragEventListener(IMAGE_DROP, (event) => {
546
+ const { src, clientX, clientY } = event.detail;
547
+ onImageDrop(src, clientX, clientY);
548
+ });
549
+ const removeTextDrop = addLibraryDragEventListener(TEXT_DROP, (event) => {
550
+ const { text, clientX, clientY } = event.detail;
551
+ onTextDrop(text, clientX, clientY);
552
+ });
553
+ return () => {
554
+ removeLibraryDragStart();
555
+ window.removeEventListener("pointermove", onPointerMove);
556
+ window.removeEventListener("pointerup", onPointerUp);
557
+ removeImageDrop();
558
+ removeTextDrop();
559
+ highlighter.clear();
560
+ };
561
+ }, [iframeDoc, getPuck]);
562
+ }
563
+ function CanvasIframe({
564
+ children,
565
+ document: iframeDoc
566
+ }) {
567
+ useThemeSync({ document: iframeDoc, injectCanvasCss: true });
568
+ useLibraryDropBridge(iframeDoc);
569
+ return /* @__PURE__ */ jsx(Fragment, { children });
570
+ }
571
+ function CanvasPreview({
572
+ children
573
+ }) {
574
+ return /* @__PURE__ */ jsx("div", { className: "w-full h-full px-3 py-2 text-sm font-medium text-foreground", children });
575
+ }
576
+ function ComponentOverlay({
577
+ children,
578
+ hover,
579
+ isSelected
580
+ }) {
581
+ return /* @__PURE__ */ jsx(
582
+ "div",
583
+ {
584
+ className: [
585
+ "absolute inset-0 rounded-sm pointer-events-none z-10 transition-colors",
586
+ isSelected ? "border-2 border-primary/80" : hover ? "border-2 border-primary/40" : ""
587
+ ].filter(Boolean).join(" "),
588
+ children
589
+ }
590
+ );
591
+ }
592
+ function TooltipProvider({
593
+ delay = 0,
594
+ ...props
595
+ }) {
596
+ return /* @__PURE__ */ jsx(
597
+ Tooltip$1.Provider,
598
+ {
599
+ "data-slot": "tooltip-provider",
600
+ delay,
601
+ ...props
602
+ }
603
+ );
604
+ }
605
+ function Tooltip({ ...props }) {
606
+ return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
607
+ }
608
+ function TooltipTrigger({ ...props }) {
609
+ return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
610
+ }
611
+ function TooltipContent({
612
+ className,
613
+ side = "top",
614
+ sideOffset = 4,
615
+ align = "center",
616
+ alignOffset = 0,
617
+ children,
618
+ ...props
619
+ }) {
620
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(
621
+ Tooltip$1.Positioner,
622
+ {
623
+ align,
624
+ alignOffset,
625
+ side,
626
+ sideOffset,
627
+ className: "isolate z-50",
628
+ children: /* @__PURE__ */ jsxs(
629
+ Tooltip$1.Popup,
630
+ {
631
+ "data-slot": "tooltip-content",
632
+ className: cn(
633
+ "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",
634
+ className
635
+ ),
636
+ ...props,
637
+ children: [
638
+ children,
639
+ /* @__PURE__ */ jsx(Tooltip$1.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" })
640
+ ]
641
+ }
642
+ )
643
+ }
644
+ ) });
645
+ }
646
+ function ActionBar({
647
+ children,
648
+ label,
649
+ parentAction
650
+ }) {
651
+ return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 rounded-md border bg-background shadow-md px-1 py-0.5", children: [
652
+ label && /* @__PURE__ */ jsxs(Fragment, { children: [
653
+ /* @__PURE__ */ jsx("span", { className: "px-2 text-xs font-medium text-muted-foreground truncate max-w-[120px]", children: label }),
654
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-5" })
655
+ ] }),
656
+ parentAction && /* @__PURE__ */ jsxs(Fragment, { children: [
657
+ parentAction,
658
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-5" })
659
+ ] }),
660
+ children
661
+ ] }) });
662
+ }
663
+ function Label({ className, ...props }) {
664
+ return /* @__PURE__ */ jsx(
665
+ "label",
666
+ {
667
+ "data-slot": "label",
668
+ className: cn(
669
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
670
+ className
671
+ ),
672
+ ...props
673
+ }
674
+ );
675
+ }
676
+ function Breadcrumb({ className, ...props }) {
677
+ return /* @__PURE__ */ jsx(
678
+ "nav",
679
+ {
680
+ "aria-label": "breadcrumb",
681
+ "data-slot": "breadcrumb",
682
+ className: cn(className),
683
+ ...props
684
+ }
685
+ );
686
+ }
687
+ function BreadcrumbList({ className, ...props }) {
688
+ return /* @__PURE__ */ jsx(
689
+ "ol",
690
+ {
691
+ "data-slot": "breadcrumb-list",
692
+ className: cn(
693
+ "flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground sm:gap-2.5",
694
+ className
695
+ ),
696
+ ...props
697
+ }
698
+ );
699
+ }
700
+ function BreadcrumbItem({ className, ...props }) {
701
+ return /* @__PURE__ */ jsx(
702
+ "li",
703
+ {
704
+ "data-slot": "breadcrumb-item",
705
+ className: cn("inline-flex items-center gap-1.5", className),
706
+ ...props
707
+ }
708
+ );
709
+ }
710
+ function BreadcrumbLink({
711
+ className,
712
+ render,
713
+ ...props
714
+ }) {
715
+ return useRender({
716
+ defaultTagName: "a",
717
+ props: mergeProps(
718
+ {
719
+ className: cn("transition-colors hover:text-foreground", className)
720
+ },
721
+ props
722
+ ),
723
+ render,
724
+ state: {
725
+ slot: "breadcrumb-link"
726
+ }
727
+ });
728
+ }
729
+ function BreadcrumbPage({ className, ...props }) {
730
+ return /* @__PURE__ */ jsx(
731
+ "span",
732
+ {
733
+ "data-slot": "breadcrumb-page",
734
+ role: "link",
735
+ "aria-disabled": "true",
736
+ "aria-current": "page",
737
+ className: cn("font-normal text-foreground", className),
738
+ ...props
739
+ }
740
+ );
741
+ }
742
+ function BreadcrumbSeparator({
743
+ children,
744
+ className,
745
+ ...props
746
+ }) {
747
+ return /* @__PURE__ */ jsx(
748
+ "li",
749
+ {
750
+ "data-slot": "breadcrumb-separator",
751
+ role: "presentation",
752
+ "aria-hidden": "true",
753
+ className: cn("[&>svg]:size-3.5", className),
754
+ ...props,
755
+ children: children ?? /* @__PURE__ */ jsx(ChevronRightIcon, {})
756
+ }
757
+ );
758
+ }
759
+ function getComponentTypeLabel(item) {
760
+ if (!item) return "Component";
761
+ return typeof item.type === "string" ? item.type : String(item.type);
762
+ }
763
+ function getComponentId(item) {
764
+ const id = item?.props?.id;
765
+ return typeof id === "string" ? id : null;
766
+ }
767
+ function useBreadcrumbs() {
768
+ const { appState, dispatch, selectedItem, getParentById } = usePuck();
769
+ const { itemSelector } = appState.ui;
770
+ const selectRoot = () => dispatch({ type: "setUi", ui: { itemSelector: null } });
771
+ if (!itemSelector || !selectedItem) {
772
+ return [{ label: "Page" }];
773
+ }
774
+ const selectedType = getComponentTypeLabel(selectedItem);
775
+ const parentId = getComponentId(selectedItem);
776
+ const parent = parentId ? getParentById(parentId) : void 0;
777
+ if (!parent) {
778
+ return [{ label: "Page", onSelect: selectRoot }, { label: selectedType }];
779
+ }
780
+ const parentType = getComponentTypeLabel(parent);
781
+ return [
782
+ { label: "Page", onSelect: selectRoot },
783
+ { label: parentType },
784
+ { label: selectedType }
785
+ ];
786
+ }
787
+ function FieldWrapper({
788
+ children
789
+ }) {
790
+ const crumbs = useBreadcrumbs();
791
+ return /* @__PURE__ */ jsx(ScrollArea, { className: "h-full", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0", children: [
792
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2 border-b", children: /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsx(BreadcrumbList, { children: crumbs.map((crumb, i) => {
793
+ const isLast = i === crumbs.length - 1;
794
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
795
+ i > 0 && /* @__PURE__ */ jsx(BreadcrumbSeparator, {}),
796
+ /* @__PURE__ */ jsx(BreadcrumbItem, { children: isLast ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: crumb.label }) : /* @__PURE__ */ jsx(
797
+ BreadcrumbLink,
798
+ {
799
+ className: "cursor-pointer",
800
+ onClick: crumb.onSelect,
801
+ children: crumb.label
802
+ }
803
+ ) })
804
+ ] }, i);
805
+ }) }) }) }),
806
+ children
807
+ ] }) });
808
+ }
809
+ function FieldLabel({
810
+ children,
811
+ label,
812
+ icon,
813
+ labelIcon,
814
+ el,
815
+ readOnly,
816
+ className
817
+ }) {
818
+ const El = el ?? "div";
819
+ const labelAdornment = icon ?? labelIcon;
820
+ return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(El, { className: `flex flex-col gap-1.5 ${className ?? ""}`, children: [
821
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
822
+ labelAdornment && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: labelAdornment }),
823
+ /* @__PURE__ */ jsx(Label, { className: "text-xs font-medium text-muted-foreground", children: label }),
824
+ label && /* @__PURE__ */ jsxs(Tooltip, { children: [
825
+ /* @__PURE__ */ jsx(
826
+ TooltipTrigger,
827
+ {
828
+ "aria-label": label,
829
+ render: /* @__PURE__ */ jsx(
830
+ "button",
831
+ {
832
+ type: "button",
833
+ className: "inline-flex cursor-help items-center justify-center text-muted-foreground/60 transition-colors hover:text-muted-foreground"
834
+ }
835
+ ),
836
+ children: /* @__PURE__ */ jsx(Info, { className: "h-3 w-3 text-muted-foreground/60 cursor-help" })
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: label })
840
+ ] }),
841
+ readOnly && /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs text-muted-foreground/50", children: "Read only" })
842
+ ] }),
843
+ children
844
+ ] }) });
845
+ }
846
+ var buttonVariants = cva(
847
+ "group/button inline-flex shrink-0 items-center justify-center rounded-md 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: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",
848
+ {
849
+ variants: {
850
+ variant: {
851
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
852
+ outline: "border-border bg-background shadow-xs 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",
853
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
854
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
855
+ 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",
856
+ link: "text-primary underline-offset-4 hover:underline"
857
+ },
858
+ size: {
859
+ default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
860
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
861
+ sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
862
+ lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
863
+ icon: "size-9",
864
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
865
+ "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
866
+ "icon-lg": "size-10"
867
+ }
868
+ },
869
+ defaultVariants: {
870
+ variant: "default",
871
+ size: "default"
872
+ }
873
+ }
874
+ );
875
+ function Button({
876
+ className,
877
+ variant = "default",
878
+ size = "default",
879
+ ...props
880
+ }) {
881
+ return /* @__PURE__ */ jsx(
882
+ Button$1,
883
+ {
884
+ "data-slot": "button",
885
+ className: cn(buttonVariants({ variant, size, className })),
886
+ ...props
887
+ }
888
+ );
889
+ }
890
+ var MOCK_VALUES = {
891
+ default: ["AI-generated content", "Smart suggestion", "Generated text"]
892
+ };
893
+ function getMock(instructions) {
894
+ if (instructions?.toLowerCase().includes("caps")) {
895
+ return "AI GENERATED TITLE";
896
+ }
897
+ const pool = MOCK_VALUES.default;
898
+ return pool[Math.floor(Math.random() * pool.length)];
899
+ }
900
+ function AiButton({ ai, onGenerate }) {
901
+ const [loading, setLoading] = React6.useState(false);
902
+ const handleClick = () => {
903
+ setLoading(true);
904
+ setTimeout(() => {
905
+ onGenerate(getMock(ai.instructions));
906
+ setLoading(false);
907
+ }, 600);
908
+ };
909
+ return /* @__PURE__ */ jsx(
910
+ Button,
911
+ {
912
+ type: "button",
913
+ variant: "outline",
914
+ size: "icon",
915
+ className: "h-8 w-8 shrink-0 text-muted-foreground hover:text-primary",
916
+ onClick: handleClick,
917
+ disabled: loading,
918
+ "aria-label": "Generate with AI",
919
+ children: /* @__PURE__ */ jsx(Sparkles, { className: `h-3.5 w-3.5 ${loading ? "animate-pulse" : ""}` })
920
+ }
921
+ );
922
+ }
923
+ function TextField({
924
+ field,
925
+ value,
926
+ onChange,
927
+ readOnly,
928
+ placeholder,
929
+ label,
930
+ labelIcon
931
+ }) {
932
+ const [local, setLocal] = React6.useState(value ?? "");
933
+ const onChangeRef = React6.useRef(onChange);
934
+ const lastCommittedValueRef = React6.useRef(value ?? "");
935
+ React6.useEffect(() => {
936
+ onChangeRef.current = onChange;
937
+ }, [onChange]);
938
+ React6.useEffect(() => {
939
+ lastCommittedValueRef.current = value ?? "";
940
+ setLocal(value ?? "");
941
+ }, [value]);
942
+ React6.useEffect(() => {
943
+ const timer = setTimeout(() => {
944
+ if (local !== lastCommittedValueRef.current) {
945
+ onChangeRef.current(local);
946
+ lastCommittedValueRef.current = local;
947
+ }
948
+ }, 200);
949
+ return () => clearTimeout(timer);
950
+ }, [local]);
951
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", labelIcon, readOnly, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
952
+ /* @__PURE__ */ jsx(
953
+ Input,
954
+ {
955
+ value: local,
956
+ onChange: (e) => setLocal(e.target.value),
957
+ readOnly,
958
+ placeholder,
959
+ className: "h-8 text-sm flex-1"
960
+ }
961
+ ),
962
+ field?.ai && /* @__PURE__ */ jsx(
963
+ AiButton,
964
+ {
965
+ ai: field.ai,
966
+ onGenerate: (nextValue) => {
967
+ lastCommittedValueRef.current = nextValue;
968
+ setLocal(nextValue);
969
+ onChangeRef.current(nextValue);
970
+ }
971
+ }
972
+ )
973
+ ] }) });
974
+ }
975
+ function Textarea({ className, ...props }) {
976
+ return /* @__PURE__ */ jsx(
977
+ "textarea",
978
+ {
979
+ "data-slot": "textarea",
980
+ className: cn(
981
+ "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
982
+ className
983
+ ),
984
+ ...props
985
+ }
986
+ );
987
+ }
988
+ function TextareaField({ value, onChange, readOnly, placeholder, label }) {
989
+ const [local, setLocal] = React6.useState(value ?? "");
990
+ React6.useEffect(() => {
991
+ setLocal(value ?? "");
992
+ }, [value]);
993
+ React6.useEffect(() => {
994
+ const timer = setTimeout(() => {
995
+ if (local !== value) onChange(local);
996
+ }, 200);
997
+ return () => clearTimeout(timer);
998
+ }, [local]);
999
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", readOnly, children: /* @__PURE__ */ jsx(
1000
+ Textarea,
1001
+ {
1002
+ value: local,
1003
+ onChange: (e) => setLocal(e.target.value),
1004
+ readOnly,
1005
+ placeholder,
1006
+ className: "min-h-[80px] text-sm resize-y"
1007
+ }
1008
+ ) });
1009
+ }
1010
+ function NumberField({ value, onChange, readOnly, min, max, step, label }) {
1011
+ const [local, setLocal] = React6.useState(String(value ?? ""));
1012
+ React6.useEffect(() => {
1013
+ setLocal(String(value ?? ""));
1014
+ }, [value]);
1015
+ React6.useEffect(() => {
1016
+ const parsed = parseFloat(local);
1017
+ if (!isNaN(parsed) && parsed !== value) {
1018
+ const timer = setTimeout(() => onChange(parsed), 200);
1019
+ return () => clearTimeout(timer);
1020
+ }
1021
+ }, [local]);
1022
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", readOnly, children: /* @__PURE__ */ jsx(
1023
+ Input,
1024
+ {
1025
+ type: "number",
1026
+ value: local,
1027
+ onChange: (e) => setLocal(e.target.value),
1028
+ readOnly,
1029
+ min,
1030
+ max,
1031
+ step,
1032
+ className: "h-8 text-sm"
1033
+ }
1034
+ ) });
1035
+ }
1036
+ var Select = Select$1.Root;
1037
+ function SelectGroup({ className, ...props }) {
1038
+ return /* @__PURE__ */ jsx(
1039
+ Select$1.Group,
1040
+ {
1041
+ "data-slot": "select-group",
1042
+ className: cn("scroll-my-1 p-1", className),
1043
+ ...props
1044
+ }
1045
+ );
1046
+ }
1047
+ function SelectValue({ className, ...props }) {
1048
+ return /* @__PURE__ */ jsx(
1049
+ Select$1.Value,
1050
+ {
1051
+ "data-slot": "select-value",
1052
+ className: cn("flex flex-1 text-left", className),
1053
+ ...props
1054
+ }
1055
+ );
1056
+ }
1057
+ function SelectTrigger({
1058
+ className,
1059
+ size = "default",
1060
+ children,
1061
+ ...props
1062
+ }) {
1063
+ return /* @__PURE__ */ jsxs(
1064
+ Select$1.Trigger,
1065
+ {
1066
+ "data-slot": "select-trigger",
1067
+ "data-size": size,
1068
+ className: cn(
1069
+ "flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 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",
1070
+ className
1071
+ ),
1072
+ ...props,
1073
+ children: [
1074
+ children,
1075
+ /* @__PURE__ */ jsx(
1076
+ Select$1.Icon,
1077
+ {
1078
+ render: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "pointer-events-none size-4 text-muted-foreground" })
1079
+ }
1080
+ )
1081
+ ]
1082
+ }
1083
+ );
1084
+ }
1085
+ function SelectContent({
1086
+ className,
1087
+ children,
1088
+ side = "bottom",
1089
+ sideOffset = 4,
1090
+ align = "center",
1091
+ alignOffset = 0,
1092
+ alignItemWithTrigger = true,
1093
+ ...props
1094
+ }) {
1095
+ return /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsx(
1096
+ Select$1.Positioner,
1097
+ {
1098
+ side,
1099
+ sideOffset,
1100
+ align,
1101
+ alignOffset,
1102
+ alignItemWithTrigger,
1103
+ className: "isolate z-50",
1104
+ children: /* @__PURE__ */ jsxs(
1105
+ Select$1.Popup,
1106
+ {
1107
+ "data-slot": "select-content",
1108
+ "data-align-trigger": alignItemWithTrigger,
1109
+ className: cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
1110
+ ...props,
1111
+ children: [
1112
+ /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
1113
+ /* @__PURE__ */ jsx(Select$1.List, { children }),
1114
+ /* @__PURE__ */ jsx(SelectScrollDownButton, {})
1115
+ ]
1116
+ }
1117
+ )
1118
+ }
1119
+ ) });
1120
+ }
1121
+ function SelectItem({
1122
+ className,
1123
+ children,
1124
+ ...props
1125
+ }) {
1126
+ return /* @__PURE__ */ jsxs(
1127
+ Select$1.Item,
1128
+ {
1129
+ "data-slot": "select-item",
1130
+ className: cn(
1131
+ "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
1132
+ className
1133
+ ),
1134
+ ...props,
1135
+ children: [
1136
+ /* @__PURE__ */ jsx(Select$1.ItemText, { className: "flex flex-1 shrink-0 gap-2 whitespace-nowrap", children }),
1137
+ /* @__PURE__ */ jsx(
1138
+ Select$1.ItemIndicator,
1139
+ {
1140
+ render: /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center" }),
1141
+ children: /* @__PURE__ */ jsx(CheckIcon, { className: "pointer-events-none" })
1142
+ }
1143
+ )
1144
+ ]
1145
+ }
1146
+ );
1147
+ }
1148
+ function SelectScrollUpButton({
1149
+ className,
1150
+ ...props
1151
+ }) {
1152
+ return /* @__PURE__ */ jsx(
1153
+ Select$1.ScrollUpArrow,
1154
+ {
1155
+ "data-slot": "select-scroll-up-button",
1156
+ className: cn(
1157
+ "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
1158
+ className
1159
+ ),
1160
+ ...props,
1161
+ children: /* @__PURE__ */ jsx(
1162
+ ChevronUpIcon,
1163
+ {}
1164
+ )
1165
+ }
1166
+ );
1167
+ }
1168
+ function SelectScrollDownButton({
1169
+ className,
1170
+ ...props
1171
+ }) {
1172
+ return /* @__PURE__ */ jsx(
1173
+ Select$1.ScrollDownArrow,
1174
+ {
1175
+ "data-slot": "select-scroll-down-button",
1176
+ className: cn(
1177
+ "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
1178
+ className
1179
+ ),
1180
+ ...props,
1181
+ children: /* @__PURE__ */ jsx(
1182
+ ChevronDownIcon,
1183
+ {}
1184
+ )
1185
+ }
1186
+ );
1187
+ }
1188
+ function SelectField({ field, value, onChange, id, readOnly, label }) {
1189
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", readOnly, children: /* @__PURE__ */ jsxs(
1190
+ Select,
1191
+ {
1192
+ value: String(value ?? ""),
1193
+ onValueChange: (v) => {
1194
+ if (v === null) return;
1195
+ const match = field.options.find((o) => String(o.value) === v);
1196
+ onChange(match ? match.value : v);
1197
+ },
1198
+ children: [
1199
+ /* @__PURE__ */ jsx(SelectTrigger, { id, className: "h-8 text-sm w-full", disabled: readOnly, children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select..." }) }),
1200
+ /* @__PURE__ */ jsx(SelectContent, { children: /* @__PURE__ */ jsx(SelectGroup, { children: field.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: String(opt.value), children: opt.label }, String(opt.value))) }) })
1201
+ ]
1202
+ }
1203
+ ) });
1204
+ }
1205
+ var buttonGroupVariants = cva(
1206
+ "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
1207
+ {
1208
+ variants: {
1209
+ orientation: {
1210
+ horizontal: "*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
1211
+ vertical: "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0"
1212
+ }
1213
+ },
1214
+ defaultVariants: {
1215
+ orientation: "horizontal"
1216
+ }
1217
+ }
1218
+ );
1219
+ function ButtonGroup({
1220
+ className,
1221
+ orientation,
1222
+ ...props
1223
+ }) {
1224
+ return /* @__PURE__ */ jsx(
1225
+ "div",
1226
+ {
1227
+ role: "group",
1228
+ "data-slot": "button-group",
1229
+ "data-orientation": orientation,
1230
+ className: cn(buttonGroupVariants({ orientation }), className),
1231
+ ...props
1232
+ }
1233
+ );
1234
+ }
1235
+ function RadioField({ field, value, onChange, readOnly, label }) {
1236
+ const options = field.options ?? [];
1237
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", readOnly, el: "div", children: /* @__PURE__ */ jsx(ButtonGroup, { className: "w-full", children: options.map((opt) => {
1238
+ const selected = String(opt.value) === String(value ?? "");
1239
+ return /* @__PURE__ */ jsx(
1240
+ Button,
1241
+ {
1242
+ type: "button",
1243
+ variant: selected ? "default" : "outline",
1244
+ size: "sm",
1245
+ className: "flex-1 text-xs",
1246
+ disabled: readOnly,
1247
+ onClick: () => onChange(opt.value),
1248
+ children: opt.label
1249
+ },
1250
+ String(opt.value)
1251
+ );
1252
+ }) }) });
1253
+ }
1254
+ function ItemGroup({ className, ...props }) {
1255
+ return /* @__PURE__ */ jsx(
1256
+ "div",
1257
+ {
1258
+ role: "list",
1259
+ "data-slot": "item-group",
1260
+ className: cn(
1261
+ "group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
1262
+ className
1263
+ ),
1264
+ ...props
1265
+ }
1266
+ );
1267
+ }
1268
+ var itemVariants = cva(
1269
+ "group/item flex w-full flex-wrap items-center rounded-md border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted",
1270
+ {
1271
+ variants: {
1272
+ variant: {
1273
+ default: "border-transparent",
1274
+ outline: "border-border",
1275
+ muted: "border-transparent bg-muted/50"
1276
+ },
1277
+ size: {
1278
+ default: "gap-3.5 px-4 py-3.5",
1279
+ sm: "gap-2.5 px-3 py-2.5",
1280
+ xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0"
1281
+ }
1282
+ },
1283
+ defaultVariants: {
1284
+ variant: "default",
1285
+ size: "default"
1286
+ }
1287
+ }
1288
+ );
1289
+ function Item({
1290
+ className,
1291
+ variant = "default",
1292
+ size = "default",
1293
+ render,
1294
+ ...props
1295
+ }) {
1296
+ return useRender({
1297
+ defaultTagName: "div",
1298
+ props: mergeProps(
1299
+ {
1300
+ className: cn(itemVariants({ variant, size, className }))
1301
+ },
1302
+ props
1303
+ ),
1304
+ render,
1305
+ state: {
1306
+ slot: "item",
1307
+ variant,
1308
+ size
1309
+ }
1310
+ });
1311
+ }
1312
+ cva(
1313
+ "flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
1314
+ {
1315
+ variants: {
1316
+ variant: {
1317
+ default: "bg-transparent",
1318
+ icon: "[&_svg:not([class*='size-'])]:size-4",
1319
+ image: "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover"
1320
+ }
1321
+ },
1322
+ defaultVariants: {
1323
+ variant: "default"
1324
+ }
1325
+ }
1326
+ );
1327
+ function ItemContent({ className, ...props }) {
1328
+ return /* @__PURE__ */ jsx(
1329
+ "div",
1330
+ {
1331
+ "data-slot": "item-content",
1332
+ className: cn(
1333
+ "flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
1334
+ className
1335
+ ),
1336
+ ...props
1337
+ }
1338
+ );
1339
+ }
1340
+ function ItemTitle({ className, ...props }) {
1341
+ return /* @__PURE__ */ jsx(
1342
+ "div",
1343
+ {
1344
+ "data-slot": "item-title",
1345
+ className: cn(
1346
+ "line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
1347
+ className
1348
+ ),
1349
+ ...props
1350
+ }
1351
+ );
1352
+ }
1353
+ function ItemActions({ className, ...props }) {
1354
+ return /* @__PURE__ */ jsx(
1355
+ "div",
1356
+ {
1357
+ "data-slot": "item-actions",
1358
+ className: cn("flex items-center gap-2", className),
1359
+ ...props
1360
+ }
1361
+ );
1362
+ }
1363
+ function useControlledState(props) {
1364
+ const { value, defaultValue, onChange } = props;
1365
+ const [state, setInternalState] = React6.useState(
1366
+ value !== void 0 ? value : defaultValue
1367
+ );
1368
+ React6.useEffect(() => {
1369
+ if (value !== void 0) setInternalState(value);
1370
+ }, [value]);
1371
+ const setState = React6.useCallback(
1372
+ (next, ...args) => {
1373
+ setInternalState(next);
1374
+ onChange?.(next, ...args);
1375
+ },
1376
+ [onChange]
1377
+ );
1378
+ return [state, setState];
1379
+ }
1380
+ var [PopoverProvider, usePopover] = getStrictContext("PopoverContext");
1381
+ function Popover(props) {
1382
+ const [isOpen, setIsOpen] = useControlledState({
1383
+ value: props?.open,
1384
+ defaultValue: props?.defaultOpen,
1385
+ onChange: props?.onOpenChange
1386
+ });
1387
+ return /* @__PURE__ */ jsx(PopoverProvider, { value: { isOpen, setIsOpen }, children: /* @__PURE__ */ jsx(
1388
+ Popover$1.Root,
1389
+ {
1390
+ "data-slot": "popover",
1391
+ ...props,
1392
+ onOpenChange: setIsOpen
1393
+ }
1394
+ ) });
1395
+ }
1396
+ function PopoverTrigger(props) {
1397
+ return /* @__PURE__ */ jsx(Popover$1.Trigger, { "data-slot": "popover-trigger", ...props });
1398
+ }
1399
+ function PopoverPortal(props) {
1400
+ const { isOpen } = usePopover();
1401
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsx(
1402
+ Popover$1.Portal,
1403
+ {
1404
+ keepMounted: true,
1405
+ "data-slot": "popover-portal",
1406
+ ...props
1407
+ }
1408
+ ) });
1409
+ }
1410
+ function PopoverPositioner(props) {
1411
+ return /* @__PURE__ */ jsx(Popover$1.Positioner, { "data-slot": "popover-positioner", ...props });
1412
+ }
1413
+ function PopoverPopup({
1414
+ initialFocus,
1415
+ finalFocus,
1416
+ transition = { type: "spring", stiffness: 300, damping: 25 },
1417
+ ...props
1418
+ }) {
1419
+ return /* @__PURE__ */ jsx(
1420
+ Popover$1.Popup,
1421
+ {
1422
+ initialFocus,
1423
+ finalFocus,
1424
+ render: /* @__PURE__ */ jsx(
1425
+ motion.div,
1426
+ {
1427
+ "data-slot": "popover-popup",
1428
+ initial: { opacity: 0, scale: 0.5 },
1429
+ animate: { opacity: 1, scale: 1 },
1430
+ exit: { opacity: 0, scale: 0.5 },
1431
+ transition,
1432
+ ...props
1433
+ },
1434
+ "popover-popup"
1435
+ )
1436
+ }
1437
+ );
1438
+ }
1439
+ function Popover2(props) {
1440
+ return /* @__PURE__ */ jsx(Popover, { ...props });
1441
+ }
1442
+ function PopoverTrigger2(props) {
1443
+ return /* @__PURE__ */ jsx(PopoverTrigger, { ...props });
1444
+ }
1445
+ function PopoverPanel({
1446
+ className,
1447
+ align = "center",
1448
+ sideOffset = 4,
1449
+ initialFocus,
1450
+ finalFocus,
1451
+ style,
1452
+ children,
1453
+ ...props
1454
+ }) {
1455
+ return /* @__PURE__ */ jsx(PopoverPortal, { children: /* @__PURE__ */ jsx(
1456
+ PopoverPositioner,
1457
+ {
1458
+ align,
1459
+ sideOffset,
1460
+ className: "z-50",
1461
+ ...props,
1462
+ children: /* @__PURE__ */ jsx(
1463
+ PopoverPopup,
1464
+ {
1465
+ initialFocus,
1466
+ finalFocus,
1467
+ className: cn(
1468
+ "bg-popover text-popover-foreground w-72 rounded-md border p-4 shadow-md outline-hidden origin-(--transform-origin)",
1469
+ className
1470
+ ),
1471
+ style,
1472
+ children
1473
+ }
1474
+ )
1475
+ }
1476
+ ) });
1477
+ }
1478
+ function getArraySubField(subName, subField) {
1479
+ return { ...subField, label: subField.label ?? subName };
1480
+ }
1481
+ function SortableItem({
1482
+ id,
1483
+ index,
1484
+ item,
1485
+ field,
1486
+ readOnly,
1487
+ atMax,
1488
+ atMin,
1489
+ fieldId,
1490
+ open,
1491
+ onOpenChange,
1492
+ onDuplicate,
1493
+ onRemove,
1494
+ onUpdate,
1495
+ getSummary
1496
+ }) {
1497
+ const {
1498
+ attributes,
1499
+ listeners,
1500
+ setNodeRef,
1501
+ transform,
1502
+ transition,
1503
+ isDragging
1504
+ } = useSortable({ id });
1505
+ const style = {
1506
+ transform: CSS.Transform.toString(transform),
1507
+ transition,
1508
+ opacity: isDragging ? 0.5 : 1
1509
+ };
1510
+ return /* @__PURE__ */ jsx("div", { ref: setNodeRef, style, children: /* @__PURE__ */ jsxs(Popover2, { open, onOpenChange, children: [
1511
+ /* @__PURE__ */ jsxs(Item, { variant: "outline", size: "sm", className: "cursor-pointer", children: [
1512
+ /* @__PURE__ */ jsx(
1513
+ "button",
1514
+ {
1515
+ type: "button",
1516
+ className: "cursor-grab active:cursor-grabbing touch-none p-0.5 text-muted-foreground/40 hover:text-muted-foreground",
1517
+ ...attributes,
1518
+ ...listeners,
1519
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-3.5 w-3.5 shrink-0" })
1520
+ }
1521
+ ),
1522
+ /* @__PURE__ */ jsx(PopoverTrigger2, { className: "flex-1 min-w-0 text-left", children: /* @__PURE__ */ jsx(ItemContent, { children: /* @__PURE__ */ jsx(ItemTitle, { className: "text-xs", children: getSummary(item, index) }) }) }),
1523
+ !readOnly && /* @__PURE__ */ jsxs(ItemActions, { children: [
1524
+ /* @__PURE__ */ jsx(
1525
+ Button,
1526
+ {
1527
+ variant: "ghost",
1528
+ size: "icon",
1529
+ className: "h-5 w-5",
1530
+ disabled: atMax,
1531
+ onClick: (e) => {
1532
+ e.stopPropagation();
1533
+ onDuplicate(index);
1534
+ },
1535
+ title: "Duplicate",
1536
+ children: /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
1537
+ }
1538
+ ),
1539
+ /* @__PURE__ */ jsx(
1540
+ Button,
1541
+ {
1542
+ variant: "ghost",
1543
+ size: "icon",
1544
+ className: "h-5 w-5 text-destructive hover:text-destructive",
1545
+ disabled: atMin,
1546
+ onClick: (e) => {
1547
+ e.stopPropagation();
1548
+ onRemove(index);
1549
+ },
1550
+ title: "Delete",
1551
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
1552
+ }
1553
+ )
1554
+ ] })
1555
+ ] }),
1556
+ /* @__PURE__ */ jsx(
1557
+ PopoverPanel,
1558
+ {
1559
+ side: "left",
1560
+ sideOffset: 60,
1561
+ align: "start",
1562
+ alignOffset: -14,
1563
+ className: "w-56 p-3 flex flex-col gap-2",
1564
+ children: Object.entries(field.arrayFields).map(([subName, subField]) => /* @__PURE__ */ jsx(
1565
+ AutoField,
1566
+ {
1567
+ field: getArraySubField(subName, subField),
1568
+ value: item[subName],
1569
+ onChange: (val) => onUpdate(index, subName, val),
1570
+ readOnly,
1571
+ id: `${fieldId}-${index}-${subName}`
1572
+ },
1573
+ subName
1574
+ ))
1575
+ }
1576
+ )
1577
+ ] }) });
1578
+ }
1579
+ function ArrayField({
1580
+ field,
1581
+ value = [],
1582
+ onChange,
1583
+ readOnly,
1584
+ label,
1585
+ id = ""
1586
+ }) {
1587
+ const [openIndex, setOpenIndex] = React6.useState(null);
1588
+ const stableValue = React6.useMemo(() => {
1589
+ const needsId = value.some((item) => !item._id);
1590
+ if (!needsId) return value;
1591
+ return value.map(
1592
+ (item) => item._id ? item : { ...item, _id: `${id}-${Math.random().toString(36).slice(2)}` }
1593
+ );
1594
+ }, [value, id]);
1595
+ const itemIds = stableValue.map((item) => item._id);
1596
+ const atMax = field.max !== void 0 && stableValue.length >= field.max;
1597
+ const atMin = field.min !== void 0 && stableValue.length <= field.min;
1598
+ const sensors = useSensors(useSensor(PointerSensor));
1599
+ const handleDragEnd = (event) => {
1600
+ const { active, over } = event;
1601
+ if (!over || active.id === over.id) return;
1602
+ const from = itemIds.indexOf(active.id);
1603
+ const to = itemIds.indexOf(over.id);
1604
+ if (from === -1 || to === -1) return;
1605
+ onChange(arrayMove(stableValue, from, to));
1606
+ };
1607
+ const defaultItem = () => {
1608
+ if (!field.defaultItemProps) return {};
1609
+ return typeof field.defaultItemProps === "function" ? field.defaultItemProps(value.length) : { ...field.defaultItemProps };
1610
+ };
1611
+ const addItem = () => {
1612
+ if (atMax || readOnly) return;
1613
+ const newIndex = stableValue.length;
1614
+ onChange([...stableValue, defaultItem()]);
1615
+ setOpenIndex(newIndex);
1616
+ };
1617
+ const removeItem = (i) => {
1618
+ if (atMin || readOnly) return;
1619
+ const next = [...stableValue];
1620
+ next.splice(i, 1);
1621
+ onChange(next);
1622
+ };
1623
+ const duplicateItem = (i) => {
1624
+ if (atMax || readOnly) return;
1625
+ const next = [...stableValue];
1626
+ next.splice(i + 1, 0, { ...stableValue[i] });
1627
+ onChange(next);
1628
+ };
1629
+ const updateItem = (i, subName, val) => {
1630
+ onChange(
1631
+ stableValue.map(
1632
+ (item, idx) => idx === i ? { ...item, [subName]: val } : item
1633
+ )
1634
+ );
1635
+ };
1636
+ const getSummary = (item, i) => {
1637
+ if (field.getItemSummary) return field.getItemSummary(item, i);
1638
+ const first = Object.entries(item).find(
1639
+ (entry) => entry[0] !== "_id" && typeof entry[1] === "string" && entry[1].length > 0
1640
+ );
1641
+ return first?.[1] ?? `Item ${i + 1}`;
1642
+ };
1643
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? field.label ?? "", readOnly, el: "div", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1644
+ /* @__PURE__ */ jsx(
1645
+ DndContext,
1646
+ {
1647
+ sensors,
1648
+ collisionDetection: closestCenter,
1649
+ onDragEnd: handleDragEnd,
1650
+ children: /* @__PURE__ */ jsx(
1651
+ SortableContext,
1652
+ {
1653
+ items: itemIds,
1654
+ strategy: verticalListSortingStrategy,
1655
+ children: /* @__PURE__ */ jsx(ItemGroup, { children: stableValue.map((item, i) => /* @__PURE__ */ jsx(
1656
+ SortableItem,
1657
+ {
1658
+ id: itemIds[i],
1659
+ index: i,
1660
+ item,
1661
+ field,
1662
+ readOnly,
1663
+ atMax,
1664
+ atMin,
1665
+ fieldId: id,
1666
+ open: openIndex === i,
1667
+ onOpenChange: (o) => setOpenIndex(o ? i : null),
1668
+ onDuplicate: duplicateItem,
1669
+ onRemove: removeItem,
1670
+ onUpdate: updateItem,
1671
+ getSummary
1672
+ },
1673
+ itemIds[i]
1674
+ )) })
1675
+ }
1676
+ )
1677
+ }
1678
+ ),
1679
+ !readOnly && !atMax && /* @__PURE__ */ jsxs(
1680
+ Button,
1681
+ {
1682
+ variant: "outline",
1683
+ size: "sm",
1684
+ onClick: addItem,
1685
+ className: "w-full gap-1.5 mt-0.5",
1686
+ children: [
1687
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1688
+ "Add item"
1689
+ ]
1690
+ }
1691
+ )
1692
+ ] }) });
1693
+ }
1694
+ function ObjectField({ children, name, readOnly }) {
1695
+ return /* @__PURE__ */ jsx(FieldLabel, { label: name, el: "div", readOnly, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3 pl-3 border-l-2 border-border/60 ml-1", children }) });
1696
+ }
1697
+ function Card({
1698
+ className,
1699
+ size = "default",
1700
+ ...props
1701
+ }) {
1702
+ return /* @__PURE__ */ jsx(
1703
+ "div",
1704
+ {
1705
+ "data-slot": "card",
1706
+ "data-size": size,
1707
+ className: cn(
1708
+ "group/card flex flex-col gap-6 overflow-hidden rounded-xl bg-card py-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
1709
+ className
1710
+ ),
1711
+ ...props
1712
+ }
1713
+ );
1714
+ }
1715
+ function CardContent({ className, ...props }) {
1716
+ return /* @__PURE__ */ jsx(
1717
+ "div",
1718
+ {
1719
+ "data-slot": "card-content",
1720
+ className: cn("px-6 group-data-[size=sm]/card:px-4", className),
1721
+ ...props
1722
+ }
1723
+ );
1724
+ }
1725
+ function SlotField({ children, label }) {
1726
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", el: "div", children: /* @__PURE__ */ jsx(Card, { className: "border-dashed", children: /* @__PURE__ */ jsx(CardContent, { className: "py-3 px-3", children: children ?? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground text-center", children: "Slot content is managed directly on the canvas." }) }) }) });
1727
+ }
1728
+ function CustomField({ children, label }) {
1729
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", el: "div", children: /* @__PURE__ */ jsx("div", { className: "rounded-md border border-border/60 p-3", children }) });
1730
+ }
1731
+ var toggleVariants = cva(
1732
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-foreground 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 aria-pressed:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1733
+ {
1734
+ variants: {
1735
+ variant: {
1736
+ default: "bg-transparent",
1737
+ outline: "border border-input bg-transparent shadow-xs hover:bg-muted"
1738
+ },
1739
+ size: {
1740
+ default: "h-9 min-w-9 px-2",
1741
+ sm: "h-8 min-w-8 px-1.5",
1742
+ lg: "h-10 min-w-10 px-2.5"
1743
+ }
1744
+ },
1745
+ defaultVariants: {
1746
+ variant: "default",
1747
+ size: "default"
1748
+ }
1749
+ }
1750
+ );
1751
+ function Toggle({
1752
+ className,
1753
+ variant = "default",
1754
+ size = "default",
1755
+ ...props
1756
+ }) {
1757
+ return /* @__PURE__ */ jsx(
1758
+ Toggle$1,
1759
+ {
1760
+ "data-slot": "toggle",
1761
+ className: cn(toggleVariants({ variant, size, className })),
1762
+ ...props
1763
+ }
1764
+ );
1765
+ }
1766
+ function RichtextField({ value, onChange, readOnly, label }) {
1767
+ const ref = React6.useRef(null);
1768
+ const boldLabel = useMsg("richtext.bold");
1769
+ const italicLabel = useMsg("richtext.italic");
1770
+ const linkLabel = useMsg("richtext.link");
1771
+ const linkPrompt = useMsg("richtext.link.prompt");
1772
+ React6.useEffect(() => {
1773
+ if (ref.current && ref.current.innerHTML !== value) {
1774
+ ref.current.innerHTML = value ?? "";
1775
+ }
1776
+ }, [value]);
1777
+ const exec = (cmd, val) => {
1778
+ document.execCommand(cmd, false, val);
1779
+ if (ref.current) onChange(ref.current.innerHTML);
1780
+ };
1781
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", readOnly, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 rounded-md border border-input overflow-hidden", children: [
1782
+ !readOnly && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 border-b px-1 py-0.5 bg-muted/30", children: [
1783
+ /* @__PURE__ */ jsx(Toggle, { size: "sm", onPressedChange: () => exec("bold"), "aria-label": boldLabel, children: /* @__PURE__ */ jsx(Bold, { className: "h-3.5 w-3.5" }) }),
1784
+ /* @__PURE__ */ jsx(Toggle, { size: "sm", onPressedChange: () => exec("italic"), "aria-label": italicLabel, children: /* @__PURE__ */ jsx(Italic, { className: "h-3.5 w-3.5" }) }),
1785
+ /* @__PURE__ */ jsx(
1786
+ Toggle,
1787
+ {
1788
+ size: "sm",
1789
+ onPressedChange: () => {
1790
+ const url = window.prompt(linkPrompt);
1791
+ if (url) exec("createLink", url);
1792
+ },
1793
+ "aria-label": linkLabel,
1794
+ children: /* @__PURE__ */ jsx(Link, { className: "h-3.5 w-3.5" })
1795
+ }
1796
+ )
1797
+ ] }),
1798
+ /* @__PURE__ */ jsx(
1799
+ "div",
1800
+ {
1801
+ ref,
1802
+ contentEditable: !readOnly,
1803
+ suppressContentEditableWarning: true,
1804
+ onInput: () => {
1805
+ if (ref.current) onChange(ref.current.innerHTML);
1806
+ },
1807
+ className: "min-h-[60px] px-3 py-2 text-sm outline-none"
1808
+ }
1809
+ )
1810
+ ] }) });
1811
+ }
1812
+ function InputGroup({ className, ...props }) {
1813
+ return /* @__PURE__ */ jsx(
1814
+ "div",
1815
+ {
1816
+ "data-slot": "input-group",
1817
+ role: "group",
1818
+ className: cn(
1819
+ "group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
1820
+ className
1821
+ ),
1822
+ ...props
1823
+ }
1824
+ );
1825
+ }
1826
+ var inputGroupAddonVariants = cva(
1827
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
1828
+ {
1829
+ variants: {
1830
+ align: {
1831
+ "inline-start": "order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]",
1832
+ "inline-end": "order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]",
1833
+ "block-start": "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
1834
+ "block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"
1835
+ }
1836
+ },
1837
+ defaultVariants: {
1838
+ align: "inline-start"
1839
+ }
1840
+ }
1841
+ );
1842
+ function InputGroupAddon({
1843
+ className,
1844
+ align = "inline-start",
1845
+ ...props
1846
+ }) {
1847
+ return /* @__PURE__ */ jsx(
1848
+ "div",
1849
+ {
1850
+ role: "group",
1851
+ "data-slot": "input-group-addon",
1852
+ "data-align": align,
1853
+ className: cn(inputGroupAddonVariants({ align }), className),
1854
+ onClick: (e) => {
1855
+ if (e.target.closest("button")) {
1856
+ return;
1857
+ }
1858
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
1859
+ },
1860
+ ...props
1861
+ }
1862
+ );
1863
+ }
1864
+ cva(
1865
+ "flex items-center gap-2 text-sm shadow-none",
1866
+ {
1867
+ variants: {
1868
+ size: {
1869
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
1870
+ sm: "",
1871
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
1872
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0"
1873
+ }
1874
+ },
1875
+ defaultVariants: {
1876
+ size: "xs"
1877
+ }
1878
+ }
1879
+ );
1880
+ function Command({
1881
+ className,
1882
+ ...props
1883
+ }) {
1884
+ return /* @__PURE__ */ jsx(
1885
+ Command$1,
1886
+ {
1887
+ "data-slot": "command",
1888
+ className: cn(
1889
+ "flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground",
1890
+ className
1891
+ ),
1892
+ ...props
1893
+ }
1894
+ );
1895
+ }
1896
+ function CommandInput({
1897
+ className,
1898
+ ...props
1899
+ }) {
1900
+ return /* @__PURE__ */ jsx("div", { "data-slot": "command-input-wrapper", className: "p-1 pb-0", children: /* @__PURE__ */ jsxs(InputGroup, { className: "h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!", children: [
1901
+ /* @__PURE__ */ jsx(
1902
+ Command$1.Input,
1903
+ {
1904
+ "data-slot": "command-input",
1905
+ className: cn(
1906
+ "w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
1907
+ className
1908
+ ),
1909
+ ...props
1910
+ }
1911
+ ),
1912
+ /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(SearchIcon, { className: "size-4 shrink-0 opacity-50" }) })
1913
+ ] }) });
1914
+ }
1915
+ function CommandList({
1916
+ className,
1917
+ ...props
1918
+ }) {
1919
+ return /* @__PURE__ */ jsx(
1920
+ Command$1.List,
1921
+ {
1922
+ "data-slot": "command-list",
1923
+ className: cn(
1924
+ "no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
1925
+ className
1926
+ ),
1927
+ ...props
1928
+ }
1929
+ );
1930
+ }
1931
+ function CommandEmpty({
1932
+ className,
1933
+ ...props
1934
+ }) {
1935
+ return /* @__PURE__ */ jsx(
1936
+ Command$1.Empty,
1937
+ {
1938
+ "data-slot": "command-empty",
1939
+ className: cn("py-6 text-center text-sm", className),
1940
+ ...props
1941
+ }
1942
+ );
1943
+ }
1944
+ function CommandItem({
1945
+ className,
1946
+ children,
1947
+ ...props
1948
+ }) {
1949
+ return /* @__PURE__ */ jsxs(
1950
+ Command$1.Item,
1951
+ {
1952
+ "data-slot": "command-item",
1953
+ className: cn(
1954
+ "group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:**:[svg]:text-foreground",
1955
+ className
1956
+ ),
1957
+ ...props,
1958
+ children: [
1959
+ children,
1960
+ /* @__PURE__ */ jsx(CheckIcon, { className: "ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" })
1961
+ ]
1962
+ }
1963
+ );
1964
+ }
1965
+ function Skeleton({ className, ...props }) {
1966
+ return /* @__PURE__ */ jsx(
1967
+ "div",
1968
+ {
1969
+ "data-slot": "skeleton",
1970
+ className: cn("animate-pulse rounded-md bg-muted", className),
1971
+ ...props
1972
+ }
1973
+ );
1974
+ }
1975
+ function ExternalField({ value, onChange, fetchList, readOnly, label }) {
1976
+ const [query, setQuery] = React6.useState("");
1977
+ const [results, setResults] = React6.useState([]);
1978
+ const [loading, setLoading] = React6.useState(false);
1979
+ React6.useEffect(() => {
1980
+ if (!fetchList) return;
1981
+ setLoading(true);
1982
+ const timer = setTimeout(async () => {
1983
+ try {
1984
+ const data = await fetchList(query);
1985
+ setResults(data);
1986
+ } catch {
1987
+ setResults([]);
1988
+ } finally {
1989
+ setLoading(false);
1990
+ }
1991
+ }, 300);
1992
+ return () => clearTimeout(timer);
1993
+ }, [query, fetchList]);
1994
+ const selectedLabel = results.find((r) => r.value === value)?.label ?? String(value ?? "");
1995
+ return /* @__PURE__ */ jsx(FieldLabel, { label: label ?? "", readOnly, children: /* @__PURE__ */ jsxs(Command, { className: "rounded-md border border-input", children: [
1996
+ /* @__PURE__ */ jsx(
1997
+ CommandInput,
1998
+ {
1999
+ placeholder: "Search...",
2000
+ value: query,
2001
+ onValueChange: setQuery,
2002
+ disabled: readOnly
2003
+ }
2004
+ ),
2005
+ /* @__PURE__ */ jsx(CommandList, { children: loading ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 p-2", children: [
2006
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-full" }),
2007
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-3/4" }),
2008
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-1/2" })
2009
+ ] }) : results.length === 0 ? /* @__PURE__ */ jsx(CommandEmpty, { children: "No results found." }) : results.map((r, i) => /* @__PURE__ */ jsx(
2010
+ CommandItem,
2011
+ {
2012
+ "data-checked": r.value === value,
2013
+ onSelect: () => onChange(r.value),
2014
+ children: r.label
2015
+ },
2016
+ i
2017
+ )) }),
2018
+ value != null && /* @__PURE__ */ jsxs("div", { className: "border-t px-3 py-1.5 text-xs text-muted-foreground truncate", children: [
2019
+ "Selected: ",
2020
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: selectedLabel })
2021
+ ] })
2022
+ ] }) });
2023
+ }
2024
+
2025
+ // src/core/overrides/fields/FieldTypesRegistry.ts
2026
+ var fieldTypesRegistry = {
2027
+ text: TextField,
2028
+ textarea: TextareaField,
2029
+ number: NumberField,
2030
+ select: SelectField,
2031
+ radio: RadioField,
2032
+ array: ArrayField,
2033
+ object: ObjectField,
2034
+ slot: SlotField,
2035
+ custom: CustomField,
2036
+ richtext: RichtextField,
2037
+ external: ExternalField
2038
+ };
2039
+ function PuckRoot({ children }) {
2040
+ return /* @__PURE__ */ jsx(Fragment, { children });
2041
+ }
2042
+ var puckOverrides = {
2043
+ drawer: EditorDrawer,
2044
+ components: EditorComponents,
2045
+ drawerItem: DrawerItem,
2046
+ componentItem: DrawerItem,
2047
+ outline: EditorOutline,
2048
+ iframe: CanvasIframe,
2049
+ preview: CanvasPreview,
2050
+ componentOverlay: ComponentOverlay,
2051
+ actionBar: ActionBar,
2052
+ fields: FieldWrapper,
2053
+ fieldTypes: fieldTypesRegistry,
2054
+ puck: PuckRoot
2055
+ };
2056
+
2057
+ export { ActionBar, CanvasIframe, CanvasPreview, ComponentOverlay, DrawerItem, EditorComponents, EditorDrawer, EditorOutline, FieldLabel, FieldWrapper, fieldTypesRegistry, puckOverrides };