@jbpark/live-editor 4.6.0 → 5.0.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.
Files changed (83) hide show
  1. package/README.ko.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/{boundary-BLB80tyw.js → boundary-6_nTsWxP.js} +2 -2
  4. package/dist/{boundary-BLB80tyw.js.map → boundary-6_nTsWxP.js.map} +1 -1
  5. package/dist/{compile-Dxas-D05.js → compile-DwOyWrmI.js} +8 -7
  6. package/dist/compile-DwOyWrmI.js.map +1 -0
  7. package/dist/constants-D-XKuEY2.js +58 -0
  8. package/dist/constants-D-XKuEY2.js.map +1 -0
  9. package/dist/{context-B8MzP7_p.js → context-DDR1xVKo.js} +3 -3
  10. package/dist/{context-B8MzP7_p.js.map → context-DDR1xVKo.js.map} +1 -1
  11. package/dist/{core-Bgfek364.js → core-CKazr4tT.js} +2 -2
  12. package/dist/core-CKazr4tT.js.map +1 -0
  13. package/dist/{detect-typescript-Dr_oLniM.js → detect-typescript-CoAO-QUJ.js} +2 -2
  14. package/dist/{detect-typescript-Dr_oLniM.js.map → detect-typescript-CoAO-QUJ.js.map} +1 -1
  15. package/dist/dnd/index.d.ts +2 -2
  16. package/dist/dnd/index.js +2 -2
  17. package/dist/dnd-BlC1gqUy.js +4446 -0
  18. package/dist/dnd-BlC1gqUy.js.map +1 -0
  19. package/dist/document-BQhb7YR8.d.ts +17 -0
  20. package/dist/document-DcwIEHRV.js +34077 -0
  21. package/dist/document-DcwIEHRV.js.map +1 -0
  22. package/dist/editor/index.js +2 -2
  23. package/dist/{editor-CMElfHer.js → editor-Bty5XzpV.js} +2 -2
  24. package/dist/{editor-CMElfHer.js.map → editor-Bty5XzpV.js.map} +1 -1
  25. package/dist/error/index.d.ts +1 -1
  26. package/dist/error/index.js +2 -2
  27. package/dist/{error-CN_GCnXB.js → error-DyYx2NQ2.js} +17 -31
  28. package/dist/error-DyYx2NQ2.js.map +1 -0
  29. package/dist/{index-C69Lv-jR.d.ts → index-0VB5C3Jk.d.ts} +28 -50
  30. package/dist/{index-DpDU1IAw.d.ts → index-92L7F440.d.ts} +2 -2
  31. package/dist/index-C8h8TcIl.d.ts +15 -0
  32. package/dist/{index-DQkL37eL.d.ts → index-Cl79M7ZJ.d.ts} +6 -4
  33. package/dist/{index-CRwEhJ7w.d.ts → index-kKUjo9lr.d.ts} +2 -2
  34. package/dist/{index-DrWtSKXu.d.ts → index-tAiuHZsC.d.ts} +2 -2
  35. package/dist/index.d.ts +9 -9
  36. package/dist/index.js +6 -6
  37. package/dist/index.js.map +1 -1
  38. package/dist/{messages-CSrMI4Rm.js → messages-Bt5WXKTE.js} +5 -2
  39. package/dist/messages-Bt5WXKTE.js.map +1 -0
  40. package/dist/{messages-IBPs4TkZ.d.ts → messages-Yb3b7YJt.d.ts} +5 -2
  41. package/dist/preview/index.d.ts +1 -1
  42. package/dist/preview/index.js +2 -2
  43. package/dist/{preview-CtyG5UbH.js → preview-DC0pyOdu.js} +4 -4
  44. package/dist/preview-DC0pyOdu.js.map +1 -0
  45. package/dist/provider/index.d.ts +2 -2
  46. package/dist/provider/index.js +2 -2
  47. package/dist/{sections-BYWOBISr.js → sections-4uirsXRU.js} +4 -7
  48. package/dist/sections-4uirsXRU.js.map +1 -0
  49. package/dist/style.css +776 -346
  50. package/dist/{validate-DB59Z_Nx.d.ts → types-Czps1Txy.d.ts} +12 -28
  51. package/dist/update-2PJbwoS8.d.ts +68 -0
  52. package/dist/{items-B6OXxcLG.js → update-DNInQSM7.js} +261 -512
  53. package/dist/update-DNInQSM7.js.map +1 -0
  54. package/dist/{use-dynamic-tailwind-BP1toJkW.js → use-dynamic-tailwind-CSYKXDmX.js} +301 -273
  55. package/dist/use-dynamic-tailwind-CSYKXDmX.js.map +1 -0
  56. package/dist/utils/ast/index.d.ts +47 -4
  57. package/dist/utils/ast/index.js +15 -77
  58. package/dist/utils/ast/index.js.map +1 -1
  59. package/dist/utils/index.d.ts +13 -32
  60. package/dist/utils/index.js +4 -26
  61. package/dist/validate-CZz30Pnb.js +122 -0
  62. package/dist/validate-CZz30Pnb.js.map +1 -0
  63. package/dist/validate-CiA1LF6S.d.ts +33 -0
  64. package/package.json +13 -15
  65. package/dist/compile-Dxas-D05.js.map +0 -1
  66. package/dist/constants-DU17Tdwo.js +0 -1459
  67. package/dist/constants-DU17Tdwo.js.map +0 -1
  68. package/dist/core-Bgfek364.js.map +0 -1
  69. package/dist/dnd-UoJILA75.js +0 -2756
  70. package/dist/dnd-UoJILA75.js.map +0 -1
  71. package/dist/document-CGbe6bSV.js +0 -38667
  72. package/dist/document-CGbe6bSV.js.map +0 -1
  73. package/dist/document-Ct4jW78J.d.ts +0 -1553
  74. package/dist/error-CN_GCnXB.js.map +0 -1
  75. package/dist/index-DKYUxi-E.d.ts +0 -227
  76. package/dist/items-B6OXxcLG.js.map +0 -1
  77. package/dist/messages-CSrMI4Rm.js.map +0 -1
  78. package/dist/preview-CtyG5UbH.js.map +0 -1
  79. package/dist/sections-BYWOBISr.js.map +0 -1
  80. package/dist/use-dynamic-tailwind-BP1toJkW.js.map +0 -1
  81. package/dist/utils/index.js.map +0 -1
  82. package/dist/validate-iLo8IONY.js +0 -64
  83. package/dist/validate-iLo8IONY.js.map +0 -1
@@ -0,0 +1,4446 @@
1
+ import { C as isJSXElement, D as isJSXIdentifier, E as isJSXFragment, I as isSpreadElement, L as isStringLiteral, N as isObjectExpression, O as isJSXOpeningElement, P as isObjectProperty, S as isJSXAttribute, a as inspectDocument, at as stringLiteral, b as isExpression, ct as traverseFast, d as applyEdits, dt as parseExpression, k as isJSXSpreadAttribute, l as fillIdsFrom, n as fillSectionIds, ot as templateElement, p as nanoid, st as templateLiteral, t as createSectionPreviewCache, u as replaceIds, x as isIdentifier } from "./document-DcwIEHRV.js";
2
+ import { r as DATA_ATTR, s as RESERVED_BINDING_PROPERTIES, t as BINDING_PROP } from "./constants-D-XKuEY2.js";
3
+ import { n as validateBindingValue } from "./validate-CZz30Pnb.js";
4
+ import { i as useLiveMessages } from "./messages-Bt5WXKTE.js";
5
+ import { i as usePreview } from "./states-Ci1AvoQ9.js";
6
+ import { _ as parseArrayExpression, a as getChildrenSignatures, b as generateCode, c as removeIndices, d as getStructuredValue, f as readNodeBindings, g as isLosslesslyEvaluable, h as extractObjectProperties, i as valueToExpression, l as findEditableChildren, m as extractNodeValue, n as updateAll, o as extract, p as canLosslesslyEvaluateSource, r as createNodeFromValue, s as moveSelectedIndices, u as getCurrentValue, v as parseValue } from "./update-DNInQSM7.js";
7
+ import { t as cn } from "./cn-CAnad2vL.js";
8
+ import { r as preloadScripts } from "./compile-DwOyWrmI.js";
9
+ import { a as replaceSections, i as generateSection, r as extractSections } from "./sections-4uirsXRU.js";
10
+ import { n as Error$1, t as ErrorBoundary } from "./boundary-6_nTsWxP.js";
11
+ import { i as Frame, n as useCompiledModule, t as useDynamicTailwind } from "./use-dynamic-tailwind-CSYKXDmX.js";
12
+ import { t as Core } from "./core-CKazr4tT.js";
13
+ import { Children, createContext, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
14
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
+ import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, PointerSensor, TouchSensor, closestCenter, useDndContext, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
16
+ import { restrictToParentElement, restrictToVerticalAxis } from "@dnd-kit/modifiers";
17
+ import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
18
+ import { useDebounce, useElementPosition, useKeyPress, useLatest, useMultiSelect, usePendingCommit, useResponsiveSize, useSet, useStableValue } from "@jbpark/use-hooks";
19
+ import { Button, Card, Checkbox, ColorPicker, DatePicker, Drawer, Input, RichTextEditor, Select, Space, Splitter, TimePicker, Toast, Typography } from "@jbpark/ui-kit";
20
+ import { CSS as CSS$1 } from "@dnd-kit/utilities";
21
+ import { ArrowDown, ArrowUp, ChevronDown, ChevronRight, ChevronUp, Copy, Crosshair, GripVertical, LayoutGrid, Plus, Trash, X } from "lucide-react";
22
+ //#region src/constants/palette-sections.ts
23
+ const PALETTE_SECTIONS = [
24
+ {
25
+ id: "hero",
26
+ name: "Hero",
27
+ code: `
28
+ <section
29
+ data-name="Hero"
30
+ className={cn(
31
+ 'text-center text-white',
32
+ //
33
+ )}
34
+ >
35
+ <ui.Space
36
+ data-id=""
37
+ data-binding={[
38
+ {
39
+ label: 'Background Style',
40
+ property: 'style',
41
+ type: 'object',
42
+ // Types the keys inside the object value; a leaf takes the same
43
+ // fields as a top-level binding.
44
+ render: {
45
+ backgroundColor: { type: 'color', label: 'Background Color' },
46
+ },
47
+ // Not a library field, so it arrives as meta.group.
48
+ group: 'layout',
49
+ },
50
+ {
51
+ label: 'Content Spacing',
52
+ property: 'size',
53
+ type: 'number',
54
+ // Constraints the library validates with or without a widget.
55
+ min: 0,
56
+ max: 40,
57
+ // Presentation only: the built-in panel ignores it and shows a
58
+ // number input; a custom panel can switch on widget.type.
59
+ widget: { type: 'slider', step: 4, unit: 'px' },
60
+ group: 'layout',
61
+ },
62
+ ]}
63
+ style={{
64
+ backgroundColor: '#6366f1',
65
+ }}
66
+ orientation="vertical"
67
+ size={16}
68
+ align="center"
69
+ className={cn(
70
+ 'px-5 py-15',
71
+ //
72
+ )}
73
+ >
74
+ <h1
75
+ data-id=""
76
+ data-binding={[
77
+ {
78
+ label: 'Title',
79
+ property: 'innerText',
80
+ type: 'string',
81
+ required: true,
82
+ },
83
+ ]}
84
+ className={cn(
85
+ 'text-5xl font-bold',
86
+ //
87
+ )}
88
+ >
89
+ Welcome to Live Editor
90
+ </h1>
91
+ <p
92
+ data-id=""
93
+ data-binding={[
94
+ {
95
+ label: 'Description',
96
+ type: 'richtext',
97
+ },
98
+ ]}
99
+ className={cn(
100
+ 'text-xl',
101
+ //
102
+ )}
103
+ >
104
+ Build <strong>dynamic components</strong> with drag & drop
105
+ </p>
106
+ <ui.Button
107
+ data-id=""
108
+ data-binding={[
109
+ {
110
+ label: 'Button Text',
111
+ property: 'innerText',
112
+ },
113
+ {
114
+ label: 'Button Icon',
115
+ property: 'icon',
116
+ type: 'jsx',
117
+ },
118
+ {
119
+ label: 'Button Size',
120
+ property: 'size',
121
+ options: [
122
+ { label: 'Small', value: 'small' },
123
+ { label: 'Middle', value: 'middle' },
124
+ { label: 'Large', value: 'large' },
125
+ ],
126
+ },
127
+ {
128
+ label: 'Button Variant',
129
+ property: 'variant',
130
+ options: [
131
+ { label: 'Solid', value: 'solid' },
132
+ { label: 'Outlined', value: 'outlined' },
133
+ { label: 'Filled', value: 'filled' },
134
+ ],
135
+ },
136
+ ]}
137
+ icon={<span>🚀</span>}
138
+ size="large"
139
+ variant="solid"
140
+ className={cn(
141
+ 'bg-white text-indigo-500',
142
+ 'hover:bg-gray-50',
143
+ //
144
+ )}
145
+ >
146
+ Get Started
147
+ </ui.Button>
148
+ </ui.Space>
149
+ </section>
150
+ `
151
+ },
152
+ {
153
+ id: "features",
154
+ name: "Features",
155
+ code: `
156
+ <section
157
+ data-name="Features"
158
+ className={cn(
159
+ 'px-5 py-10',
160
+ //
161
+ )}
162
+ >
163
+ <ui.Space
164
+ data-id=""
165
+ data-binding={[
166
+ {
167
+ label: 'Feature Cards',
168
+ property: 'children',
169
+ },
170
+ ]}
171
+ className={cn(
172
+ 'mx-auto max-w-7xl',
173
+ //
174
+ )}
175
+ size="large"
176
+ wrap
177
+ >
178
+ <ui.Card
179
+ className={cn(
180
+ 'min-w-70 flex-1 text-center',
181
+ //
182
+ )}
183
+ >
184
+ <ui.Space orientation="vertical" size="middle" align="center">
185
+ <div
186
+ data-id=""
187
+ data-binding={[
188
+ {
189
+ label: 'Icon',
190
+ property: 'innerText',
191
+ },
192
+ ]}
193
+ className={cn(
194
+ 'text-5xl',
195
+ //
196
+ )}
197
+ >
198
+ 🚀
199
+ </div>
200
+ <h3
201
+ data-id=""
202
+ data-binding={[
203
+ {
204
+ label: 'Title',
205
+ property: 'innerText',
206
+ },
207
+ ]}
208
+ className={cn(
209
+ 'text-2xl text-gray-800',
210
+ //
211
+ )}
212
+ >
213
+ Feature 1
214
+ </h3>
215
+ <p
216
+ data-id=""
217
+ data-binding={[
218
+ {
219
+ label: 'Description',
220
+ property: 'innerText',
221
+ },
222
+ ]}
223
+ className={cn(
224
+ 'text-gray-600',
225
+ //
226
+ )}
227
+ >
228
+ Amazing feature description
229
+ </p>
230
+ </ui.Space>
231
+ </ui.Card>
232
+ <ui.Card
233
+ className={cn(
234
+ 'min-w-70 flex-1 text-center',
235
+ //
236
+ )}
237
+ >
238
+ <ui.Space orientation="vertical" size="middle" align="center">
239
+ <div
240
+ data-id=""
241
+ data-binding={[
242
+ {
243
+ label: 'Icon',
244
+ property: 'innerText',
245
+ },
246
+ ]}
247
+ className={cn(
248
+ 'text-5xl',
249
+ //
250
+ )}
251
+ >
252
+ ⚡
253
+ </div>
254
+ <h3
255
+ data-id=""
256
+ data-binding={[
257
+ {
258
+ label: 'Title',
259
+ property: 'innerText',
260
+ },
261
+ ]}
262
+ className={cn(
263
+ 'text-2xl text-gray-800',
264
+ //
265
+ )}
266
+ >
267
+ Feature 2
268
+ </h3>
269
+ <p
270
+ data-id=""
271
+ data-binding={[
272
+ {
273
+ label: 'Description',
274
+ property: 'innerText',
275
+ },
276
+ ]}
277
+ className={cn(
278
+ 'text-gray-600',
279
+ //
280
+ )}
281
+ >
282
+ Another great feature
283
+ </p>
284
+ </ui.Space>
285
+ </ui.Card>
286
+ <ui.Card
287
+ className={cn(
288
+ 'min-w-70 flex-1 text-center',
289
+ //
290
+ )}
291
+ >
292
+ <ui.Space orientation="vertical" size="middle" align="center">
293
+ <div
294
+ data-id=""
295
+ data-binding={[
296
+ {
297
+ label: 'Icon',
298
+ property: 'innerText',
299
+ },
300
+ ]}
301
+ className={cn(
302
+ 'text-5xl',
303
+ //
304
+ )}
305
+ >
306
+ ✨
307
+ </div>
308
+ <h3
309
+ data-id=""
310
+ data-binding={[
311
+ {
312
+ label: 'Title',
313
+ property: 'innerText',
314
+ },
315
+ ]}
316
+ className={cn(
317
+ 'text-2xl text-gray-800',
318
+ //
319
+ )}
320
+ >
321
+ Feature 3
322
+ </h3>
323
+ <p
324
+ data-id=""
325
+ data-binding={[
326
+ {
327
+ label: 'Description',
328
+ property: 'innerText',
329
+ },
330
+ ]}
331
+ className={cn(
332
+ 'text-gray-600',
333
+ //
334
+ )}
335
+ >
336
+ One more awesome thing
337
+ </p>
338
+ </ui.Space>
339
+ </ui.Card>
340
+ </ui.Space>
341
+ </section>
342
+ `
343
+ },
344
+ {
345
+ id: "about",
346
+ name: "About",
347
+ code: `
348
+ <section
349
+ data-name="About"
350
+ className={cn(
351
+ 'mx-auto max-w-3xl px-5 py-10',
352
+ //
353
+ )}
354
+ >
355
+ <ui.Typography.Title
356
+ data-id=""
357
+ data-binding={[{ label: 'Title', property: 'innerText' }]}
358
+ level={2}
359
+ className={cn(
360
+ 'mb-5 text-4xl text-gray-800',
361
+ //
362
+ )}
363
+ >
364
+ About Live Editor
365
+ </ui.Typography.Title>
366
+ <ui.Typography.Paragraph
367
+ data-id=""
368
+ data-binding={[
369
+ {
370
+ label: 'Description 1',
371
+ type: 'richtext',
372
+ },
373
+ ]}
374
+ className={cn(
375
+ 'mb-4 text-lg leading-relaxed text-gray-600',
376
+ //
377
+ )}
378
+ >
379
+ Live Editor is a powerful visual development tool that enables you to
380
+ create stunning web interfaces through an intuitive drag-and-drop
381
+ experience. Built with modern web technologies, it streamlines your
382
+ workflow and accelerates development cycles.
383
+ </ui.Typography.Paragraph>
384
+ <ui.Typography.Paragraph
385
+ data-id=""
386
+ data-binding={[
387
+ {
388
+ label: 'Description 2',
389
+ type: 'richtext',
390
+ },
391
+ ]}
392
+ className={cn(
393
+ 'text-lg leading-relaxed text-gray-600',
394
+ //
395
+ )}
396
+ >
397
+ Whether you&apos;re a seasoned developer or just starting out, Live
398
+ Editor provides the flexibility and control you need to bring your
399
+ creative vision to life. Focus on what matters most - building great
400
+ products.
401
+ </ui.Typography.Paragraph>
402
+ </section>
403
+ `
404
+ },
405
+ {
406
+ id: "cta",
407
+ name: "CTA",
408
+ code: `
409
+ <section
410
+ data-name="CTA"
411
+ className={cn(
412
+ 'bg-gray-50 px-5 py-12 text-center',
413
+ //
414
+ )}
415
+ >
416
+ <div
417
+ className={cn(
418
+ 'mx-auto max-w-xl',
419
+ //
420
+ )}
421
+ >
422
+ <ui.Typography.Title
423
+ data-id=""
424
+ data-binding={[{ label: 'Title', property: 'innerText' }]}
425
+ level={2}
426
+ className={cn(
427
+ 'mb-4 text-3xl text-gray-800',
428
+ //
429
+ )}
430
+ >
431
+ Ready to get started?
432
+ </ui.Typography.Title>
433
+ <ui.Typography.Paragraph
434
+ data-id=""
435
+ data-binding={[
436
+ {
437
+ label: 'Description',
438
+ property: 'innerText',
439
+ },
440
+ ]}
441
+ className={cn(
442
+ 'mb-6 text-lg text-gray-600',
443
+ //
444
+ )}
445
+ >
446
+ Start exploring what Live Editor can do for your projects
447
+ </ui.Typography.Paragraph>
448
+ <ui.Space
449
+ data-id=""
450
+ data-binding={[{ label: 'Buttons', property: 'children' }]}
451
+ className={cn(
452
+ 'flex flex-wrap justify-center gap-4',
453
+ //
454
+ )}
455
+ >
456
+ <ui.Button
457
+ data-id=""
458
+ data-binding={[
459
+ {
460
+ label: 'Button 1 Text',
461
+ property: 'innerText',
462
+ },
463
+ ]}
464
+ size="large"
465
+ className={cn(
466
+ 'bg-indigo-500 px-8 text-base text-white',
467
+ 'hover:bg-indigo-600',
468
+ //
469
+ )}
470
+ >
471
+ View Documentation
472
+ </ui.Button>
473
+ <ui.Button
474
+ data-id=""
475
+ data-binding={[
476
+ {
477
+ label: 'Button 2 Text',
478
+ property: 'innerText',
479
+ },
480
+ ]}
481
+ variant="outlined"
482
+ size="large"
483
+ className={cn(
484
+ 'border-2 border-indigo-500 px-8 text-base text-indigo-500',
485
+ 'hover:bg-indigo-50',
486
+ //
487
+ )}
488
+ >
489
+ View Examples
490
+ </ui.Button>
491
+ </ui.Space>
492
+ </div>
493
+ </section>
494
+ `
495
+ },
496
+ {
497
+ id: "getting-started",
498
+ name: "Getting Started",
499
+ code: `
500
+ <section
501
+ data-name="Getting Started"
502
+ className={cn(
503
+ 'px-5 py-16',
504
+ //
505
+ )}
506
+ >
507
+ <div
508
+ className={cn(
509
+ 'mx-auto max-w-5xl',
510
+ //
511
+ )}
512
+ >
513
+ <ui.Typography.Title
514
+ data-id=""
515
+ data-binding={[{ label: 'Title', property: 'innerText' }]}
516
+ level={2}
517
+ className={cn(
518
+ 'mb-12 text-center text-4xl font-bold text-gray-800',
519
+ //
520
+ )}
521
+ >
522
+ How to Get Started
523
+ </ui.Typography.Title>
524
+ <div
525
+ className={cn(
526
+ 'grid gap-8 md:grid-cols-3',
527
+ //
528
+ )}
529
+ >
530
+ <ui.Card
531
+ data-id=""
532
+ data-binding={[
533
+ {
534
+ label: 'Card 1 Content',
535
+ property: 'children',
536
+ },
537
+ ]}
538
+ className={cn(
539
+ 'rounded-lg border-2 border-gray-200 bg-white p-8',
540
+ //
541
+ )}
542
+ >
543
+ <ui.Typography.Title
544
+ data-id=""
545
+ data-binding={[
546
+ {
547
+ label: 'Card 1 Title',
548
+ property: 'innerText',
549
+ },
550
+ ]}
551
+ level={3}
552
+ className={cn(
553
+ 'mb-2 text-2xl font-bold text-gray-800',
554
+ //
555
+ )}
556
+ >
557
+ Explore
558
+ </ui.Typography.Title>
559
+ <ui.Typography.Paragraph
560
+ data-id=""
561
+ data-binding={[
562
+ {
563
+ label: 'Card 1 Icon',
564
+ property: 'innerText',
565
+ },
566
+ ]}
567
+ className={cn(
568
+ 'mb-6 text-4xl font-bold text-indigo-600',
569
+ //
570
+ )}
571
+ >
572
+ 📚
573
+ </ui.Typography.Paragraph>
574
+ <ul
575
+ className={cn(
576
+ 'mb-8 space-y-3 text-gray-600',
577
+ //
578
+ )}
579
+ >
580
+ <li>✓ Read documentation</li>
581
+ <li>✓ Browse examples</li>
582
+ <li>✓ Try the demo</li>
583
+ </ul>
584
+ <ui.Button
585
+ data-id=""
586
+ data-binding={[
587
+ {
588
+ label: 'Card 1 Button',
589
+ property: 'innerText',
590
+ },
591
+ ]}
592
+ variant="outlined"
593
+ size="large"
594
+ block
595
+ className={cn(
596
+ 'border-2 border-indigo-500 text-indigo-500',
597
+ 'hover:bg-indigo-50',
598
+ //
599
+ )}
600
+ >
601
+ View Docs
602
+ </ui.Button>
603
+ </ui.Card>
604
+ <ui.Card
605
+ data-id=""
606
+ data-binding={[
607
+ {
608
+ label: 'Card 2 Content',
609
+ property: 'children',
610
+ },
611
+ ]}
612
+ className={cn(
613
+ 'relative rounded-lg border-2 border-indigo-500 bg-white p-8',
614
+ //
615
+ )}
616
+ >
617
+ <div
618
+ className={cn(
619
+ 'absolute -top-4 left-1/2 -translate-x-1/2',
620
+ 'rounded-full bg-indigo-500 px-4 py-1 text-sm text-white',
621
+ //
622
+ )}
623
+ >
624
+ Recommended
625
+ </div>
626
+ <ui.Typography.Title
627
+ data-id=""
628
+ data-binding={[
629
+ {
630
+ label: 'Card 2 Title',
631
+ property: 'innerText',
632
+ },
633
+ ]}
634
+ level={3}
635
+ className={cn(
636
+ 'mb-2 text-2xl font-bold text-gray-800',
637
+ //
638
+ )}
639
+ >
640
+ Practice
641
+ </ui.Typography.Title>
642
+ <ui.Typography.Paragraph
643
+ data-id=""
644
+ data-binding={[
645
+ {
646
+ label: 'Card 2 Icon',
647
+ property: 'innerText',
648
+ },
649
+ ]}
650
+ className={cn(
651
+ 'mb-6 text-4xl font-bold text-indigo-600',
652
+ //
653
+ )}
654
+ >
655
+ 💻
656
+ </ui.Typography.Paragraph>
657
+ <ul
658
+ className={cn(
659
+ 'mb-8 space-y-3 text-gray-600',
660
+ //
661
+ )}
662
+ >
663
+ <li>✓ Interactive playground</li>
664
+ <li>✓ Experiment freely</li>
665
+ <li>✓ Build your own</li>
666
+ </ul>
667
+ <ui.Button
668
+ data-id=""
669
+ data-binding={[
670
+ {
671
+ label: 'Card 2 Button',
672
+ property: 'innerText',
673
+ },
674
+ ]}
675
+ size="large"
676
+ block
677
+ className={cn(
678
+ 'bg-indigo-500 text-white',
679
+ 'hover:bg-indigo-600',
680
+ //
681
+ )}
682
+ >
683
+ Start Building
684
+ </ui.Button>
685
+ </ui.Card>
686
+ <ui.Card
687
+ data-id=""
688
+ data-binding={[
689
+ {
690
+ label: 'Card 3 Content',
691
+ property: 'children',
692
+ },
693
+ ]}
694
+ className={cn(
695
+ 'rounded-lg border-2 border-gray-200 bg-white p-8',
696
+ //
697
+ )}
698
+ >
699
+ <ui.Typography.Title
700
+ data-id=""
701
+ data-binding={[
702
+ {
703
+ label: 'Card 3 Title',
704
+ property: 'innerText',
705
+ },
706
+ ]}
707
+ level={3}
708
+ className={cn(
709
+ 'mb-2 text-2xl font-bold text-gray-800',
710
+ //
711
+ )}
712
+ >
713
+ Contribute
714
+ </ui.Typography.Title>
715
+ <ui.Typography.Paragraph
716
+ data-id=""
717
+ data-binding={[
718
+ {
719
+ label: 'Card 3 Icon',
720
+ property: 'innerText',
721
+ },
722
+ ]}
723
+ className={cn(
724
+ 'mb-6 text-4xl font-bold text-indigo-600',
725
+ //
726
+ )}
727
+ >
728
+ 🤝
729
+ </ui.Typography.Paragraph>
730
+ <ul
731
+ className={cn(
732
+ 'mb-8 space-y-3 text-gray-600',
733
+ //
734
+ )}
735
+ >
736
+ <li>✓ Report issues</li>
737
+ <li>✓ Suggest features</li>
738
+ <li>✓ Submit pull requests</li>
739
+ </ul>
740
+ <ui.Button
741
+ data-id=""
742
+ data-binding={[
743
+ {
744
+ label: 'Card 3 Button',
745
+ property: 'innerText',
746
+ },
747
+ ]}
748
+ variant="outlined"
749
+ size="large"
750
+ block
751
+ className={cn(
752
+ 'border-2 border-indigo-500 text-indigo-500',
753
+ 'hover:bg-indigo-50',
754
+ //
755
+ )}
756
+ >
757
+ View on GitHub
758
+ </ui.Button>
759
+ </ui.Card>
760
+ </div>
761
+ </div>
762
+ </section>
763
+ `
764
+ },
765
+ {
766
+ id: "key-features",
767
+ name: "Key Features",
768
+ code: `
769
+ <section
770
+ data-name="Key Features"
771
+ className={cn(
772
+ 'bg-indigo-50 px-5 py-16',
773
+ //
774
+ )}
775
+ >
776
+ <div
777
+ className={cn(
778
+ 'mx-auto max-w-6xl',
779
+ //
780
+ )}
781
+ >
782
+ <h2
783
+ data-id=""
784
+ data-binding={[{ label: 'Title', property: 'innerText' }]}
785
+ className={cn(
786
+ 'mb-12 text-center text-4xl font-bold text-gray-800',
787
+ //
788
+ )}
789
+ >
790
+ Key Features
791
+ </h2>
792
+ <div
793
+ className={cn(
794
+ 'grid gap-8 md:grid-cols-2',
795
+ //
796
+ )}
797
+ >
798
+ <div
799
+ className={cn(
800
+ 'rounded-lg bg-white p-8 shadow-md',
801
+ //
802
+ )}
803
+ >
804
+ <div
805
+ className={cn(
806
+ 'mb-4 text-4xl',
807
+ //
808
+ )}
809
+ >
810
+ 🎨
811
+ </div>
812
+ <h3
813
+ className={cn(
814
+ 'mb-4 text-2xl font-bold text-gray-800',
815
+ //
816
+ )}
817
+ >
818
+ Visual Editing
819
+ </h3>
820
+ <p
821
+ className={cn(
822
+ 'text-gray-600',
823
+ //
824
+ )}
825
+ >
826
+ See your changes in real-time as you build. Live Editor provides
827
+ instant visual feedback, making it easier to understand how code
828
+ affects your interface.
829
+ </p>
830
+ </div>
831
+ <div
832
+ className={cn(
833
+ 'rounded-lg bg-white p-8 shadow-md',
834
+ //
835
+ )}
836
+ >
837
+ <div
838
+ className={cn(
839
+ 'mb-4 text-4xl',
840
+ //
841
+ )}
842
+ >
843
+ 🧩
844
+ </div>
845
+ <h3
846
+ className={cn(
847
+ 'mb-4 text-2xl font-bold text-gray-800',
848
+ //
849
+ )}
850
+ >
851
+ Component-Based
852
+ </h3>
853
+ <p
854
+ className={cn(
855
+ 'text-gray-600',
856
+ //
857
+ )}
858
+ >
859
+ Build with reusable components that you can drag, drop, and
860
+ customize. Learn modern development patterns while creating your
861
+ interface.
862
+ </p>
863
+ </div>
864
+ </div>
865
+ </div>
866
+ </section>
867
+ `
868
+ },
869
+ {
870
+ id: "stats",
871
+ name: "Stats",
872
+ code: `
873
+ <section
874
+ data-name="Stats"
875
+ className={cn(
876
+ 'bg-indigo-600 text-white',
877
+ //
878
+ )}
879
+ >
880
+ <ui.Marquees
881
+ data-id=""
882
+ data-binding={[
883
+ { label: 'Stats Items', property: 'items' },
884
+ {
885
+ label: 'Scroll Speed',
886
+ property: 'speed',
887
+ type: 'number',
888
+ min: 20,
889
+ max: 300,
890
+ // The bare-string form; it arrives as { type: 'slider' }.
891
+ widget: 'slider',
892
+ },
893
+ { label: 'Pause On Hover', property: 'pauseOnHover', type: 'boolean' },
894
+ ]}
895
+ speed={100}
896
+ pauseOnHover={true}
897
+ autoFill={true}
898
+ className="px-5 py-16"
899
+ items={[
900
+ {
901
+ key: 'stats-row',
902
+ children: (
903
+ <div
904
+ data-id=""
905
+ data-binding={[
906
+ {
907
+ label: 'Stats Cards',
908
+ property: 'children',
909
+ },
910
+ ]}
911
+ className={cn('flex gap-20')}
912
+ >
913
+ <div
914
+ className={cn(
915
+ 'min-w-60 text-center',
916
+ //
917
+ )}
918
+ >
919
+ <p
920
+ data-id=""
921
+ data-binding={[
922
+ {
923
+ label: 'Title',
924
+ property: 'innerText',
925
+ },
926
+ ]}
927
+ className={cn(
928
+ 'mb-2 text-5xl font-bold',
929
+ //
930
+ )}
931
+ >
932
+ Open
933
+ </p>
934
+ <p
935
+ data-id=""
936
+ data-binding={[
937
+ {
938
+ label: 'Description',
939
+ property: 'innerText',
940
+ },
941
+ ]}
942
+ className={cn(
943
+ 'text-indigo-200',
944
+ //
945
+ )}
946
+ >
947
+ Source Project
948
+ </p>
949
+ </div>
950
+ <div
951
+ className={cn(
952
+ 'min-w-60 text-center',
953
+ //
954
+ )}
955
+ >
956
+ <p
957
+ data-id=""
958
+ data-binding={[
959
+ {
960
+ label: 'Title',
961
+ property: 'innerText',
962
+ },
963
+ ]}
964
+ className={cn(
965
+ 'mb-2 text-5xl font-bold',
966
+ //
967
+ )}
968
+ >
969
+ Free
970
+ </p>
971
+ <p
972
+ data-id=""
973
+ data-binding={[
974
+ {
975
+ label: 'Description',
976
+ property: 'innerText',
977
+ },
978
+ ]}
979
+ className={cn(
980
+ 'text-indigo-200',
981
+ //
982
+ )}
983
+ >
984
+ For Everyone
985
+ </p>
986
+ </div>
987
+ <div
988
+ className={cn(
989
+ 'min-w-60 text-center',
990
+ //
991
+ )}
992
+ >
993
+ <p
994
+ data-id=""
995
+ data-binding={[
996
+ {
997
+ label: 'Title',
998
+ property: 'innerText',
999
+ },
1000
+ ]}
1001
+ className={cn(
1002
+ 'mb-2 text-5xl font-bold',
1003
+ //
1004
+ )}
1005
+ >
1006
+ Active
1007
+ </p>
1008
+ <p
1009
+ data-id=""
1010
+ data-binding={[
1011
+ {
1012
+ label: 'Description',
1013
+ property: 'innerText',
1014
+ },
1015
+ ]}
1016
+ className={cn(
1017
+ 'text-indigo-200',
1018
+ //
1019
+ )}
1020
+ >
1021
+ Community
1022
+ </p>
1023
+ </div>
1024
+ <div
1025
+ className={cn(
1026
+ 'min-w-60 text-center',
1027
+ //
1028
+ )}
1029
+ >
1030
+ <p
1031
+ data-id=""
1032
+ data-binding={[
1033
+ {
1034
+ label: 'Title',
1035
+ property: 'innerText',
1036
+ },
1037
+ ]}
1038
+ className={cn(
1039
+ 'mb-2 text-5xl font-bold',
1040
+ //
1041
+ )}
1042
+ >
1043
+ Learn
1044
+ </p>
1045
+ <p
1046
+ data-id=""
1047
+ data-binding={[
1048
+ {
1049
+ label: 'Description',
1050
+ property: 'innerText',
1051
+ },
1052
+ ]}
1053
+ className={cn(
1054
+ 'text-indigo-200',
1055
+ //
1056
+ )}
1057
+ >
1058
+ By Doing
1059
+ </p>
1060
+ </div>
1061
+ </div>
1062
+ ),
1063
+ },
1064
+ ]}
1065
+ />
1066
+ </section>
1067
+ `
1068
+ },
1069
+ {
1070
+ id: "roadmap",
1071
+ name: "Roadmap",
1072
+ code: `
1073
+ <section
1074
+ data-name="Roadmap"
1075
+ className={cn(
1076
+ 'px-5 py-16',
1077
+ //
1078
+ )}
1079
+ >
1080
+ <div
1081
+ className={cn(
1082
+ 'mx-auto max-w-3xl',
1083
+ //
1084
+ )}
1085
+ >
1086
+ <h2
1087
+ data-id=""
1088
+ data-binding={[
1089
+ { label: 'Title', property: 'innerText', required: true },
1090
+ ]}
1091
+ className={cn(
1092
+ 'mb-8 text-center text-4xl font-bold text-gray-800',
1093
+ //
1094
+ )}
1095
+ >
1096
+ Roadmap
1097
+ </h2>
1098
+ <ui.List
1099
+ data-id=""
1100
+ data-binding={[
1101
+ {
1102
+ label: 'Milestones',
1103
+ property: 'data',
1104
+ type: 'array',
1105
+ // One leaf per item key. Each leaf takes the same fields as a
1106
+ // top-level binding, and label falls back to the key.
1107
+ render: {
1108
+ title: {
1109
+ type: 'string',
1110
+ label: 'Title',
1111
+ required: true,
1112
+ },
1113
+ date: { type: 'date', label: 'Release Date', required: true },
1114
+ status: {
1115
+ type: 'string',
1116
+ label: 'Status',
1117
+ options: [
1118
+ { label: 'Planned', value: 'planned' },
1119
+ { label: 'In Progress', value: 'in-progress' },
1120
+ { label: 'Shipped', value: 'shipped' },
1121
+ ],
1122
+ },
1123
+ progress: {
1124
+ type: 'number',
1125
+ label: 'Progress',
1126
+ min: 0,
1127
+ max: 100,
1128
+ widget: { type: 'slider', step: 10, unit: '%' },
1129
+ },
1130
+ color: { type: 'color', label: 'Accent Color' },
1131
+ highlight: { type: 'boolean', label: 'Highlight' },
1132
+ // An object-valued key: its own render map types the keys
1133
+ // inside it, one level further down.
1134
+ link: {
1135
+ type: 'object',
1136
+ label: 'Link',
1137
+ render: {
1138
+ text: { type: 'string', label: 'Link Text' },
1139
+ href: {
1140
+ type: 'url',
1141
+ label: 'Link URL',
1142
+ pattern: '^https://',
1143
+ // Not a library field, so it arrives as meta.hint.
1144
+ hint: 'Must use https',
1145
+ },
1146
+ },
1147
+ },
1148
+ },
1149
+ },
1150
+ ]}
1151
+ data={[
1152
+ {
1153
+ title: 'Visual editing',
1154
+ date: '2026-03-02',
1155
+ status: 'shipped',
1156
+ progress: 100,
1157
+ color: '#22c55e',
1158
+ highlight: false,
1159
+ link: { text: 'Release notes', href: 'https://github.com' },
1160
+ },
1161
+ {
1162
+ title: 'Custom panels',
1163
+ date: '2026-09-26',
1164
+ status: 'in-progress',
1165
+ progress: 60,
1166
+ color: '#6366f1',
1167
+ highlight: true,
1168
+ link: { text: 'Guide', href: 'https://github.com' },
1169
+ },
1170
+ {
1171
+ title: 'Collaboration',
1172
+ date: '2027-01-15',
1173
+ status: 'planned',
1174
+ progress: 0,
1175
+ color: '#f59e0b',
1176
+ highlight: false,
1177
+ link: { text: 'Discussion', href: 'https://github.com' },
1178
+ },
1179
+ ]}
1180
+ itemKey={item => item.title}
1181
+ renderItem={item => (
1182
+ <div
1183
+ className={cn(
1184
+ 'flex items-center gap-4 border-b border-gray-100 py-4',
1185
+ item.highlight && 'rounded-lg bg-indigo-50 px-4',
1186
+ )}
1187
+ >
1188
+ <span
1189
+ className="size-3 shrink-0 rounded-full"
1190
+ style={{ backgroundColor: item.color }}
1191
+ />
1192
+ <div className="min-w-0 flex-1">
1193
+ <p className="font-semibold text-gray-800">{item.title}</p>
1194
+ <p className="text-sm text-gray-500">
1195
+ {item.date} · {item.status}
1196
+ </p>
1197
+ <ui.Progress value={item.progress} className="mt-2" />
1198
+ </div>
1199
+ <a
1200
+ href={item.link.href}
1201
+ className="text-sm text-indigo-500 hover:underline"
1202
+ >
1203
+ {item.link.text}
1204
+ </a>
1205
+ </div>
1206
+ )}
1207
+ />
1208
+ <p
1209
+ className={cn(
1210
+ 'mt-6 text-center text-sm text-gray-500',
1211
+ //
1212
+ )}
1213
+ >
1214
+ Last updated{' '}
1215
+ <time
1216
+ data-id=""
1217
+ data-binding={[
1218
+ {
1219
+ label: 'Updated On',
1220
+ property: 'dateTime',
1221
+ type: 'date',
1222
+ required: true,
1223
+ },
1224
+ { label: 'Updated Text', property: 'innerText' },
1225
+ ]}
1226
+ dateTime="2026-09-26"
1227
+ >
1228
+ September 26, 2026
1229
+ </time>
1230
+ {' · '}
1231
+ <a
1232
+ data-id=""
1233
+ data-binding={[
1234
+ { label: 'Link Text', property: 'innerText' },
1235
+ {
1236
+ label: 'Link URL',
1237
+ property: 'href',
1238
+ type: 'url',
1239
+ required: true,
1240
+ pattern: '^https://',
1241
+ },
1242
+ ]}
1243
+ href="https://github.com/pjb0811/live-editor/releases"
1244
+ className="text-indigo-500 hover:underline"
1245
+ >
1246
+ All releases
1247
+ </a>
1248
+ </p>
1249
+ </div>
1250
+ </section>
1251
+ `
1252
+ },
1253
+ {
1254
+ id: "faq",
1255
+ name: "FAQ",
1256
+ code: `
1257
+ <section
1258
+ data-name="FAQ"
1259
+ className={cn(
1260
+ 'px-5 py-16',
1261
+ //
1262
+ )}
1263
+ >
1264
+ <div
1265
+ className={cn(
1266
+ 'mx-auto max-w-3xl',
1267
+ //
1268
+ )}
1269
+ >
1270
+ <ui.Typography.Title
1271
+ data-id=""
1272
+ data-binding={[{ label: 'Title', property: 'innerText' }]}
1273
+ level={2}
1274
+ className={cn(
1275
+ 'mb-12 text-center text-4xl font-bold text-gray-800',
1276
+ //
1277
+ )}
1278
+ >
1279
+ Frequently Asked Questions
1280
+ </ui.Typography.Title>
1281
+ <ui.Collapse
1282
+ data-id=""
1283
+ data-binding={[{ label: 'FAQ Items', property: 'items' }]}
1284
+ items={[
1285
+ {
1286
+ key: '1',
1287
+ label: (
1288
+ <ui.Typography.Text
1289
+ data-id=""
1290
+ data-binding={[
1291
+ {
1292
+ label: 'Question 1',
1293
+ property: 'innerText',
1294
+ },
1295
+ ]}
1296
+ className={cn('text-lg font-semibold text-gray-800')}
1297
+ >
1298
+ What is Live Editor?
1299
+ </ui.Typography.Text>
1300
+ ),
1301
+ children: (
1302
+ <ui.Typography.Paragraph
1303
+ data-id=""
1304
+ data-binding={[
1305
+ {
1306
+ label: 'Answer 1',
1307
+ type: 'richtext',
1308
+ },
1309
+ ]}
1310
+ className={cn('text-gray-600')}
1311
+ >
1312
+ Live Editor is an open-source tool for building web
1313
+ interfaces visually. It helps you learn web development by
1314
+ providing instant feedback as you build components.
1315
+ </ui.Typography.Paragraph>
1316
+ ),
1317
+ },
1318
+ {
1319
+ key: '2',
1320
+ label: (
1321
+ <ui.Typography.Text
1322
+ data-id=""
1323
+ data-binding={[
1324
+ {
1325
+ label: 'Question 2',
1326
+ property: 'innerText',
1327
+ },
1328
+ ]}
1329
+ className={cn('text-lg font-semibold text-gray-800')}
1330
+ >
1331
+ Do I need coding experience?
1332
+ </ui.Typography.Text>
1333
+ ),
1334
+ children: (
1335
+ <ui.Typography.Paragraph
1336
+ data-id=""
1337
+ data-binding={[
1338
+ {
1339
+ label: 'Answer 2',
1340
+ type: 'richtext',
1341
+ },
1342
+ ]}
1343
+ className={cn('text-gray-600')}
1344
+ >
1345
+ No! Live Editor is designed for all skill levels. Beginners
1346
+ can learn through visual editing, while experienced
1347
+ developers can work directly with code.
1348
+ </ui.Typography.Paragraph>
1349
+ ),
1350
+ },
1351
+ {
1352
+ key: '3',
1353
+ label: (
1354
+ <ui.Typography.Text
1355
+ data-id=""
1356
+ data-binding={[
1357
+ {
1358
+ label: 'Question 3',
1359
+ property: 'innerText',
1360
+ },
1361
+ ]}
1362
+ className={cn('text-lg font-semibold text-gray-800')}
1363
+ >
1364
+ Is it really free?
1365
+ </ui.Typography.Text>
1366
+ ),
1367
+ children: (
1368
+ <ui.Typography.Paragraph
1369
+ data-id=""
1370
+ data-binding={[
1371
+ {
1372
+ label: 'Answer 3',
1373
+ type: 'richtext',
1374
+ },
1375
+ ]}
1376
+ className={cn('text-gray-600')}
1377
+ >
1378
+ Yes, completely free! Live Editor is an open-source project
1379
+ with no hidden costs or premium tiers. All features are
1380
+ available to everyone.
1381
+ </ui.Typography.Paragraph>
1382
+ ),
1383
+ },
1384
+ {
1385
+ key: '4',
1386
+ label: (
1387
+ <ui.Typography.Text
1388
+ data-id=""
1389
+ data-binding={[
1390
+ {
1391
+ label: 'Question 4',
1392
+ property: 'innerText',
1393
+ },
1394
+ ]}
1395
+ className={cn('text-lg font-semibold text-gray-800')}
1396
+ >
1397
+ How can I contribute?
1398
+ </ui.Typography.Text>
1399
+ ),
1400
+ children: (
1401
+ <ui.Typography.Paragraph
1402
+ data-id=""
1403
+ data-binding={[
1404
+ {
1405
+ label: 'Answer 4',
1406
+ type: 'richtext',
1407
+ },
1408
+ ]}
1409
+ className={cn('text-gray-600')}
1410
+ >
1411
+ You can contribute by reporting bugs, suggesting features,
1412
+ sharing your projects, or contributing code. Check our
1413
+ GitHub repository for more details.
1414
+ </ui.Typography.Paragraph>
1415
+ ),
1416
+ },
1417
+ ]}
1418
+ />
1419
+ </div>
1420
+ </section>
1421
+ `
1422
+ }
1423
+ ];
1424
+ //#endregion
1425
+ //#region src/components/dnd/canvas/canvas-notice.tsx
1426
+ const CanvasNotice = ({ title, detail }) => /* @__PURE__ */ jsxs(Space, {
1427
+ orientation: "vertical",
1428
+ align: "center",
1429
+ children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: title }), /* @__PURE__ */ jsx(Typography.Text, { children: detail })]
1430
+ });
1431
+ //#endregion
1432
+ //#region src/components/dnd/palette/palette-drag.ts
1433
+ const PALETTE_SECTION = "palette-section";
1434
+ const paletteDragData = (section) => ({
1435
+ type: PALETTE_SECTION,
1436
+ section
1437
+ });
1438
+ const paletteSectionOf = (data) => {
1439
+ const current = data?.current;
1440
+ return current?.type === PALETTE_SECTION ? current.section : void 0;
1441
+ };
1442
+ //#endregion
1443
+ //#region src/components/dnd/canvas/droppable.tsx
1444
+ const Droppable = ({ children, className }) => {
1445
+ const { setNodeRef, isOver, active } = useDroppable({ id: "sortable-area" });
1446
+ const { setNodeRef: setBottomRef, isOver: isBottomOver } = useDroppable({ id: "sortable-area-bottom" });
1447
+ const isPaletteSectionDragging = !!paletteSectionOf(active?.data);
1448
+ const shouldHighlight = isOver && isPaletteSectionDragging;
1449
+ const shouldHighlightBottom = isBottomOver && isPaletteSectionDragging;
1450
+ const messages = useLiveMessages();
1451
+ return /* @__PURE__ */ jsxs("div", {
1452
+ ref: setNodeRef,
1453
+ className: cn("min-h-full", "border-2 border-dashed p-1", shouldHighlight ? "border-blue-300 bg-blue-50" : "border-gray-200", className),
1454
+ children: [children, isPaletteSectionDragging && /* @__PURE__ */ jsx("div", {
1455
+ ref: setBottomRef,
1456
+ className: cn("mt-2 min-h-24", "rounded-lg border-2 border-dashed", "transition-all duration-200", "flex items-center justify-center", shouldHighlightBottom ? "border-blue-400 bg-blue-100" : "border-gray-300 bg-gray-50"),
1457
+ children: /* @__PURE__ */ jsx(Typography.Text, {
1458
+ className: "text-sm text-gray-500",
1459
+ children: shouldHighlightBottom ? messages.canvas.dropHere : messages.canvas.dropAtBottom
1460
+ })
1461
+ })]
1462
+ });
1463
+ };
1464
+ //#endregion
1465
+ //#region src/components/dnd/canvas/inspector-highlight.tsx
1466
+ const InspectorHighlight = ({ rect }) => {
1467
+ const ref = useRef(null);
1468
+ useLayoutEffect(() => {
1469
+ const box = ref.current;
1470
+ const container = box?.closest("[data-frame-container]");
1471
+ if (!box || !rect || !container) return;
1472
+ const outer = container.getBoundingClientRect();
1473
+ box.style.left = `${rect.left - outer.left - container.clientLeft + container.scrollLeft}px`;
1474
+ box.style.top = `${rect.top - outer.top - container.clientTop + container.scrollTop}px`;
1475
+ box.style.width = `${rect.width}px`;
1476
+ box.style.height = `${rect.height}px`;
1477
+ }, [rect]);
1478
+ return rect ? /* @__PURE__ */ jsx("div", {
1479
+ ref,
1480
+ "aria-hidden": true,
1481
+ "data-dnd-inspector-highlight": true,
1482
+ style: {
1483
+ position: "absolute",
1484
+ pointerEvents: "none",
1485
+ outline: "2px solid #3b82f6",
1486
+ outlineOffset: -1,
1487
+ background: "rgba(59, 130, 246, 0.12)",
1488
+ zIndex: 65
1489
+ }
1490
+ }) : null;
1491
+ };
1492
+ //#endregion
1493
+ //#region src/components/dnd/palette/draggable.tsx
1494
+ const DraggableItem = ({ item, children }) => {
1495
+ const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
1496
+ id: item.id,
1497
+ data: paletteDragData(item)
1498
+ });
1499
+ return children({
1500
+ ref: setNodeRef,
1501
+ dragProps: {
1502
+ ...listeners,
1503
+ ...attributes
1504
+ },
1505
+ isDragging
1506
+ });
1507
+ };
1508
+ const DefaultDraggableItem = ({ item, onAdd, tapToAdd = false }) => /* @__PURE__ */ jsx(DraggableItem, {
1509
+ item,
1510
+ children: ({ ref, dragProps, isDragging }) => /* @__PURE__ */ jsx(Card, {
1511
+ ref,
1512
+ style: { opacity: isDragging ? .5 : 1 },
1513
+ ...dragProps,
1514
+ className: cn("cursor-grab", "outline-none focus-visible:ring-2 focus-visible:ring-blue-500", "hover:border-blue-300 hover:shadow-md", isDragging && "opacity-50"),
1515
+ onClick: tapToAdd && onAdd ? () => onAdd(item) : void 0,
1516
+ onDoubleClick: onAdd ? () => onAdd(item) : void 0,
1517
+ onKeyDown: (event) => {
1518
+ dragProps.onKeyDown?.(event);
1519
+ if (onAdd && event.key === "Enter" && event.target === event.currentTarget && !event.defaultPrevented) {
1520
+ event.preventDefault();
1521
+ onAdd(item);
1522
+ }
1523
+ },
1524
+ children: item.name
1525
+ })
1526
+ });
1527
+ //#endregion
1528
+ //#region src/components/dnd/canvas/section-fallback-context.ts
1529
+ const SectionFallbackContext = createContext(void 0);
1530
+ //#endregion
1531
+ //#region src/components/dnd/canvas/section-fallback.tsx
1532
+ const ConsumerFallback = ({ render, args, builtin }) => {
1533
+ const node = render(args);
1534
+ return node === void 0 ? builtin : /* @__PURE__ */ jsx(Fragment, { children: node });
1535
+ };
1536
+ const SectionFallback = ({ args, builtin }) => {
1537
+ const render = useContext(SectionFallbackContext);
1538
+ if (!render) return builtin;
1539
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
1540
+ fallback: () => builtin,
1541
+ children: /* @__PURE__ */ jsx(ConsumerFallback, {
1542
+ render,
1543
+ args,
1544
+ builtin
1545
+ })
1546
+ });
1547
+ };
1548
+ //#endregion
1549
+ //#region src/components/dnd/canvas/renderer.tsx
1550
+ const Renderer = ({ preview, sectionId = "", sectionName = "", sectionCode = preview, forceFallback = false, headers, modules, frame, dynamicTailwind = false, provider }) => {
1551
+ const module = useCompiledModule(forceFallback ? "" : preview, modules);
1552
+ const section = {
1553
+ id: sectionId,
1554
+ name: sectionName,
1555
+ code: sectionCode
1556
+ };
1557
+ const messages = useLiveMessages();
1558
+ const { ref: wrapperRef, css: dynamicCSS } = useDynamicTailwind(preview, dynamicTailwind && !forceFallback);
1559
+ const renderProvider = (component) => {
1560
+ return provider ? provider(component) : component;
1561
+ };
1562
+ if (forceFallback) return /* @__PURE__ */ jsx(SectionFallback, {
1563
+ args: {
1564
+ section,
1565
+ reason: "forced"
1566
+ },
1567
+ builtin: /* @__PURE__ */ jsx(Error$1, {
1568
+ message: messages.errors.sectionNotRendered,
1569
+ title: messages.errors.sectionUnavailable
1570
+ })
1571
+ });
1572
+ if (module?.error) return /* @__PURE__ */ jsx(SectionFallback, {
1573
+ args: {
1574
+ section,
1575
+ reason: "compile",
1576
+ message: module.error
1577
+ },
1578
+ builtin: /* @__PURE__ */ jsx(Error$1, {
1579
+ message: module.error,
1580
+ title: messages.errors.compile
1581
+ })
1582
+ });
1583
+ const Component = module?.exports?.default;
1584
+ if (!Component) return null;
1585
+ return /* @__PURE__ */ jsx(Frame, {
1586
+ ...frame,
1587
+ autoHeight: true,
1588
+ children: (container) => /* @__PURE__ */ jsx("div", {
1589
+ ref: wrapperRef,
1590
+ className: "w-full overflow-x-hidden",
1591
+ "data-editor-mode": true,
1592
+ children: /* @__PURE__ */ jsx(ErrorBoundary, {
1593
+ resetKeys: [preview],
1594
+ fallback: (message, reset) => /* @__PURE__ */ jsx(SectionFallback, {
1595
+ args: {
1596
+ section,
1597
+ reason: "runtime",
1598
+ message
1599
+ },
1600
+ builtin: /* @__PURE__ */ jsx(Error$1, {
1601
+ message,
1602
+ onReset: reset,
1603
+ title: messages.errors.rendering
1604
+ })
1605
+ }),
1606
+ children: renderProvider(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Component, {
1607
+ headers,
1608
+ container
1609
+ }), dynamicTailwind && dynamicCSS && /* @__PURE__ */ jsx("style", { children: dynamicCSS })] }))
1610
+ })
1611
+ })
1612
+ });
1613
+ };
1614
+ const sameValue = (a, b) => {
1615
+ if (Object.is(a, b)) return true;
1616
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, i) => Object.is(item, b[i]));
1617
+ if (a && b && typeof a === "object" && typeof b === "object" && Object.getPrototypeOf(a) === Object.prototype && Object.getPrototypeOf(b) === Object.prototype) {
1618
+ const aKeys = Object.keys(a);
1619
+ return aKeys.length === Object.keys(b).length && aKeys.every((key) => Object.is(a[key], b[key]));
1620
+ }
1621
+ return false;
1622
+ };
1623
+ const sameFrame = (a, b) => {
1624
+ if (a === b) return true;
1625
+ if (!a || !b) return false;
1626
+ return [.../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])].every((key) => sameValue(a[key], b[key]));
1627
+ };
1628
+ const arePropsEqual = (prev, next) => {
1629
+ return [.../* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)])].every((key) => key === "frame" ? sameFrame(prev.frame, next.frame) : Object.is(prev[key], next[key]));
1630
+ };
1631
+ var renderer_default = memo(Renderer, arePropsEqual);
1632
+ //#endregion
1633
+ //#region src/components/dnd/canvas/section-handlers.ts
1634
+ const SectionHandlersContext = createContext(null);
1635
+ const useSectionHandlers = () => useContext(SectionHandlersContext);
1636
+ //#endregion
1637
+ //#region src/components/dnd/canvas/sortable.tsx
1638
+ const NAVIGATION_KEYS = {
1639
+ ArrowUp: "previous",
1640
+ ArrowDown: "next",
1641
+ Home: "first",
1642
+ End: "last"
1643
+ };
1644
+ const Sortable = ({ id, children, selected, onClick, onDelete, onCopy, onMoveUp, onMoveDown, canMoveUp = false, canMoveDown = false }) => {
1645
+ const handlers = useSectionHandlers();
1646
+ const inspecting = handlers?.inspecting ?? false;
1647
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging, isOver, active } = useSortable({
1648
+ id,
1649
+ disabled: inspecting
1650
+ });
1651
+ const messages = useLiveMessages();
1652
+ const style = {
1653
+ transform: CSS$1.Transform.toString(transform),
1654
+ transition,
1655
+ opacity: isDragging ? .5 : 1,
1656
+ cursor: inspecting ? "crosshair" : "grab"
1657
+ };
1658
+ const isPaletteSectionOver = isOver && !!paletteSectionOf(active?.data);
1659
+ const onKeyDown = (e) => {
1660
+ listeners?.onKeyDown?.(e);
1661
+ if (e.target !== e.currentTarget || active || e.defaultPrevented) return;
1662
+ if (e.key === "Enter") {
1663
+ e.preventDefault();
1664
+ onClick?.();
1665
+ return;
1666
+ }
1667
+ const to = NAVIGATION_KEYS[e.key];
1668
+ if (to && handlers) {
1669
+ e.preventDefault();
1670
+ handlers.onNavigate(id, to);
1671
+ return;
1672
+ }
1673
+ if ((e.key === "Delete" || e.key === "Backspace") && handlers) {
1674
+ e.preventDefault();
1675
+ handlers.onDeleteKey(id);
1676
+ }
1677
+ };
1678
+ const setRefs = (node) => {
1679
+ setNodeRef(node);
1680
+ setActivatorNodeRef(node);
1681
+ handlers?.nodeRef(id)(node);
1682
+ };
1683
+ const press = (action) => (e) => {
1684
+ e.stopPropagation();
1685
+ action?.();
1686
+ };
1687
+ return /* @__PURE__ */ jsxs("div", {
1688
+ ref: setRefs,
1689
+ style,
1690
+ ...attributes,
1691
+ ...listeners,
1692
+ onKeyDown,
1693
+ onClick,
1694
+ className: cn("relative", selected && "z-10 outline-2 outline-offset-2 outline-blue-500", isPaletteSectionOver && "border-t-4 border-t-green-500"),
1695
+ children: [
1696
+ /* @__PURE__ */ jsx("div", {
1697
+ className: cn("absolute inset-0 z-50"),
1698
+ onPointerMove: inspecting ? (e) => handlers?.onInspectMove(e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY) : void 0,
1699
+ onPointerLeave: inspecting ? handlers?.onInspectLeave : void 0,
1700
+ onClick: inspecting ? (e) => {
1701
+ e.stopPropagation();
1702
+ handlers?.onInspectPick(id, e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY);
1703
+ } : void 0
1704
+ }),
1705
+ children,
1706
+ selected && /* @__PURE__ */ jsxs(Space, {
1707
+ className: cn("absolute top-1 right-1 z-60"),
1708
+ children: [
1709
+ onMoveUp && /* @__PURE__ */ jsx(Button, {
1710
+ icon: /* @__PURE__ */ jsx(ChevronUp, {}),
1711
+ disabled: !canMoveUp,
1712
+ onClick: press(onMoveUp),
1713
+ "aria-label": messages.section.moveUp
1714
+ }),
1715
+ onMoveDown && /* @__PURE__ */ jsx(Button, {
1716
+ icon: /* @__PURE__ */ jsx(ChevronDown, {}),
1717
+ disabled: !canMoveDown,
1718
+ onClick: press(onMoveDown),
1719
+ "aria-label": messages.section.moveDown
1720
+ }),
1721
+ /* @__PURE__ */ jsx(Button, {
1722
+ icon: /* @__PURE__ */ jsx(Copy, {}),
1723
+ onClick: press(() => onCopy?.(id)),
1724
+ "aria-label": messages.section.duplicate
1725
+ }),
1726
+ /* @__PURE__ */ jsx(Button, {
1727
+ danger: true,
1728
+ icon: /* @__PURE__ */ jsx(Trash, {}),
1729
+ onClick: press(() => onDelete?.(id)),
1730
+ "aria-label": messages.section.delete
1731
+ })
1732
+ ]
1733
+ })
1734
+ ]
1735
+ });
1736
+ };
1737
+ //#endregion
1738
+ //#region src/components/dnd/canvas/overlay.tsx
1739
+ const Overlay = ({ sections, isForced, renderProps }) => {
1740
+ const { active } = useDndContext();
1741
+ if (!active) return null;
1742
+ const paletteSection = paletteSectionOf(active.data);
1743
+ if (paletteSection) return /* @__PURE__ */ jsx(DefaultDraggableItem, { item: paletteSection });
1744
+ const section = sections.find((s) => s.id === active.id);
1745
+ if (section) {
1746
+ const preview = generateSection(section.code, renderProps.fullCode, { containerId: renderProps.containerId });
1747
+ return /* @__PURE__ */ jsx(Sortable, {
1748
+ id: section.id,
1749
+ name: section.name,
1750
+ children: /* @__PURE__ */ jsx(renderer_default, {
1751
+ preview,
1752
+ sectionId: section.id,
1753
+ sectionName: section.name,
1754
+ sectionCode: section.code,
1755
+ forceFallback: isForced?.(section) ?? false,
1756
+ modules: renderProps.modules,
1757
+ frame: renderProps.frame,
1758
+ dynamicTailwind: renderProps.dynamicTailwind
1759
+ })
1760
+ });
1761
+ }
1762
+ return null;
1763
+ };
1764
+ //#endregion
1765
+ //#region src/components/dnd/edit-options.ts
1766
+ const toastEditError = (error) => {
1767
+ Toast.error(error.title, error.description ? { description: error.description } : void 0);
1768
+ };
1769
+ const DndEditOptionsContext = createContext({ reportError: toastEditError });
1770
+ const useDndEditOptions = () => useContext(DndEditOptionsContext);
1771
+ //#endregion
1772
+ //#region src/components/dnd/inspector.ts
1773
+ const DndInspectorContext = createContext(null);
1774
+ const useDndInspector = () => {
1775
+ const inspector = useContext(DndInspectorContext);
1776
+ if (!inspector) throw new Error("useDndInspector() must be used inside <Live.Dnd>.");
1777
+ return inspector;
1778
+ };
1779
+ const outside = (root) => (element) => element.getRootNode() !== root;
1780
+ const deepestAt = (root, x, y, skip) => {
1781
+ const element = root.elementsFromPoint(x, y).find((candidate) => !skip(candidate));
1782
+ if (!element) return null;
1783
+ if (element instanceof HTMLIFrameElement) {
1784
+ const document = element.contentDocument;
1785
+ if (!document) return element;
1786
+ const rect = element.getBoundingClientRect();
1787
+ return deepestAt(document, x - rect.left, y - rect.top, outside(document)) ?? element;
1788
+ }
1789
+ if (element.shadowRoot && element.shadowRoot !== root) return deepestAt(element.shadowRoot, x, y, outside(element.shadowRoot)) ?? element;
1790
+ return element;
1791
+ };
1792
+ const hasId = (element) => Boolean(element.getAttribute(DATA_ATTR.ID));
1793
+ const pickElement = (section, overlay, x, y) => {
1794
+ let element = deepestAt(section.ownerDocument, x, y, (candidate) => candidate === overlay || candidate === section || !section.contains(candidate));
1795
+ while (element && element !== section && !hasId(element)) element = element.parentElement ?? element.parentNode?.host ?? null;
1796
+ return element === section ? null : element;
1797
+ };
1798
+ const findElementById = (section, id) => {
1799
+ const selector = `[${DATA_ATTR.ID}="${CSS.escape(id)}"]`;
1800
+ const roots = [section];
1801
+ for (const root of roots) {
1802
+ const found = root.querySelector(selector);
1803
+ if (found) return found;
1804
+ root.querySelectorAll("iframe").forEach((frame) => {
1805
+ if (frame.contentDocument) roots.push(frame.contentDocument);
1806
+ });
1807
+ root.querySelectorAll("*").forEach((element) => {
1808
+ if (element.shadowRoot) roots.push(element.shadowRoot);
1809
+ });
1810
+ }
1811
+ return null;
1812
+ };
1813
+ const viewportRect = (element) => {
1814
+ const rect = element.getBoundingClientRect();
1815
+ let x = rect.left;
1816
+ let y = rect.top;
1817
+ let frame = element.ownerDocument.defaultView?.frameElement ?? null;
1818
+ while (frame) {
1819
+ const frameRect = frame.getBoundingClientRect();
1820
+ x += frameRect.left;
1821
+ y += frameRect.top;
1822
+ frame = frame.ownerDocument.defaultView?.frameElement ?? null;
1823
+ }
1824
+ return new DOMRect(x, y, rect.width, rect.height);
1825
+ };
1826
+ //#endregion
1827
+ //#region src/components/dnd/layout-context.ts
1828
+ const DndRegionContext = createContext(null);
1829
+ const useDndRegions = (subject) => {
1830
+ const regions = useContext(DndRegionContext);
1831
+ if (!regions) throw new Error(`${subject} must be used inside <Live.Dnd>.`);
1832
+ return regions;
1833
+ };
1834
+ const useDndPalette = () => useDndRegions("useDndPalette()").palette;
1835
+ const useDndPanel = () => useDndRegions("useDndPanel()").panel;
1836
+ const useDndLayout = () => useDndRegions("useDndLayout()");
1837
+ //#endregion
1838
+ //#region src/components/dnd/panel/element-selection.ts
1839
+ const ElementSelectionContext = createContext({ ids: /* @__PURE__ */ new Set() });
1840
+ //#endregion
1841
+ //#region src/components/dnd/panel-binding.ts
1842
+ const readAttribute = (node, name) => node.dataAttributes.find((attribute) => attribute.name === name)?.value;
1843
+ const canEditBindingValue = (node, binding) => {
1844
+ if (RESERVED_BINDING_PROPERTIES.includes(binding.property)) return false;
1845
+ if (binding.property === "children" || binding.property === "innerText" || binding.property === "innerHTML" || binding.property === "items" || binding.property === "data" || binding.type === "array" || binding.type === "jsx" || binding.type === "richtext") return true;
1846
+ const attribute = node.attributes.find((candidate) => candidate.name === binding.property);
1847
+ if (!attribute) return true;
1848
+ return attribute.isStringLiteral === true || canLosslesslyEvaluateSource(attribute?.value ?? "");
1849
+ };
1850
+ const toBindingFields = (binding) => ({
1851
+ label: binding.label,
1852
+ property: binding.property,
1853
+ type: binding.type,
1854
+ widget: binding.widget,
1855
+ options: binding.options,
1856
+ render: binding.render,
1857
+ min: binding.min,
1858
+ max: binding.max,
1859
+ pattern: binding.pattern,
1860
+ patternMessage: binding.patternMessage,
1861
+ required: binding.required,
1862
+ minLength: binding.minLength,
1863
+ maxLength: binding.maxLength,
1864
+ step: binding.step,
1865
+ minItems: binding.minItems,
1866
+ maxItems: binding.maxItems,
1867
+ minDate: binding.minDate,
1868
+ maxDate: binding.maxDate,
1869
+ protocols: binding.protocols,
1870
+ meta: binding.meta
1871
+ });
1872
+ const resolveRenderEntry = (render, key) => {
1873
+ const entry = render?.[key];
1874
+ if (entry && "type" in entry) {
1875
+ const leaf = entry;
1876
+ return {
1877
+ ...leaf,
1878
+ label: leaf.label ?? key,
1879
+ property: leaf.property ?? key
1880
+ };
1881
+ }
1882
+ return {
1883
+ label: key,
1884
+ property: key,
1885
+ render: entry
1886
+ };
1887
+ };
1888
+ const CONTENT_PROPERTIES = /* @__PURE__ */ new Set([
1889
+ "innerText",
1890
+ "innerHTML",
1891
+ "children"
1892
+ ]);
1893
+ const isAttributeAbsent = (node, binding) => !CONTENT_PROPERTIES.has(binding.property) && !node.attributes.some((candidate) => candidate.name === binding.property);
1894
+ const toPanelBindingData = (node, id, binding) => ({
1895
+ ...toBindingFields(binding),
1896
+ id,
1897
+ value: getStructuredValue(node, binding.property, binding.type),
1898
+ rawValue: getCurrentValue(node, binding.property),
1899
+ ...!canEditBindingValue(node, binding) && { canEditValue: false },
1900
+ ...isAttributeAbsent(node, binding) && { present: false },
1901
+ element: {
1902
+ tagName: node.tagName || "element",
1903
+ text: node.textContent.replace(/\s+/g, " ").trim()
1904
+ }
1905
+ });
1906
+ const resolvePanelBindings = (node) => {
1907
+ const id = readAttribute(node, "data-id");
1908
+ if (!id) return null;
1909
+ const parsed = readNodeBindings(node);
1910
+ if (!parsed.length) return null;
1911
+ return {
1912
+ id,
1913
+ tagName: node.tagName || "element",
1914
+ bindings: parsed.map((binding) => toPanelBindingData(node, id, binding))
1915
+ };
1916
+ };
1917
+ const withPanelCommit = (bindings, commit) => bindings.map((binding) => ({
1918
+ ...binding,
1919
+ onChange: (value) => commit?.({
1920
+ id: binding.id,
1921
+ label: binding.label,
1922
+ property: binding.property,
1923
+ value
1924
+ })
1925
+ }));
1926
+ //#endregion
1927
+ //#region src/components/dnd/panel/row-controls.tsx
1928
+ const RowControls = ({ index, count, labels, disabled = false, deleteDisabled = false, onMove, onDelete }) => /* @__PURE__ */ jsxs("div", {
1929
+ className: "flex space-x-1",
1930
+ children: [
1931
+ /* @__PURE__ */ jsx(Button, {
1932
+ size: "small",
1933
+ icon: /* @__PURE__ */ jsx(ArrowUp, {}),
1934
+ "aria-label": labels.moveUp,
1935
+ disabled: disabled || index === 0,
1936
+ onClick: () => onMove(index - 1)
1937
+ }),
1938
+ /* @__PURE__ */ jsx(Button, {
1939
+ size: "small",
1940
+ icon: /* @__PURE__ */ jsx(ArrowDown, {}),
1941
+ "aria-label": labels.moveDown,
1942
+ disabled: disabled || index === count - 1,
1943
+ onClick: () => onMove(index + 1)
1944
+ }),
1945
+ /* @__PURE__ */ jsx(Button, {
1946
+ danger: true,
1947
+ size: "small",
1948
+ icon: /* @__PURE__ */ jsx(X, {}),
1949
+ "aria-label": labels.delete,
1950
+ title: labels.delete,
1951
+ disabled: disabled || deleteDisabled,
1952
+ onClick: onDelete
1953
+ })
1954
+ ]
1955
+ });
1956
+ //#endregion
1957
+ //#region src/components/dnd/panel/selection/bulk-actions-bar.tsx
1958
+ const BulkActionsBar = ({ count, disabled = false, duplicateDisabled = false, deleteDisabled = false, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
1959
+ const messages = useLiveMessages();
1960
+ if (count === 0) return null;
1961
+ return /* @__PURE__ */ jsxs("div", {
1962
+ className: "flex items-center justify-between rounded border\n border-blue-200 bg-blue-50 p-2",
1963
+ children: [/* @__PURE__ */ jsx("div", {
1964
+ className: "text-xs font-medium text-blue-700",
1965
+ children: messages.selection.count(count)
1966
+ }), /* @__PURE__ */ jsxs("div", {
1967
+ className: "flex items-center space-x-1",
1968
+ children: [
1969
+ /* @__PURE__ */ jsx(Button, {
1970
+ size: "small",
1971
+ icon: /* @__PURE__ */ jsx(Copy, {}),
1972
+ title: messages.selection.duplicate,
1973
+ disabled: disabled || duplicateDisabled,
1974
+ onClick: onDuplicate
1975
+ }),
1976
+ /* @__PURE__ */ jsx(Button, {
1977
+ size: "small",
1978
+ icon: /* @__PURE__ */ jsx(ArrowUp, {}),
1979
+ title: messages.selection.moveUp,
1980
+ disabled,
1981
+ onClick: onMoveUp
1982
+ }),
1983
+ /* @__PURE__ */ jsx(Button, {
1984
+ size: "small",
1985
+ icon: /* @__PURE__ */ jsx(ArrowDown, {}),
1986
+ title: messages.selection.moveDown,
1987
+ disabled,
1988
+ onClick: onMoveDown
1989
+ }),
1990
+ /* @__PURE__ */ jsx(Button, {
1991
+ danger: true,
1992
+ size: "small",
1993
+ icon: /* @__PURE__ */ jsx(X, {}),
1994
+ title: messages.selection.delete,
1995
+ disabled: disabled || deleteDisabled,
1996
+ onClick: onDelete
1997
+ }),
1998
+ /* @__PURE__ */ jsx(Button, {
1999
+ size: "small",
2000
+ onClick: onClear,
2001
+ children: messages.selection.clear
2002
+ })
2003
+ ]
2004
+ })]
2005
+ });
2006
+ };
2007
+ //#endregion
2008
+ //#region src/components/dnd/panel/selection/select-all-checkbox.tsx
2009
+ const SelectAllCheckbox = ({ total, selected, disabled = false, onSelectAll, onClear }) => {
2010
+ const messages = useLiveMessages();
2011
+ const isAllSelected = total > 0 && selected >= total;
2012
+ return /* @__PURE__ */ jsx(Checkbox, {
2013
+ className: "text-xs",
2014
+ checked: isAllSelected,
2015
+ indeterminate: selected > 0 && !isAllSelected,
2016
+ disabled,
2017
+ onChange: () => isAllSelected ? onClear() : onSelectAll(),
2018
+ children: messages.selection.selectAll
2019
+ });
2020
+ };
2021
+ //#endregion
2022
+ //#region src/components/dnd/panel/selection/selection-checkbox.tsx
2023
+ const SelectionCheckbox = ({ checked, disabled = false, onToggle }) => /* @__PURE__ */ jsx("div", {
2024
+ onClick: (e) => {
2025
+ if (!disabled) onToggle(e.shiftKey);
2026
+ },
2027
+ className: "inline-flex",
2028
+ children: /* @__PURE__ */ jsx(Checkbox, {
2029
+ checked,
2030
+ disabled
2031
+ })
2032
+ });
2033
+ //#endregion
2034
+ //#region src/components/dnd/panel/selection/use-structural-selection.ts
2035
+ const selectionAfter = (command, selected, count) => {
2036
+ switch (command.type) {
2037
+ case "append":
2038
+ case "duplicate": return new Set(selected);
2039
+ case "move-selected": return moveSelectedIndices(Array.from({ length: count }, (_, index) => index), new Set(command.indices), command.direction).indices;
2040
+ default: return /* @__PURE__ */ new Set();
2041
+ }
2042
+ };
2043
+ const useStructuralSelection = (count, revision) => {
2044
+ const selection = useMultiSelect(count);
2045
+ const [seen, setSeen] = useState(revision);
2046
+ const [pending, setPending] = useState(null);
2047
+ if (revision !== seen) {
2048
+ setSeen(revision);
2049
+ setPending(null);
2050
+ if (pending?.matches(revision) && pending.selection.size > 0) selection.replace(pending.selection);
2051
+ else selection.clear();
2052
+ }
2053
+ const record = (next, matches, { now = false } = {}) => {
2054
+ setPending({
2055
+ matches,
2056
+ selection: next
2057
+ });
2058
+ if (!now) return;
2059
+ if (next.size > 0) selection.replace(next);
2060
+ else selection.clear();
2061
+ };
2062
+ return {
2063
+ selection,
2064
+ record
2065
+ };
2066
+ };
2067
+ //#endregion
2068
+ //#region src/components/dnd/panel/children/use-dnd-children.ts
2069
+ const producedBy = (expected, action) => {
2070
+ if (action.type === "move-selected") {
2071
+ const moved = JSON.stringify(moveSelectedIndices(expected, new Set(action.indices), action.direction).items);
2072
+ return (revision) => revision === moved;
2073
+ }
2074
+ const added = action.type === "append" ? 1 : action.type === "duplicate" ? action.indices.length : 0;
2075
+ return (revision) => {
2076
+ const next = JSON.parse(revision);
2077
+ return next.length === expected.length + added && expected.every((signature, index) => next[index] === signature);
2078
+ };
2079
+ };
2080
+ const useDndChildren = (items, { onChange } = {}) => {
2081
+ const expected = useMemo(() => getChildrenSignatures(items), [items]);
2082
+ const revision = JSON.stringify(expected);
2083
+ const { selection, record } = useStructuralSelection(items.length, revision);
2084
+ const commit = (action) => {
2085
+ const edit = {
2086
+ kind: "children-edit",
2087
+ expected,
2088
+ action
2089
+ };
2090
+ const next = selectionAfter(action, selection.selected, items.length);
2091
+ if (next.size > 0) record(next, producedBy(expected, action));
2092
+ onChange?.(JSON.stringify(edit));
2093
+ };
2094
+ return {
2095
+ items,
2096
+ selection,
2097
+ actions: {
2098
+ move: (from, to) => commit({
2099
+ type: "move",
2100
+ from,
2101
+ to
2102
+ }),
2103
+ remove: (index) => commit({
2104
+ type: "remove",
2105
+ indices: [index]
2106
+ }),
2107
+ add: () => commit({ type: "append" }),
2108
+ duplicateSelected: () => commit({
2109
+ type: "duplicate",
2110
+ indices: [...selection.selected]
2111
+ }),
2112
+ moveSelected: (direction) => commit({
2113
+ type: "move-selected",
2114
+ indices: [...selection.selected],
2115
+ direction
2116
+ }),
2117
+ removeSelected: () => commit({
2118
+ type: "remove",
2119
+ indices: [...selection.selected]
2120
+ })
2121
+ }
2122
+ };
2123
+ };
2124
+ //#endregion
2125
+ //#region src/components/dnd/panel/children/children.tsx
2126
+ const bindingsOf = (node, onChange) => {
2127
+ const source = resolvePanelBindings(node);
2128
+ return source ? withPanelCommit(source.bindings, onChange) : [];
2129
+ };
2130
+ const Children$1 = ({ value, onChange, onNodeChange }) => {
2131
+ const items = useMemo(() => Array.isArray(value) ? value : [], [value]);
2132
+ const { selection, actions } = useDndChildren(items, { onChange });
2133
+ const messages = useLiveMessages();
2134
+ const editableChildrenMap = useMemo(() => {
2135
+ const map = /* @__PURE__ */ new Map();
2136
+ items.forEach((item, index) => {
2137
+ const editableNodes = findEditableChildren(item);
2138
+ if (editableNodes.length > 0) map.set(index, editableNodes);
2139
+ });
2140
+ return map;
2141
+ }, [items]);
2142
+ return /* @__PURE__ */ jsxs("div", {
2143
+ className: "space-y-3",
2144
+ children: [
2145
+ /* @__PURE__ */ jsxs("div", {
2146
+ className: "flex items-center justify-between",
2147
+ children: [/* @__PURE__ */ jsx("div", {
2148
+ className: "text-sm font-semibold text-green-700",
2149
+ children: messages.children.heading(items.length)
2150
+ }), /* @__PURE__ */ jsx(Button, {
2151
+ size: "small",
2152
+ color: "green",
2153
+ icon: /* @__PURE__ */ jsx(Plus, {}),
2154
+ onClick: actions.add,
2155
+ children: messages.children.add
2156
+ })]
2157
+ }),
2158
+ items.length > 0 && /* @__PURE__ */ jsx(SelectAllCheckbox, {
2159
+ total: items.length,
2160
+ selected: selection.selected.size,
2161
+ onSelectAll: () => selection.replace(new Set(items.map((_, index) => index))),
2162
+ onClear: selection.clear
2163
+ }),
2164
+ /* @__PURE__ */ jsx(BulkActionsBar, {
2165
+ count: selection.selected.size,
2166
+ onDuplicate: actions.duplicateSelected,
2167
+ onMoveUp: () => actions.moveSelected("up"),
2168
+ onMoveDown: () => actions.moveSelected("down"),
2169
+ onDelete: actions.removeSelected,
2170
+ onClear: selection.clear
2171
+ }),
2172
+ items.map((item, itemIndex) => /* @__PURE__ */ jsxs("div", {
2173
+ className: "space-y-2 rounded border border-green-200 bg-green-50 p-3",
2174
+ children: [
2175
+ /* @__PURE__ */ jsxs("div", {
2176
+ className: "flex items-center justify-between",
2177
+ children: [/* @__PURE__ */ jsxs("div", {
2178
+ className: "flex items-center space-x-2",
2179
+ children: [/* @__PURE__ */ jsx(SelectionCheckbox, {
2180
+ checked: selection.isSelected(itemIndex),
2181
+ onToggle: (shiftKey) => selection.toggle(itemIndex, shiftKey)
2182
+ }), /* @__PURE__ */ jsx("div", {
2183
+ className: "text-xs font-medium text-green-800",
2184
+ children: messages.children.child(itemIndex + 1, item.tagName || "fragment")
2185
+ })]
2186
+ }), /* @__PURE__ */ jsx(RowControls, {
2187
+ index: itemIndex,
2188
+ count: items.length,
2189
+ labels: messages.children,
2190
+ onMove: (toIndex) => actions.move(itemIndex, toIndex),
2191
+ onDelete: () => actions.remove(itemIndex)
2192
+ })]
2193
+ }),
2194
+ editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
2195
+ className: "space-y-2",
2196
+ children: [/* @__PURE__ */ jsx("div", {
2197
+ className: "text-xs font-medium text-green-700",
2198
+ children: messages.children.editableBindings
2199
+ }), editableChildrenMap.get(itemIndex).map((editableNode, idx) => {
2200
+ const nodeId = editableNode.dataAttributes.find((a) => a.name === "data-id")?.value;
2201
+ return /* @__PURE__ */ jsx(ElementFields, {
2202
+ id: nodeId ?? void 0,
2203
+ className: "rounded border border-green-100 bg-white p-2\n data-[picked]:ring-2 data-[picked]:ring-blue-400",
2204
+ header: /* @__PURE__ */ jsx("div", {
2205
+ className: "mb-1 text-xs text-green-600",
2206
+ children: editableNode.tagName
2207
+ }),
2208
+ bindings: bindingsOf(editableNode, onNodeChange),
2209
+ onNodeChange
2210
+ }, `editable-${nodeId || idx}`);
2211
+ })]
2212
+ }),
2213
+ !!item.children && !editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
2214
+ className: "space-y-1",
2215
+ children: [/* @__PURE__ */ jsx("div", {
2216
+ className: "text-xs font-medium text-green-700",
2217
+ children: messages.children.childNodes
2218
+ }), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx(ElementFields, {
2219
+ className: "ml-2 rounded border border-green-100 bg-white p-2",
2220
+ bindings: bindingsOf(node, onNodeChange),
2221
+ onNodeChange
2222
+ }, `node-${itemIndex}-${nodeIndex}`))]
2223
+ })
2224
+ ]
2225
+ }, item.id || itemIndex))
2226
+ ]
2227
+ });
2228
+ };
2229
+ //#endregion
2230
+ //#region src/components/dnd/panel/fields/color-picker-field.tsx
2231
+ const COLOR_COMMIT_DELAY = 75;
2232
+ const normalizeToHex = (value) => {
2233
+ const trimmed = value.trim();
2234
+ if (/^#([0-9A-Fa-f]{3}){1,2}$/.test(trimmed)) return trimmed;
2235
+ return "#000000";
2236
+ };
2237
+ const ColorPickerField = ({ value: source, onChange, label }) => {
2238
+ const value = normalizeToHex(source);
2239
+ const [liveValue, setLiveValue] = useState(value);
2240
+ const [prevValue, setPrevValue] = useState(value);
2241
+ const lastCommittedRef = useRef(value);
2242
+ if (value !== prevValue) {
2243
+ setPrevValue(value);
2244
+ setLiveValue(value);
2245
+ }
2246
+ useEffect(() => {
2247
+ lastCommittedRef.current = value;
2248
+ }, [value]);
2249
+ const commit = (next) => {
2250
+ if (next === lastCommittedRef.current) return;
2251
+ lastCommittedRef.current = next;
2252
+ onChange(next);
2253
+ };
2254
+ const debouncedCommit = useDebounce(() => commit(liveValue), {
2255
+ delay: COLOR_COMMIT_DELAY,
2256
+ autoInvoke: false
2257
+ });
2258
+ return /* @__PURE__ */ jsx(ColorPicker, {
2259
+ "aria-label": label,
2260
+ showText: true,
2261
+ value: liveValue,
2262
+ onChange: (next) => {
2263
+ setLiveValue(next);
2264
+ debouncedCommit();
2265
+ },
2266
+ onOpenChange: (open) => {
2267
+ if (!open) commit(liveValue);
2268
+ }
2269
+ });
2270
+ };
2271
+ //#endregion
2272
+ //#region src/components/dnd/panel/fields/field-ids.ts
2273
+ const FieldIdsContext = createContext(null);
2274
+ const useFieldIds = () => {
2275
+ const labelledId = useContext(FieldIdsContext);
2276
+ const fallbackId = useId();
2277
+ const controlId = labelledId ?? fallbackId;
2278
+ return {
2279
+ controlId,
2280
+ errorId: `${controlId}-error`,
2281
+ isLabelled: labelledId !== null
2282
+ };
2283
+ };
2284
+ const LABELABLE_KINDS = /* @__PURE__ */ new Set([
2285
+ "text",
2286
+ "number",
2287
+ "url",
2288
+ "email",
2289
+ "tel",
2290
+ "time"
2291
+ ]);
2292
+ //#endregion
2293
+ //#region src/components/dnd/panel/fields/field-kind.ts
2294
+ const isColorProperty = (property) => property.toLowerCase().includes("color");
2295
+ const getFieldKind = (binding) => {
2296
+ const { property, type, value } = binding;
2297
+ if (binding.canEditValue === false) return "readonly";
2298
+ if (property === "items" || property === "data" || type === "array") return "items";
2299
+ if (type === "richtext") return "richtext";
2300
+ if (property === BINDING_PROP.INNER_HTML) return "html";
2301
+ if (type === "jsx") return "jsx";
2302
+ if (property === "children" && Array.isArray(value)) return "children";
2303
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return "object";
2304
+ if (type === "boolean" || typeof value === "boolean") return "boolean";
2305
+ if (binding.options && Array.isArray(binding.options)) return "select";
2306
+ if (type === "color" || isColorProperty(property)) return "color";
2307
+ if (type === "date") return "date";
2308
+ if (type === "time") return "time";
2309
+ if (type === "datetime") return "datetime";
2310
+ if (type === "url") return "url";
2311
+ if (type === "email") return "email";
2312
+ if (type === "tel") return "tel";
2313
+ if (type === "number" || typeof value === "number") return "number";
2314
+ return "text";
2315
+ };
2316
+ const isStructuralFieldKind = (kind) => kind === "items" || kind === "children";
2317
+ //#endregion
2318
+ //#region src/components/dnd/panel/fields/jsx-editor-field.tsx
2319
+ const JSXEditorField = ({ value, isHTML, onSave }) => {
2320
+ const selectionRef = useRef(null);
2321
+ const focusedRef = useRef(false);
2322
+ const handleUpdate = useCallback((update) => {
2323
+ selectionRef.current = update.state.selection;
2324
+ }, []);
2325
+ const handleCreateEditor = useCallback((view) => {
2326
+ if (selectionRef.current) view.dispatch({ selection: selectionRef.current });
2327
+ if (focusedRef.current) view.focus();
2328
+ }, []);
2329
+ const handleFocus = useCallback(() => {
2330
+ focusedRef.current = true;
2331
+ }, []);
2332
+ const handleBlur = useCallback((event) => {
2333
+ if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) focusedRef.current = false;
2334
+ }, []);
2335
+ return /* @__PURE__ */ jsx("div", {
2336
+ onFocusCapture: handleFocus,
2337
+ onBlurCapture: handleBlur,
2338
+ children: /* @__PURE__ */ jsx(Core, {
2339
+ value,
2340
+ height: "150px",
2341
+ fragment: !isHTML,
2342
+ raw: isHTML,
2343
+ onSave,
2344
+ onUpdate: handleUpdate,
2345
+ onCreateEditor: handleCreateEditor
2346
+ })
2347
+ });
2348
+ };
2349
+ //#endregion
2350
+ //#region src/components/dnd/panel/fields/labeled-field.tsx
2351
+ const LabeledField = ({ binding, label, labelClassName, children }) => {
2352
+ const controlId = useId();
2353
+ const isLabelable = LABELABLE_KINDS.has(getFieldKind(binding));
2354
+ return /* @__PURE__ */ jsxs(FieldIdsContext.Provider, {
2355
+ value: isLabelable ? controlId : null,
2356
+ children: [/* @__PURE__ */ jsx("label", {
2357
+ htmlFor: isLabelable ? controlId : void 0,
2358
+ className: labelClassName,
2359
+ children: label
2360
+ }), children]
2361
+ });
2362
+ };
2363
+ //#endregion
2364
+ //#region src/components/dnd/panel/items/sortable-items.tsx
2365
+ const label = (messages, items, id) => {
2366
+ const index = items.findIndex((item) => item.id === id);
2367
+ return index >= 0 ? messages.items.item(index + 1) : String(id);
2368
+ };
2369
+ const SortableItems = ({ items, disabled, onMove, children }) => {
2370
+ const sensors = useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 4 } }), useSensor(TouchSensor, { activationConstraint: {
2371
+ delay: 250,
2372
+ tolerance: 5
2373
+ } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
2374
+ const onDragEnd = ({ active, over }) => {
2375
+ const from = items.find((item) => item.id === active.id);
2376
+ const to = items.find((item) => item.id === over?.id);
2377
+ if (from && to && from !== to) onMove(from, to.index);
2378
+ };
2379
+ const messages = useLiveMessages();
2380
+ const { announcements: say } = messages;
2381
+ const name = (id) => label(messages, items, id);
2382
+ return /* @__PURE__ */ jsx(DndContext, {
2383
+ sensors,
2384
+ collisionDetection: closestCenter,
2385
+ modifiers: [restrictToVerticalAxis, restrictToParentElement],
2386
+ accessibility: { announcements: {
2387
+ onDragStart: ({ active }) => say.pickedUp(name(active.id)),
2388
+ onDragOver: ({ active, over }) => over ? say.itemOver(name(active.id), name(over.id)) : void 0,
2389
+ onDragEnd: ({ active, over }) => over ? say.itemMoved(name(active.id), name(over.id)) : say.dropped(name(active.id)),
2390
+ onDragCancel: ({ active }) => say.cancelled(name(active.id))
2391
+ } },
2392
+ onDragEnd,
2393
+ children: /* @__PURE__ */ jsx(SortableContext, {
2394
+ items: items.map((item) => item.id),
2395
+ strategy: verticalListSortingStrategy,
2396
+ disabled,
2397
+ children: /* @__PURE__ */ jsx("div", {
2398
+ className: "space-y-4",
2399
+ children: items.map(children)
2400
+ })
2401
+ })
2402
+ });
2403
+ };
2404
+ const ItemCard = ({ id, title, disabled, expanded, onToggle, checkbox, controls, className, children }) => {
2405
+ const messages = useLiveMessages();
2406
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
2407
+ id,
2408
+ disabled
2409
+ });
2410
+ return /* @__PURE__ */ jsxs("div", {
2411
+ ref: setNodeRef,
2412
+ style: {
2413
+ transform: CSS$1.Translate.toString(transform),
2414
+ transition
2415
+ },
2416
+ className: cn(className, isDragging && "relative z-10 shadow-md"),
2417
+ children: [/* @__PURE__ */ jsxs("div", {
2418
+ className: "flex items-center justify-between",
2419
+ children: [/* @__PURE__ */ jsxs("div", {
2420
+ className: "flex items-center space-x-1",
2421
+ children: [
2422
+ /* @__PURE__ */ jsx("button", {
2423
+ type: "button",
2424
+ ref: setActivatorNodeRef,
2425
+ ...attributes,
2426
+ ...listeners,
2427
+ "aria-label": messages.items.reorder(title),
2428
+ disabled,
2429
+ className: "cursor-grab rounded p-0.5 text-gray-400\n hover:text-gray-700 disabled:cursor-not-allowed\n disabled:opacity-40",
2430
+ children: /* @__PURE__ */ jsx(GripVertical, { size: 14 })
2431
+ }),
2432
+ checkbox,
2433
+ /* @__PURE__ */ jsxs("button", {
2434
+ type: "button",
2435
+ "aria-expanded": expanded,
2436
+ onClick: onToggle,
2437
+ className: "inline-flex items-center space-x-1 rounded px-1 text-xs\n font-medium",
2438
+ children: [expanded ? /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 14 }), /* @__PURE__ */ jsx("span", { children: title })]
2439
+ })
2440
+ ]
2441
+ }), controls]
2442
+ }), expanded && children]
2443
+ });
2444
+ };
2445
+ //#endregion
2446
+ //#region src/utils/ast/array-source.ts
2447
+ const commaIn = (source, start, end) => {
2448
+ let comma = null;
2449
+ for (let at = start; at < end; at++) {
2450
+ if (/\s/.test(source[at])) continue;
2451
+ if (source.startsWith("/*", at)) {
2452
+ const close = source.indexOf("*/", at + 2);
2453
+ if (close < 0 || close + 2 > end) return null;
2454
+ at = close + 1;
2455
+ } else if (source.startsWith("//", at)) {
2456
+ const newline = source.indexOf("\n", at + 2);
2457
+ at = newline < 0 ? end : newline;
2458
+ } else if (source[at] === "," && comma === null) comma = at;
2459
+ else return null;
2460
+ }
2461
+ return comma;
2462
+ };
2463
+ const denseArraySource = (source) => {
2464
+ const array = parseArrayExpression(source);
2465
+ if (!array || array.elements.some((node) => !isExpression(node) || node.extra?.parenthesized)) return null;
2466
+ const elements = array.elements;
2467
+ const commas = elements.slice(0, -1).map((node, index) => commaIn(source, node.end, elements[index + 1].start));
2468
+ if (commas.some((comma) => comma === null)) return null;
2469
+ const last = elements.at(-1);
2470
+ return {
2471
+ array,
2472
+ elements,
2473
+ commas,
2474
+ trailing: last ? commaIn(source, last.end, array.end - 1) : null
2475
+ };
2476
+ };
2477
+ const canStructurallyEditArray = (source) => denseArraySource(source) !== null;
2478
+ const validArrayIndices = (indices, length) => [...indices].every((index) => Number.isInteger(index) && index >= 0 && index < length);
2479
+ const reorderArraySource = (source, elements, next) => applyEdits(source, elements.map((node, index) => ({
2480
+ start: node.start,
2481
+ end: node.end,
2482
+ content: source.slice(next[index].start, next[index].end)
2483
+ })));
2484
+ const removeArraySource = (source, data, indices) => {
2485
+ const edits = [];
2486
+ const removedCommas = /* @__PURE__ */ new Set();
2487
+ const { elements, commas, trailing } = data;
2488
+ for (let index = 0; index < elements.length; index++) {
2489
+ if (!indices.has(index)) continue;
2490
+ const start = index;
2491
+ while (index + 1 < elements.length && indices.has(index + 1)) index++;
2492
+ for (let current = start; current <= index; current++) {
2493
+ const node = elements[current];
2494
+ edits.push({
2495
+ start: node.start,
2496
+ end: node.end,
2497
+ content: ""
2498
+ });
2499
+ if (current < elements.length - 1) removedCommas.add(commas[current]);
2500
+ }
2501
+ if (index === elements.length - 1) {
2502
+ if (trailing !== null) removedCommas.add(trailing);
2503
+ else if (start > 0) removedCommas.add(commas[start - 1]);
2504
+ }
2505
+ }
2506
+ for (const comma of removedCommas) edits.push({
2507
+ start: comma,
2508
+ end: comma + 1,
2509
+ content: ""
2510
+ });
2511
+ return applyEdits(source, edits);
2512
+ };
2513
+ const appendArraySource = (source, data, copies) => {
2514
+ const last = data.elements.at(-1);
2515
+ if (!last || copies.length === 0) return source;
2516
+ return applyEdits(source, [{
2517
+ start: last.end,
2518
+ end: last.end,
2519
+ content: `, ${copies.join(", ")}`
2520
+ }]);
2521
+ };
2522
+ //#endregion
2523
+ //#region src/utils/ast/items.ts
2524
+ const elementsOf = (code) => parseArrayExpression(code)?.elements ?? null;
2525
+ const kindOf = (element) => isObjectExpression(element) ? "object" : "primitive";
2526
+ const resolveRenderLeaf = (render, key) => {
2527
+ const leaf = render?.[key];
2528
+ return leaf && "type" in leaf ? leaf : null;
2529
+ };
2530
+ const toTemplateRaw = (value) => {
2531
+ return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
2532
+ };
2533
+ const buildPropertyValue = (value, declaredType, renderLeaf, current) => {
2534
+ if (renderLeaf?.property === BINDING_PROP.INNER_HTML) {
2535
+ const raw = String(value);
2536
+ return templateLiteral([templateElement({
2537
+ raw: toTemplateRaw(raw),
2538
+ cooked: raw
2539
+ }, true)], []);
2540
+ }
2541
+ if (renderLeaf?.type === "jsx" || isJSXElement(current) || isJSXFragment(current)) {
2542
+ const trimmed = String(value).trim();
2543
+ if (!trimmed.startsWith("<")) return stringLiteral(trimmed);
2544
+ try {
2545
+ return parseExpression(trimmed, { plugins: ["jsx", "typescript"] });
2546
+ } catch {
2547
+ return;
2548
+ }
2549
+ }
2550
+ if (declaredType === "array" || declaredType === "object") {
2551
+ if (typeof value === "string") try {
2552
+ return parseExpression(value, { plugins: ["jsx", "typescript"] });
2553
+ } catch {
2554
+ return;
2555
+ }
2556
+ if (!isLosslesslyEvaluable(current)) return;
2557
+ return valueToExpression(value) ?? void 0;
2558
+ }
2559
+ return createNodeFromValue(declaredType, parseValue(value)) ?? void 0;
2560
+ };
2561
+ const updateArrayItemProperty = (code, index, key, value, render) => {
2562
+ const elements = elementsOf(code);
2563
+ const element = elements?.[index];
2564
+ if (!elements || !isObjectExpression(element)) return null;
2565
+ const target = element.properties.find((property) => isObjectProperty(property) && isIdentifier(property.key) && property.key.name === key);
2566
+ if (!target) return null;
2567
+ const declaredType = extractObjectProperties(element)[key]?.type ?? "string";
2568
+ const nextValue = buildPropertyValue(value, declaredType, resolveRenderLeaf(render, key), target.value);
2569
+ if (!nextValue) return null;
2570
+ let serialized = typeof value === "string" && (declaredType === "array" || declaredType === "object" || isJSXElement(nextValue) || isJSXFragment(nextValue)) ? value.trim() : generateCode(nextValue);
2571
+ if (nextValue.trailingComments?.some((comment) => comment.type === "CommentLine")) serialized += "\n";
2572
+ const content = target.shorthand ? `${key}: ${serialized}` : serialized;
2573
+ return applyEdits(code, [{
2574
+ start: target.value.start,
2575
+ end: target.value.end,
2576
+ content
2577
+ }]);
2578
+ };
2579
+ const updateArrayItemValue = (code, index, value) => {
2580
+ const elements = elementsOf(code);
2581
+ const element = elements?.[index];
2582
+ if (!elements || !isExpression(element)) return null;
2583
+ const nextValue = createNodeFromValue(extractNodeValue(element).type, parseValue(value));
2584
+ if (!nextValue) return null;
2585
+ return applyEdits(code, [{
2586
+ start: element.start,
2587
+ end: element.end,
2588
+ content: generateCode(nextValue)
2589
+ }]);
2590
+ };
2591
+ const moveArrayItem = (code, from, to) => {
2592
+ const data = denseArraySource(code);
2593
+ if (!data || !validArrayIndices([from], data.elements.length) || !Number.isInteger(to)) return null;
2594
+ const next = [...data.elements];
2595
+ const [moved] = next.splice(from, 1);
2596
+ next.splice(to, 0, moved);
2597
+ return reorderArraySource(code, data.elements, next);
2598
+ };
2599
+ const moveArrayItems = (code, indices, direction) => {
2600
+ const data = denseArraySource(code);
2601
+ if (!data || !validArrayIndices(indices, data.elements.length) || !["up", "down"].includes(direction)) return null;
2602
+ const next = moveSelectedIndices(data.elements, indices, direction);
2603
+ return {
2604
+ code: reorderArraySource(code, data.elements, next.items),
2605
+ indices: next.indices
2606
+ };
2607
+ };
2608
+ const removeArrayItems = (code, indices, kind) => {
2609
+ const data = denseArraySource(code);
2610
+ if (!data || !validArrayIndices(indices, data.elements.length)) return null;
2611
+ const remaining = removeIndices(data.elements, indices);
2612
+ if (!(kind === void 0 ? remaining : remaining.filter((node) => kindOf(node) === kind)).length) return null;
2613
+ return removeArraySource(code, data, indices);
2614
+ };
2615
+ const copyItem = (code, element, generateId, usedIds) => {
2616
+ const edits = [];
2617
+ let unsupported = false;
2618
+ const reserveId = (id) => {
2619
+ if (usedIds.has(id)) unsupported = true;
2620
+ usedIds.add(id);
2621
+ return JSON.stringify(id);
2622
+ };
2623
+ traverseFast(element, (node) => {
2624
+ if (!isJSXOpeningElement(node)) return;
2625
+ const ids = node.attributes.filter((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name, { name: "data-id" }));
2626
+ const attr = ids[0];
2627
+ if (ids.length > 1 || attr && (!isStringLiteral(attr.value) || node.attributes.slice(node.attributes.indexOf(attr) + 1).some((a) => isJSXSpreadAttribute(a)))) {
2628
+ unsupported = true;
2629
+ return;
2630
+ }
2631
+ if (attr && isStringLiteral(attr.value)) edits.push({
2632
+ start: attr.value.start,
2633
+ end: attr.value.end,
2634
+ content: reserveId(generateId())
2635
+ });
2636
+ });
2637
+ if (isObjectExpression(element)) {
2638
+ const keys = element.properties.filter((prop) => isObjectProperty(prop) && !prop.computed && (isIdentifier(prop.key, { name: "key" }) || isStringLiteral(prop.key, { value: "key" })));
2639
+ const key = keys[0];
2640
+ if (keys.length > 1 || key && (!isStringLiteral(key.value) || element.properties.slice(element.properties.indexOf(key) + 1).some((prop) => isSpreadElement(prop) || "computed" in prop && prop.computed))) return null;
2641
+ if (key && isStringLiteral(key.value)) edits.push({
2642
+ start: key.value.start,
2643
+ end: key.value.end,
2644
+ content: reserveId(`${key.value.value}-${generateId()}`)
2645
+ });
2646
+ }
2647
+ if (unsupported) return null;
2648
+ return applyEdits(code.slice(element.start, element.end), edits.map((edit) => ({
2649
+ ...edit,
2650
+ start: edit.start - element.start,
2651
+ end: edit.end - element.start
2652
+ })));
2653
+ };
2654
+ const duplicateArrayItems = (code, indices, generateId = () => nanoid(6)) => {
2655
+ const data = denseArraySource(code);
2656
+ if (!data || !indices.size || !validArrayIndices(indices, data.elements.length)) return null;
2657
+ const usedIds = /* @__PURE__ */ new Set();
2658
+ traverseFast(data.array, (node) => {
2659
+ if (isJSXAttribute(node) && isJSXIdentifier(node.name, { name: "data-id" }) && isStringLiteral(node.value)) usedIds.add(node.value.value);
2660
+ else if (isObjectProperty(node) && !node.computed && (isIdentifier(node.key, { name: "key" }) || isStringLiteral(node.key, { value: "key" })) && isStringLiteral(node.value)) usedIds.add(node.value.value);
2661
+ });
2662
+ const copies = [...indices].sort((a, b) => a - b).map((index) => copyItem(code, data.elements[index], generateId, usedIds));
2663
+ return copies.every((copy) => copy !== null) ? appendArraySource(code, data, copies) : null;
2664
+ };
2665
+ const appendArrayItem = (code, kind, generateId = () => nanoid(6)) => {
2666
+ const index = denseArraySource(code)?.elements.findIndex((node) => kindOf(node) === kind) ?? -1;
2667
+ return index < 0 ? null : duplicateArrayItems(code, /* @__PURE__ */ new Set([index]), generateId);
2668
+ };
2669
+ //#endregion
2670
+ //#region src/components/dnd/panel/items/item-bindings.ts
2671
+ const isPrimitiveItem = (item) => !("editableProperties" in item);
2672
+ const toItemBindings = (item, { id, value, render, onNodeChange, onValueChange, onPropertyChange }) => {
2673
+ if (isPrimitiveItem(item)) return {
2674
+ id,
2675
+ index: item.index,
2676
+ elementIndex: item.elementIndex,
2677
+ properties: [],
2678
+ nested: [],
2679
+ value: {
2680
+ id: `primitive-${item.id}`,
2681
+ label: `item-${item.index}`,
2682
+ property: item.type,
2683
+ value: item.value ?? "",
2684
+ rawValue: item.type === "unknown" ? item.source : String(item.value ?? ""),
2685
+ canEditValue: canLosslesslyEvaluateSource(item.source),
2686
+ onChange: onValueChange
2687
+ }
2688
+ };
2689
+ const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
2690
+ const binding = resolveRenderEntry(render, key);
2691
+ const propertySource = value.slice(prop.astNode.start, prop.astNode.end);
2692
+ return {
2693
+ ...toBindingFields(binding),
2694
+ id: `item-${id}-${key}`,
2695
+ meta: {
2696
+ ...binding.meta,
2697
+ valueType: prop.type
2698
+ },
2699
+ value: parseValue(String(prop.value)),
2700
+ rawValue: prop.type === "unknown" ? propertySource : String(prop.value),
2701
+ canEditValue: canLosslesslyEvaluateSource(propertySource),
2702
+ onChange: (next) => onPropertyChange(key, next)
2703
+ };
2704
+ });
2705
+ const nested = [...Object.entries(item.jsxBindings).map(([property, nodes]) => ({
2706
+ property,
2707
+ elements: nodes.flatMap((node) => {
2708
+ const source = resolvePanelBindings(node);
2709
+ if (!source) return [];
2710
+ return [{
2711
+ id: source.id,
2712
+ tagName: source.tagName,
2713
+ bindings: withPanelCommit(source.bindings, onNodeChange)
2714
+ }];
2715
+ })
2716
+ })), ...Object.entries(item.jsxFallbacks).map(([property, code]) => ({
2717
+ property,
2718
+ elements: [],
2719
+ fallback: {
2720
+ id: `item-${id}-${property}-jsx`,
2721
+ label: property,
2722
+ property,
2723
+ type: "jsx",
2724
+ value: code,
2725
+ rawValue: code,
2726
+ canEditValue: true,
2727
+ onChange: (next) => onPropertyChange(property, next)
2728
+ }
2729
+ }))];
2730
+ return {
2731
+ id,
2732
+ index: item.index,
2733
+ elementIndex: item.elementIndex,
2734
+ properties,
2735
+ nested
2736
+ };
2737
+ };
2738
+ //#endregion
2739
+ //#region src/components/dnd/panel/items/item-identity.ts
2740
+ const createIdentityState = (value, kind, items, ids = items.map((item) => item.id)) => ({
2741
+ value,
2742
+ kind,
2743
+ ids,
2744
+ signatures: items.map((item) => item.source)
2745
+ });
2746
+ const reconcileIdentityState = (current, value, kind, items) => {
2747
+ if (current && current.value === value && current.kind === kind && current.ids.length === items.length) return current;
2748
+ if (!current || current.kind !== kind) return createIdentityState(value, kind, items);
2749
+ const previousCounts = /* @__PURE__ */ new Map();
2750
+ const nextCounts = /* @__PURE__ */ new Map();
2751
+ current.signatures.forEach((signature) => {
2752
+ previousCounts.set(signature, (previousCounts.get(signature) ?? 0) + 1);
2753
+ });
2754
+ items.forEach((item) => {
2755
+ nextCounts.set(item.source, (nextCounts.get(item.source) ?? 0) + 1);
2756
+ });
2757
+ const ids = items.map((item) => {
2758
+ const previousIndex = current.signatures.indexOf(item.source);
2759
+ return previousIndex >= 0 && previousCounts.get(item.source) === 1 && nextCounts.get(item.source) === 1 ? current.ids[previousIndex] : item.id;
2760
+ });
2761
+ return createIdentityState(value, kind, items, ids);
2762
+ };
2763
+ const moveId = (ids, from, to) => {
2764
+ const next = [...ids];
2765
+ const [moved] = next.splice(from, 1);
2766
+ next.splice(to, 0, moved);
2767
+ return next;
2768
+ };
2769
+ const removeIds = (ids, indices) => {
2770
+ return ids.filter((_, index) => !indices.has(index));
2771
+ };
2772
+ //#endregion
2773
+ //#region src/components/dnd/panel/items/items-source.ts
2774
+ const bindingsInJSX = (source, bindingOptions) => {
2775
+ const nodes = extract(source, bindingOptions);
2776
+ const container = nodes.find((n) => n.bindings?.some((b) => b.property === "children"));
2777
+ if (container) return [container];
2778
+ const bindings = [];
2779
+ nodes.forEach((n) => {
2780
+ if (n.bindings && n.bindings.length > 0 && n.dataAttributes.some((a) => a.name === "data-id")) bindings.push(n);
2781
+ bindings.push(...findEditableChildren(n));
2782
+ });
2783
+ return bindings;
2784
+ };
2785
+ const parseSource = (value, bindingOptions) => {
2786
+ const ast = parseArrayExpression(value);
2787
+ if (!ast) return {
2788
+ objectItems: [],
2789
+ primitiveItems: [],
2790
+ parseError: true
2791
+ };
2792
+ const objectItems = [];
2793
+ const primitiveItems = [];
2794
+ ast.elements.forEach((element, elementIndex) => {
2795
+ if (!isExpression(element)) return;
2796
+ if (!isObjectExpression(element)) {
2797
+ const extracted = extractNodeValue(element);
2798
+ primitiveItems.push({
2799
+ id: nanoid(6),
2800
+ index: primitiveItems.length,
2801
+ elementIndex,
2802
+ source: value.slice(element.start, element.end),
2803
+ value: extracted.value,
2804
+ type: extracted.type
2805
+ });
2806
+ return;
2807
+ }
2808
+ const jsxBindings = {};
2809
+ const jsxFallbacks = {};
2810
+ element.properties.forEach((prop) => {
2811
+ if (!isObjectProperty(prop) || !isIdentifier(prop.key) || !(isJSXElement(prop.value) || isJSXFragment(prop.value))) return;
2812
+ const propertyName = prop.key.name;
2813
+ try {
2814
+ const found = bindingsInJSX(value.slice(prop.value.start, prop.value.end), bindingOptions);
2815
+ if (found.length > 0) jsxBindings[propertyName] = found;
2816
+ else jsxFallbacks[propertyName] = value.slice(prop.value.start, prop.value.end);
2817
+ } catch (error) {
2818
+ console.error(`Failed to parse JSX in property '${propertyName}':`, error);
2819
+ }
2820
+ });
2821
+ const editableProperties = extractObjectProperties(element);
2822
+ for (const property of Object.values(editableProperties)) if (property.type === "array" || property.type === "object") property.value = value.slice(property.astNode.start, property.astNode.end);
2823
+ objectItems.push({
2824
+ id: nanoid(6),
2825
+ index: objectItems.length,
2826
+ elementIndex,
2827
+ source: value.slice(element.start, element.end),
2828
+ editableProperties,
2829
+ jsxBindings,
2830
+ jsxFallbacks
2831
+ });
2832
+ });
2833
+ return {
2834
+ objectItems,
2835
+ primitiveItems,
2836
+ parseError: false
2837
+ };
2838
+ };
2839
+ //#endregion
2840
+ //#region src/components/dnd/panel/items/use-item-expansion.ts
2841
+ const useItemExpansion = (ids) => {
2842
+ const [collapsed, { toggle, set }] = useSet();
2843
+ return {
2844
+ expandedIds: ids.filter((id) => !collapsed.has(id)),
2845
+ isExpanded: (id) => !collapsed.has(id),
2846
+ toggle: (id) => toggle(id),
2847
+ setExpanded: (expandedIds) => {
2848
+ const expanded = new Set(expandedIds);
2849
+ set(ids.filter((id) => !expanded.has(id)));
2850
+ }
2851
+ };
2852
+ };
2853
+ //#endregion
2854
+ //#region src/components/dnd/panel/items/use-dnd-items.ts
2855
+ const useDndItems = (value, { render, onChange, onNodeChange, minItems, maxItems } = {}) => {
2856
+ const { reportError, bindingOptions } = useDndEditOptions();
2857
+ const messages = useLiveMessages();
2858
+ const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value, bindingOptions), [value, bindingOptions]);
2859
+ const canEditStructure = useMemo(() => canStructurallyEditArray(value), [value]);
2860
+ const reportErrorRef = useLatest(reportError);
2861
+ const messagesRef = useLatest(messages);
2862
+ useEffect(() => {
2863
+ if (parseError) reportErrorRef.current({
2864
+ type: "parse",
2865
+ target: "items",
2866
+ title: messagesRef.current.editErrors.itemsParseFailed,
2867
+ description: messagesRef.current.editErrors.checkConsole
2868
+ });
2869
+ }, [
2870
+ parseError,
2871
+ reportErrorRef,
2872
+ messagesRef
2873
+ ]);
2874
+ const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
2875
+ const isPrimitive = kind === "primitive";
2876
+ const rawItems = isPrimitive ? primitiveItems : objectItems;
2877
+ const [identityState, setIdentityState] = useState(() => createIdentityState(value, kind, rawItems));
2878
+ const identity = useMemo(() => reconcileIdentityState(identityState, value, kind, rawItems), [
2879
+ identityState,
2880
+ kind,
2881
+ rawItems,
2882
+ value
2883
+ ]);
2884
+ const { selection, record } = useStructuralSelection(isPrimitive ? primitiveItems.length : objectItems.length, value);
2885
+ const pending = usePendingCommit(value);
2886
+ const latest = () => {
2887
+ const last = pending.read();
2888
+ if (!last) return {
2889
+ value,
2890
+ items: rawItems,
2891
+ ids: identity.ids,
2892
+ selected: selection.selected
2893
+ };
2894
+ const parsed = parseSource(last.value);
2895
+ return {
2896
+ value: last.value,
2897
+ items: isPrimitive ? parsed.primitiveItems : parsed.objectItems,
2898
+ ids: last.ids,
2899
+ selected: last.selected
2900
+ };
2901
+ };
2902
+ const locate = (snapshot, elementIndex) => {
2903
+ const shown = rawItems.find((item) => item.elementIndex === elementIndex);
2904
+ if (!shown) return;
2905
+ const index = snapshot.ids.indexOf(identity.ids[shown.index]);
2906
+ return index >= 0 ? snapshot.items[index] : null;
2907
+ };
2908
+ const commit = (snapshot, next, nextIds, nextSelection) => {
2909
+ if (next === null) {
2910
+ reportError({
2911
+ type: "items",
2912
+ title: messages.editErrors.itemUpdateFailed,
2913
+ description: messages.editErrors.itemUpdateFailedDetail
2914
+ });
2915
+ return false;
2916
+ }
2917
+ const parsed = parseSource(next);
2918
+ const nextItems = kind === "primitive" ? parsed.primitiveItems : parsed.objectItems;
2919
+ const ids = nextIds ?? snapshot.ids;
2920
+ pending.write({
2921
+ value: next,
2922
+ ids,
2923
+ selected: nextSelection
2924
+ });
2925
+ setIdentityState(createIdentityState(next, kind, nextItems, ids));
2926
+ record(nextSelection, (revision) => revision === next, { now: true });
2927
+ onChange?.(next);
2928
+ return true;
2929
+ };
2930
+ const fewestItems = Math.max(minItems ?? 1, 1);
2931
+ const refuseCount = (length) => {
2932
+ const description = maxItems !== void 0 && length > maxItems ? messages.validation.maxItems(maxItems) : minItems !== void 0 && length < fewestItems ? messages.validation.minItems(fewestItems) : null;
2933
+ if (description === null) return false;
2934
+ reportError({
2935
+ type: "items",
2936
+ title: messages.editErrors.itemUpdateFailed,
2937
+ description
2938
+ });
2939
+ return true;
2940
+ };
2941
+ const elementIndicesOf = (snapshot, indices) => new Set(snapshot.items.filter((item) => indices.has(item.index)).map((item) => item.elementIndex));
2942
+ const updateProperty = (elementIndex, propertyKey, next) => {
2943
+ const snapshot = latest();
2944
+ const item = locate(snapshot, elementIndex);
2945
+ if (item === null) return;
2946
+ commit(snapshot, updateArrayItemProperty(snapshot.value, item?.elementIndex ?? elementIndex, propertyKey, next, render), void 0, new Set(snapshot.selected));
2947
+ };
2948
+ const updateValue = (elementIndex, next) => {
2949
+ const snapshot = latest();
2950
+ const item = locate(snapshot, elementIndex);
2951
+ if (item === null) return;
2952
+ commit(snapshot, updateArrayItemValue(snapshot.value, item?.elementIndex ?? elementIndex, next), void 0, new Set(snapshot.selected));
2953
+ };
2954
+ const actions = {
2955
+ add: () => {
2956
+ const snapshot = latest();
2957
+ if (refuseCount(snapshot.items.length + 1)) return;
2958
+ const next = appendArrayItem(snapshot.value, kind);
2959
+ commit(snapshot, next, next ? [...snapshot.ids, nanoid(6)] : void 0, selectionAfter({ type: "append" }, snapshot.selected, snapshot.items.length));
2960
+ },
2961
+ move: (elementIndex, toIndex) => {
2962
+ const snapshot = latest();
2963
+ const from = locate(snapshot, elementIndex);
2964
+ const target = snapshot.items.find((item) => item.index === toIndex);
2965
+ if (!from || !target) return;
2966
+ commit(snapshot, moveArrayItem(snapshot.value, from.elementIndex, target.elementIndex), moveId(snapshot.ids, from.index, target.index), /* @__PURE__ */ new Set());
2967
+ },
2968
+ remove: (elementIndex) => {
2969
+ const snapshot = latest();
2970
+ const removed = locate(snapshot, elementIndex);
2971
+ if (removed === null || refuseCount(snapshot.items.length - 1)) return;
2972
+ commit(snapshot, removeArrayItems(snapshot.value, /* @__PURE__ */ new Set([removed?.elementIndex ?? elementIndex]), kind), removed ? removeIds(snapshot.ids, /* @__PURE__ */ new Set([removed.index])) : void 0, /* @__PURE__ */ new Set());
2973
+ },
2974
+ duplicateSelected: () => {
2975
+ const snapshot = latest();
2976
+ const selected = elementIndicesOf(snapshot, snapshot.selected);
2977
+ if (refuseCount(snapshot.items.length + selected.size)) return;
2978
+ const next = duplicateArrayItems(snapshot.value, selected);
2979
+ const copied = snapshot.items.filter((item) => selected.has(item.elementIndex)).sort((a, b) => a.elementIndex - b.elementIndex).map(() => nanoid(6));
2980
+ commit(snapshot, next, next ? [...snapshot.ids, ...copied] : void 0, selectionAfter({
2981
+ type: "duplicate",
2982
+ indices: [...snapshot.selected]
2983
+ }, snapshot.selected, snapshot.items.length));
2984
+ },
2985
+ moveSelected: (direction) => {
2986
+ const snapshot = latest();
2987
+ const nextIdentity = moveSelectedIndices(snapshot.ids, snapshot.selected, direction);
2988
+ const result = moveArrayItems(snapshot.value, elementIndicesOf(snapshot, snapshot.selected), direction);
2989
+ if (!result) {
2990
+ commit(snapshot, null, void 0, snapshot.selected);
2991
+ return;
2992
+ }
2993
+ const visible = parseArrayExpression(result.code)?.elements.flatMap((node, index) => isExpression(node) && isObjectExpression(node) === !isPrimitive ? [index] : []) ?? [];
2994
+ commit(snapshot, result.code, nextIdentity.items, new Set(visible.flatMap((position, index) => result.indices.has(position) ? [index] : [])));
2995
+ },
2996
+ removeSelected: () => {
2997
+ const snapshot = latest();
2998
+ const selected = elementIndicesOf(snapshot, snapshot.selected);
2999
+ if (refuseCount(snapshot.items.length - selected.size)) return;
3000
+ commit(snapshot, removeArrayItems(snapshot.value, selected, kind), removeIds(snapshot.ids, snapshot.selected), /* @__PURE__ */ new Set());
3001
+ }
3002
+ };
3003
+ const items = rawItems.map((item) => toItemBindings(item, {
3004
+ id: identity.ids[item.index] ?? item.id,
3005
+ value,
3006
+ render,
3007
+ onNodeChange,
3008
+ onValueChange: (next) => updateValue(item.elementIndex, next),
3009
+ onPropertyChange: (key, next) => updateProperty(item.elementIndex, key, next)
3010
+ }));
3011
+ return {
3012
+ kind,
3013
+ items,
3014
+ selection,
3015
+ expansion: useItemExpansion(items.map((item) => item.id)),
3016
+ actions,
3017
+ canEditStructure,
3018
+ canAddItems: (count = 1) => maxItems === void 0 || items.length + count <= maxItems,
3019
+ canRemoveItems: (count = 1) => items.length - count >= fewestItems,
3020
+ parseError
3021
+ };
3022
+ };
3023
+ //#endregion
3024
+ //#region src/components/dnd/panel/items/items.tsx
3025
+ const NestedGroup = ({ group, onNodeChange }) => {
3026
+ if (group.fallback) return /* @__PURE__ */ jsxs("div", {
3027
+ className: "space-y-2",
3028
+ children: [/* @__PURE__ */ jsx("div", {
3029
+ className: "text-xs font-medium text-blue-700",
3030
+ children: group.property
3031
+ }), /* @__PURE__ */ jsx(Field, {
3032
+ binding: group.fallback,
3033
+ onNodeChange
3034
+ })]
3035
+ });
3036
+ return /* @__PURE__ */ jsxs("div", {
3037
+ className: "space-y-2",
3038
+ children: [/* @__PURE__ */ jsxs("div", {
3039
+ className: "text-xs font-medium text-blue-700",
3040
+ children: [
3041
+ group.property,
3042
+ " Bindings (",
3043
+ group.elements.length,
3044
+ "):"
3045
+ ]
3046
+ }), group.elements.map((element) => /* @__PURE__ */ jsx(ElementFields, {
3047
+ id: element.id,
3048
+ className: "rounded border border-blue-100 bg-blue-50 p-2\n data-[picked]:ring-2 data-[picked]:ring-blue-400",
3049
+ header: /* @__PURE__ */ jsxs("div", {
3050
+ className: "mb-1 text-xs text-blue-600",
3051
+ children: [
3052
+ "<",
3053
+ element.tagName,
3054
+ ">"
3055
+ ]
3056
+ }),
3057
+ bindings: element.bindings,
3058
+ onNodeChange
3059
+ }, `${group.property}-${element.id}`))]
3060
+ });
3061
+ };
3062
+ const Items = ({ value, render, minItems, maxItems, onChange, onChildChange }) => {
3063
+ const messages = useLiveMessages();
3064
+ const { kind, items, selection, expansion, actions, canEditStructure, canAddItems, canRemoveItems, parseError } = useDndItems(value, {
3065
+ render,
3066
+ onChange,
3067
+ onNodeChange: onChildChange,
3068
+ minItems,
3069
+ maxItems
3070
+ });
3071
+ const header = /* @__PURE__ */ jsxs("div", {
3072
+ className: "flex items-center justify-between",
3073
+ children: [/* @__PURE__ */ jsx("div", {
3074
+ className: "text-sm font-semibold",
3075
+ children: messages.items.heading(items.length)
3076
+ }), items.length > 0 && canEditStructure && /* @__PURE__ */ jsx(Button, {
3077
+ size: "small",
3078
+ icon: /* @__PURE__ */ jsx(Plus, {}),
3079
+ variant: "solid",
3080
+ color: "green",
3081
+ disabled: !canAddItems(),
3082
+ onClick: actions.add,
3083
+ children: messages.items.add
3084
+ })]
3085
+ });
3086
+ if (parseError) return /* @__PURE__ */ jsxs("div", {
3087
+ className: "space-y-4",
3088
+ children: [header, /* @__PURE__ */ jsx("div", {
3089
+ className: "rounded border border-dashed border-red-200 p-3 text-xs\n text-red-600",
3090
+ children: messages.items.unreadable
3091
+ })]
3092
+ });
3093
+ if (items.length === 0) return /* @__PURE__ */ jsxs("div", {
3094
+ className: "space-y-4",
3095
+ children: [header, /* @__PURE__ */ jsx("div", {
3096
+ className: "rounded border border-dashed border-gray-200 p-3 text-xs\n text-gray-500",
3097
+ children: messages.items.empty
3098
+ })]
3099
+ });
3100
+ const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
3101
+ count: selection.selected.size,
3102
+ disabled: !canEditStructure,
3103
+ duplicateDisabled: !canAddItems(selection.selected.size),
3104
+ deleteDisabled: !canRemoveItems(selection.selected.size),
3105
+ onDuplicate: actions.duplicateSelected,
3106
+ onMoveUp: () => actions.moveSelected("up"),
3107
+ onMoveDown: () => actions.moveSelected("down"),
3108
+ onDelete: actions.removeSelected,
3109
+ onClear: selection.clear
3110
+ });
3111
+ const objectBody = (item) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
3112
+ className: "space-y-2",
3113
+ children: item.properties.map((binding) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
3114
+ className: "flex flex-col space-y-2",
3115
+ children: /* @__PURE__ */ jsx(LabeledField, {
3116
+ binding,
3117
+ labelClassName: "w-20 shrink-0 text-xs font-medium",
3118
+ label: binding.label,
3119
+ children: /* @__PURE__ */ jsx(Field, {
3120
+ binding,
3121
+ onNodeChange: onChildChange
3122
+ })
3123
+ })
3124
+ }), /* @__PURE__ */ jsxs("span", {
3125
+ className: "text-right text-xs text-gray-500",
3126
+ children: [
3127
+ "(",
3128
+ String(binding.meta?.valueType),
3129
+ ")"
3130
+ ]
3131
+ })] }, `${item.id}-${binding.property}-${binding.label}`))
3132
+ }), /* @__PURE__ */ jsx("div", {
3133
+ className: "space-y-3 border-t pt-2",
3134
+ children: item.nested.length ? item.nested.map((group) => /* @__PURE__ */ jsx(NestedGroup, {
3135
+ group,
3136
+ onNodeChange: onChildChange
3137
+ }, group.property)) : /* @__PURE__ */ jsxs("div", {
3138
+ className: "text-xs text-gray-500",
3139
+ children: [
3140
+ /* @__PURE__ */ jsx("span", {
3141
+ "aria-hidden": "true",
3142
+ children: "✓"
3143
+ }),
3144
+ " ",
3145
+ messages.items.noNestedBindings
3146
+ ]
3147
+ })
3148
+ })] });
3149
+ const allExpanded = expansion.expandedIds.length === items.length;
3150
+ return /* @__PURE__ */ jsxs("div", {
3151
+ className: "space-y-4",
3152
+ children: [
3153
+ header,
3154
+ /* @__PURE__ */ jsxs("div", {
3155
+ className: "flex items-center justify-between",
3156
+ children: [/* @__PURE__ */ jsx(SelectAllCheckbox, {
3157
+ total: items.length,
3158
+ selected: selection.selected.size,
3159
+ disabled: !canEditStructure,
3160
+ onSelectAll: () => selection.replace(new Set(items.map((item) => item.index))),
3161
+ onClear: selection.clear
3162
+ }), items.length > 1 && /* @__PURE__ */ jsx(Button, {
3163
+ size: "small",
3164
+ variant: "text",
3165
+ onClick: () => expansion.setExpanded(allExpanded ? [] : items.map((item) => item.id)),
3166
+ children: allExpanded ? messages.items.collapseAll : messages.items.expandAll
3167
+ })]
3168
+ }),
3169
+ bulkBar,
3170
+ !canEditStructure && /* @__PURE__ */ jsx("div", {
3171
+ className: "rounded border border-dashed border-amber-200 p-3 text-xs\n text-amber-700",
3172
+ children: messages.items.structureLocked
3173
+ }),
3174
+ /* @__PURE__ */ jsx(SortableItems, {
3175
+ items,
3176
+ disabled: !canEditStructure,
3177
+ onMove: (item, toIndex) => actions.move(item.elementIndex, toIndex),
3178
+ children: (item) => /* @__PURE__ */ jsx(ItemCard, {
3179
+ id: item.id,
3180
+ title: messages.items.item(item.index + 1),
3181
+ disabled: !canEditStructure,
3182
+ expanded: expansion.isExpanded(item.id),
3183
+ onToggle: () => expansion.toggle(item.id),
3184
+ checkbox: /* @__PURE__ */ jsx(SelectionCheckbox, {
3185
+ checked: selection.isSelected(item.index),
3186
+ disabled: !canEditStructure,
3187
+ onToggle: (shiftKey) => selection.toggle(item.index, shiftKey)
3188
+ }),
3189
+ controls: /* @__PURE__ */ jsx(RowControls, {
3190
+ index: item.index,
3191
+ count: items.length,
3192
+ labels: messages.items,
3193
+ disabled: !canEditStructure,
3194
+ deleteDisabled: !canRemoveItems(),
3195
+ onMove: (toIndex) => actions.move(item.elementIndex, toIndex),
3196
+ onDelete: () => actions.remove(item.elementIndex)
3197
+ }),
3198
+ className: kind === "primitive" ? "space-y-2 rounded border border-gray-100 bg-gray-50 p-2" : "space-y-3 rounded border bg-gray-50 p-3",
3199
+ children: kind === "primitive" ? /* @__PURE__ */ jsx(Field, {
3200
+ binding: item.value,
3201
+ onNodeChange: onChildChange
3202
+ }) : objectBody(item)
3203
+ }, item.id)
3204
+ })
3205
+ ]
3206
+ });
3207
+ };
3208
+ //#endregion
3209
+ //#region src/components/dnd/panel/field.tsx
3210
+ const parseDateValue = (value) => {
3211
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?$/.exec(value.trim());
3212
+ if (!match) return;
3213
+ const [, year, month, day, hours = "0", minutes = "0"] = match;
3214
+ const date = new Date(Number(year), Number(month) - 1, Number(day), Number(hours), Number(minutes));
3215
+ return Number.isNaN(date.getTime()) ? void 0 : date;
3216
+ };
3217
+ const pad = (value) => String(value).padStart(2, "0");
3218
+ const formatDateValue = (date, withTime = false) => {
3219
+ const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
3220
+ return withTime ? `${day}T${pad(date.getHours())}:${pad(date.getMinutes())}` : day;
3221
+ };
3222
+ const RICHTEXT_TOOLBAR = [
3223
+ "bold",
3224
+ "italic",
3225
+ "underline",
3226
+ "bulletList",
3227
+ "orderedList",
3228
+ "link"
3229
+ ];
3230
+ const FieldError = ({ id, message }) => message ? /* @__PURE__ */ jsx("p", {
3231
+ id,
3232
+ role: "alert",
3233
+ className: "mt-1 mb-0 text-xs text-red-500",
3234
+ children: message
3235
+ }) : null;
3236
+ const BuiltinField = ({ binding, onNodeChange }) => {
3237
+ const messages = useLiveMessages();
3238
+ const validationOptions = { messages: messages.validation };
3239
+ const { id, value, rawValue, onChange } = binding;
3240
+ const [validationError, setValidationError] = useState(null);
3241
+ const { controlId, errorId, isLabelled } = useFieldIds();
3242
+ const controlProps = {
3243
+ id: controlId,
3244
+ "aria-label": isLabelled ? void 0 : binding.label,
3245
+ "aria-invalid": validationError ? true : void 0,
3246
+ "aria-describedby": validationError ? errorId : void 0
3247
+ };
3248
+ const [text, setText] = useState(rawValue);
3249
+ const [prevRawValue, setPrevRawValue] = useState(rawValue);
3250
+ if (rawValue !== prevRawValue) {
3251
+ setPrevRawValue(rawValue);
3252
+ setText(rawValue);
3253
+ }
3254
+ const kind = getFieldKind(binding);
3255
+ if (kind === "readonly") return /* @__PURE__ */ jsxs("div", {
3256
+ className: "space-y-1 rounded border border-dashed border-amber-200 p-3\n text-xs text-amber-700",
3257
+ children: [
3258
+ /* @__PURE__ */ jsx("div", { children: messages.panel.expressionPreserved }),
3259
+ /* @__PURE__ */ jsx("code", {
3260
+ className: "block overflow-x-auto text-amber-800",
3261
+ children: rawValue
3262
+ }),
3263
+ /* @__PURE__ */ jsx("div", { children: messages.panel.useCodeEditor })
3264
+ ]
3265
+ });
3266
+ const commitIfChanged = (next) => {
3267
+ if (next !== (typeof next === "string" ? rawValue : value)) onChange(next);
3268
+ };
3269
+ const validateAndCommit = (next) => {
3270
+ const result = validateBindingValue(binding, next, validationOptions);
3271
+ if (!result.valid) {
3272
+ setValidationError(result.message ?? messages.panel.invalidValue);
3273
+ return;
3274
+ }
3275
+ setValidationError(null);
3276
+ commitIfChanged(next);
3277
+ };
3278
+ if (kind === "items") return /* @__PURE__ */ jsx(Items, {
3279
+ value: rawValue,
3280
+ render: binding.render,
3281
+ minItems: binding.minItems,
3282
+ maxItems: binding.maxItems,
3283
+ onChange,
3284
+ onChildChange: onNodeChange
3285
+ });
3286
+ if (kind === "richtext") return /* @__PURE__ */ jsx(RichTextEditor, {
3287
+ value: rawValue,
3288
+ toolbar: RICHTEXT_TOOLBAR,
3289
+ onChange: commitIfChanged
3290
+ });
3291
+ if (kind === "html" || kind === "jsx") return /* @__PURE__ */ jsx(JSXEditorField, {
3292
+ value: rawValue,
3293
+ isHTML: kind === "html",
3294
+ onSave: commitIfChanged
3295
+ });
3296
+ if (kind === "children") return /* @__PURE__ */ jsx(Children$1, {
3297
+ value,
3298
+ onChange,
3299
+ onNodeChange
3300
+ });
3301
+ if (kind === "object") {
3302
+ const objectValue = value;
3303
+ return /* @__PURE__ */ jsx("div", {
3304
+ className: "space-y-2 rounded border border-gray-200 bg-gray-50 p-2",
3305
+ children: Object.entries(objectValue).map(([key, val]) => {
3306
+ const nested = resolveRenderEntry(binding.render, key);
3307
+ return /* @__PURE__ */ jsx("div", {
3308
+ className: "space-y-1",
3309
+ children: /* @__PURE__ */ jsx(LabeledField, {
3310
+ binding: {
3311
+ ...nested,
3312
+ value: val
3313
+ },
3314
+ labelClassName: "block text-xs font-medium text-gray-600",
3315
+ label: nested.label,
3316
+ children: /* @__PURE__ */ jsx(Field, {
3317
+ binding: {
3318
+ ...toBindingFields(nested),
3319
+ id,
3320
+ value: val,
3321
+ rawValue: typeof val === "object" && val !== null ? JSON.stringify(val) : String(val),
3322
+ onChange: (next) => {
3323
+ onChange({
3324
+ ...objectValue,
3325
+ [key]: next
3326
+ });
3327
+ }
3328
+ },
3329
+ onNodeChange
3330
+ })
3331
+ })
3332
+ }, key);
3333
+ })
3334
+ });
3335
+ }
3336
+ if (kind === "boolean") return /* @__PURE__ */ jsx(Checkbox, {
3337
+ checked: value === true || value === "true",
3338
+ onChange: (checked) => {
3339
+ onChange(checked);
3340
+ }
3341
+ });
3342
+ const stringValue = rawValue;
3343
+ if (kind === "select") return /* @__PURE__ */ jsx(Select, {
3344
+ "aria-label": binding.label,
3345
+ value: stringValue,
3346
+ options: binding.options ?? [],
3347
+ onChange: commitIfChanged
3348
+ });
3349
+ if (kind === "color") return /* @__PURE__ */ jsx(ColorPickerField, {
3350
+ value: stringValue,
3351
+ label: binding.label,
3352
+ onChange: commitIfChanged
3353
+ });
3354
+ if (kind === "date" || kind === "datetime") {
3355
+ const withTime = kind === "datetime";
3356
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DatePicker, {
3357
+ value: parseDateValue(stringValue),
3358
+ showTime: withTime,
3359
+ onChange: (date) => validateAndCommit(date ? formatDateValue(date, withTime) : "")
3360
+ }), /* @__PURE__ */ jsx(FieldError, {
3361
+ id: errorId,
3362
+ message: validationError
3363
+ })] });
3364
+ }
3365
+ if (kind === "time") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(TimePicker, {
3366
+ ...controlProps,
3367
+ value: stringValue || null,
3368
+ onChange: (time) => validateAndCommit(time ?? "")
3369
+ }), /* @__PURE__ */ jsx(FieldError, {
3370
+ id: errorId,
3371
+ message: validationError
3372
+ })] });
3373
+ if (kind === "url" || kind === "email" || kind === "tel") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
3374
+ ...controlProps,
3375
+ type: kind,
3376
+ value: text,
3377
+ onChange: (e) => setText(e.target.value),
3378
+ placeholder: kind === "url" ? messages.panel.urlPlaceholder : kind === "email" ? messages.panel.emailPlaceholder : messages.panel.telPlaceholder,
3379
+ onBlur: (e) => validateAndCommit(e.target.value.trim())
3380
+ }), /* @__PURE__ */ jsx(FieldError, {
3381
+ id: errorId,
3382
+ message: validationError
3383
+ })] });
3384
+ if (kind === "number") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
3385
+ ...controlProps,
3386
+ type: "number",
3387
+ step: binding.step,
3388
+ value: text,
3389
+ onChange: (e) => setText(e.target.value),
3390
+ placeholder: messages.panel.numberPlaceholder,
3391
+ onBlur: (e) => {
3392
+ const raw = e.target.value.trim();
3393
+ const next = raw !== "" && !Number.isNaN(Number(raw)) ? Number(raw) : raw;
3394
+ validateAndCommit(next);
3395
+ }
3396
+ }), /* @__PURE__ */ jsx(FieldError, {
3397
+ id: errorId,
3398
+ message: validationError
3399
+ })] });
3400
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input.TextArea, {
3401
+ ...controlProps,
3402
+ value: text,
3403
+ onChange: (e) => setText(e.target.value),
3404
+ placeholder: messages.panel.textPlaceholder,
3405
+ onBlur: (e) => {
3406
+ const raw = e.target.value.trim();
3407
+ const next = binding.type ? raw : parseValue(raw);
3408
+ validateAndCommit(next);
3409
+ }
3410
+ }), /* @__PURE__ */ jsx(FieldError, {
3411
+ id: errorId,
3412
+ message: validationError
3413
+ })] });
3414
+ };
3415
+ const Field = (props) => {
3416
+ const { renderField } = useDndEditOptions();
3417
+ const builtin = /* @__PURE__ */ jsx(BuiltinField, { ...props });
3418
+ if (!renderField) return builtin;
3419
+ const custom = renderField(props, builtin);
3420
+ return custom === void 0 ? builtin : /* @__PURE__ */ jsx(Fragment, { children: custom });
3421
+ };
3422
+ //#endregion
3423
+ //#region src/components/dnd/panel/element-fields.tsx
3424
+ const KEEP_IN_VIEW_MS = 600;
3425
+ const ElementFields = ({ id, header, bindings, onNodeChange, children, ...props }) => {
3426
+ const { ids, scrollRootRef } = useContext(ElementSelectionContext);
3427
+ const ref = useRef(null);
3428
+ const isPicked = id !== void 0 && ids.has(id);
3429
+ useEffect(() => {
3430
+ const card = ref.current;
3431
+ if (!isPicked || !card) return;
3432
+ const until = performance.now() + KEEP_IN_VIEW_MS;
3433
+ let frame = 0;
3434
+ const keepInView = () => {
3435
+ const root = scrollRootRef?.current;
3436
+ const box = card.getBoundingClientRect();
3437
+ const view = root?.getBoundingClientRect();
3438
+ if (!view || box.top < view.top || box.bottom > view.bottom) card.scrollIntoView?.({ block: "nearest" });
3439
+ if (performance.now() < until) frame = requestAnimationFrame(keepInView);
3440
+ };
3441
+ keepInView();
3442
+ return () => cancelAnimationFrame(frame);
3443
+ }, [isPicked, scrollRootRef]);
3444
+ return /* @__PURE__ */ jsxs("div", {
3445
+ ...props,
3446
+ ref,
3447
+ "data-node-id": id,
3448
+ "data-picked": isPicked ? "" : void 0,
3449
+ children: [
3450
+ header,
3451
+ bindings.length > 0 && /* @__PURE__ */ jsx("div", {
3452
+ className: "space-y-2 rounded",
3453
+ children: /* @__PURE__ */ jsx("div", {
3454
+ className: "space-y-1",
3455
+ children: bindings.map((binding) => /* @__PURE__ */ jsx("div", {
3456
+ className: "space-y-1",
3457
+ children: /* @__PURE__ */ jsx(LabeledField, {
3458
+ binding,
3459
+ labelClassName: "block text-xs font-semibold text-gray-700",
3460
+ label: /* @__PURE__ */ jsxs(Fragment, { children: [binding.label, /* @__PURE__ */ jsxs("span", {
3461
+ className: "ml-1 text-gray-400",
3462
+ children: [
3463
+ "(",
3464
+ binding.property,
3465
+ ")"
3466
+ ]
3467
+ })] }),
3468
+ children: /* @__PURE__ */ jsx(Field, {
3469
+ binding,
3470
+ onNodeChange
3471
+ })
3472
+ })
3473
+ }, `${binding.property}-${binding.label}`))
3474
+ })
3475
+ }),
3476
+ children
3477
+ ]
3478
+ });
3479
+ };
3480
+ //#endregion
3481
+ //#region src/components/dnd/panel/panel.tsx
3482
+ const groupBindingsById = (bindings) => {
3483
+ const groups = /* @__PURE__ */ new Map();
3484
+ for (const binding of bindings) {
3485
+ const group = groups.get(binding.id);
3486
+ if (group) group.push(binding);
3487
+ else groups.set(binding.id, [binding]);
3488
+ }
3489
+ return [...groups.values()];
3490
+ };
3491
+ const describeElement = (binding) => {
3492
+ const { tagName = "element", text = "" } = binding.element ?? {};
3493
+ return {
3494
+ tagName,
3495
+ text,
3496
+ label: text ? `${tagName} "${text}"` : tagName
3497
+ };
3498
+ };
3499
+ const nodeIdOf = (target) => target instanceof Element && target.closest("[data-node-id]")?.getAttribute("data-node-id") || null;
3500
+ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canMoveDown = false, bindings, onNodeChange, readOnly = false }) => {
3501
+ const messages = useLiveMessages();
3502
+ const groups = groupBindingsById(bindings);
3503
+ const rootRef = useRef(null);
3504
+ const inspector = useContext(DndInspectorContext);
3505
+ const pickedId = (inspector?.picked ?? null)?.id;
3506
+ const highlight = inspector?.highlight;
3507
+ const [hoveredId, setHoveredId] = useState(null);
3508
+ const [focusedId, setFocusedId] = useState(null);
3509
+ const targetId = hoveredId ?? focusedId;
3510
+ useEffect(() => {
3511
+ if (!highlight) return;
3512
+ highlight(targetId);
3513
+ return () => highlight(null);
3514
+ }, [highlight, targetId]);
3515
+ const selection = useMemo(() => ({
3516
+ ids: new Set(pickedId ? [pickedId] : []),
3517
+ scrollRootRef: rootRef
3518
+ }), [pickedId]);
3519
+ if (!item) return /* @__PURE__ */ jsx(Typography.Paragraph, {
3520
+ className: cn("p-4 text-sm text-gray-500"),
3521
+ children: messages.panel.selectSection
3522
+ });
3523
+ return /* @__PURE__ */ jsxs("div", {
3524
+ ref: rootRef,
3525
+ onPointerOver: (e) => setHoveredId(nodeIdOf(e.target)),
3526
+ onPointerLeave: () => setHoveredId(null),
3527
+ onFocus: (e) => setFocusedId(nodeIdOf(e.target)),
3528
+ onBlur: (e) => {
3529
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocusedId(null);
3530
+ },
3531
+ className: cn("h-full space-y-4 p-4", "overflow-x-hidden overflow-y-auto"),
3532
+ children: [
3533
+ /* @__PURE__ */ jsxs("div", {
3534
+ className: "flex items-center justify-between",
3535
+ children: [/* @__PURE__ */ jsx(Typography.Title, {
3536
+ className: "text-lg font-semibold",
3537
+ children: item.name
3538
+ }), /* @__PURE__ */ jsxs("div", {
3539
+ className: "flex items-center gap-1",
3540
+ children: [
3541
+ onMoveUp && /* @__PURE__ */ jsx(Button, {
3542
+ icon: /* @__PURE__ */ jsx(ChevronUp, {}),
3543
+ disabled: !canMoveUp,
3544
+ onClick: onMoveUp,
3545
+ "aria-label": messages.section.moveUp
3546
+ }),
3547
+ onMoveDown && /* @__PURE__ */ jsx(Button, {
3548
+ icon: /* @__PURE__ */ jsx(ChevronDown, {}),
3549
+ disabled: !canMoveDown,
3550
+ onClick: onMoveDown,
3551
+ "aria-label": messages.section.moveDown
3552
+ }),
3553
+ onDelete && /* @__PURE__ */ jsx(Button, {
3554
+ danger: true,
3555
+ icon: /* @__PURE__ */ jsx(Trash, {}),
3556
+ onClick: () => onDelete(item.id),
3557
+ "aria-label": messages.section.delete
3558
+ })
3559
+ ]
3560
+ })]
3561
+ }),
3562
+ readOnly && /* @__PURE__ */ jsx(Typography.Text, {
3563
+ role: "status",
3564
+ className: "block text-xs text-amber-700",
3565
+ children: messages.panel.readOnly
3566
+ }),
3567
+ !groups.length && /* @__PURE__ */ jsx(Typography.Text, {
3568
+ className: "text-xs text-gray-400",
3569
+ children: messages.panel.noEditableElements
3570
+ }),
3571
+ /* @__PURE__ */ jsx(ElementSelectionContext.Provider, {
3572
+ value: selection,
3573
+ children: groups.map((group) => {
3574
+ const element = describeElement(group[0]);
3575
+ return /* @__PURE__ */ jsx(ElementFields, {
3576
+ id: group[0].id,
3577
+ role: "group",
3578
+ "aria-label": element.label,
3579
+ className: "rounded data-[picked]:ring-2\n data-[picked]:ring-blue-400 data-[picked]:ring-offset-2",
3580
+ header: groups.length > 1 && /* @__PURE__ */ jsxs("div", {
3581
+ className: "mb-2 flex min-w-0 items-baseline gap-1.5 border-b\n border-gray-100 pb-1 text-xs",
3582
+ children: [/* @__PURE__ */ jsx("code", {
3583
+ className: "shrink-0 text-gray-500",
3584
+ children: element.tagName
3585
+ }), element.text && /* @__PURE__ */ jsx("span", {
3586
+ className: "truncate text-gray-400",
3587
+ children: element.text
3588
+ })]
3589
+ }),
3590
+ bindings: group,
3591
+ onNodeChange
3592
+ }, group[0].id);
3593
+ })
3594
+ })
3595
+ ]
3596
+ });
3597
+ };
3598
+ //#endregion
3599
+ //#region src/components/dnd/layout.tsx
3600
+ const PaletteItems = ({ subject }) => {
3601
+ const { palette: { items, onAdd }, isMobile } = useDndRegions(subject);
3602
+ return /* @__PURE__ */ jsx(Space, {
3603
+ orientation: "vertical",
3604
+ align: "start",
3605
+ children: items.map((item) => /* @__PURE__ */ jsx(DefaultDraggableItem, {
3606
+ item,
3607
+ onAdd,
3608
+ tapToAdd: isMobile
3609
+ }, item.id))
3610
+ });
3611
+ };
3612
+ const PanelFields = ({ subject }) => {
3613
+ const { panel: { item, onDelete, onMoveUp, onMoveDown, canMoveUp, canMoveDown, bindings, onNodeChange, readOnly } } = useDndRegions(subject);
3614
+ return /* @__PURE__ */ jsx(Panel$1, {
3615
+ item,
3616
+ onDelete,
3617
+ onMoveUp,
3618
+ onMoveDown,
3619
+ canMoveUp,
3620
+ canMoveDown,
3621
+ bindings,
3622
+ onNodeChange,
3623
+ readOnly
3624
+ });
3625
+ };
3626
+ const Palette = ({ className, ...restProps }) => /* @__PURE__ */ jsx("div", {
3627
+ className: cn("h-full overflow-y-auto bg-gray-50 p-4", className),
3628
+ ...restProps,
3629
+ children: /* @__PURE__ */ jsx(PaletteItems, { subject: "<Live.Dnd.Palette>" })
3630
+ });
3631
+ const Canvas = ({ className, style, ...restProps }) => {
3632
+ const { canvas, documentError } = useDndRegions("<Live.Dnd.Canvas>");
3633
+ const inspector = useDndInspector();
3634
+ const messages = useLiveMessages();
3635
+ return /* @__PURE__ */ jsxs("div", {
3636
+ className: cn("relative h-full w-full overflow-y-auto", className),
3637
+ "data-frame-container": true,
3638
+ style: {
3639
+ isolation: "isolate",
3640
+ contain: "layout style",
3641
+ transform: "translateZ(0)",
3642
+ ...style
3643
+ },
3644
+ ...restProps,
3645
+ children: [canvas, !documentError && /* @__PURE__ */ jsx("div", {
3646
+ className: "pointer-events-none sticky bottom-0 z-70 h-0",
3647
+ children: /* @__PURE__ */ jsx("div", {
3648
+ className: "pointer-events-auto absolute bottom-3 left-3",
3649
+ children: /* @__PURE__ */ jsx(Button, {
3650
+ icon: /* @__PURE__ */ jsx(Crosshair, {}),
3651
+ color: "primary",
3652
+ variant: inspector.active ? "solid" : "outlined",
3653
+ "aria-pressed": inspector.active,
3654
+ "aria-label": messages.canvas.pickElement,
3655
+ title: inspector.active ? messages.canvas.pickElementActive : messages.canvas.pickElement,
3656
+ onClick: inspector.toggle
3657
+ })
3658
+ })
3659
+ })]
3660
+ });
3661
+ };
3662
+ const Panel = ({ className, ...restProps }) => /* @__PURE__ */ jsx("div", {
3663
+ className: cn("h-full", className),
3664
+ ...restProps,
3665
+ children: /* @__PURE__ */ jsx(PanelFields, { subject: "<Live.Dnd.Panel>" })
3666
+ });
3667
+ const Layout = ({ palette, panel }) => {
3668
+ const { isMobile, selectedId, clearSelection, paletteOpen, setPaletteOpen } = useDndRegions("<Live.Dnd.Layout>");
3669
+ const messages = useLiveMessages();
3670
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3671
+ isMobile ? /* @__PURE__ */ jsx(Canvas, {}) : /* @__PURE__ */ jsxs(Splitter, {
3672
+ withHandle: true,
3673
+ orientation: "horizontal",
3674
+ children: [
3675
+ /* @__PURE__ */ jsx(Splitter.Panel, {
3676
+ defaultSize: "20%",
3677
+ minSize: "15%",
3678
+ maxSize: "35%",
3679
+ collapsible: true,
3680
+ children: palette ?? /* @__PURE__ */ jsx(Palette, {})
3681
+ }),
3682
+ /* @__PURE__ */ jsx(Splitter.Panel, {
3683
+ defaultSize: "60%",
3684
+ children: /* @__PURE__ */ jsx(Canvas, {})
3685
+ }),
3686
+ /* @__PURE__ */ jsx(Splitter.Panel, {
3687
+ defaultSize: "20%",
3688
+ minSize: "15%",
3689
+ maxSize: "35%",
3690
+ collapsible: true,
3691
+ children: panel ?? /* @__PURE__ */ jsx(Panel, {})
3692
+ })
3693
+ ]
3694
+ }),
3695
+ /* @__PURE__ */ jsx(Button, {
3696
+ type: "primary",
3697
+ shape: "circle",
3698
+ icon: /* @__PURE__ */ jsx(LayoutGrid, {}),
3699
+ "aria-label": messages.canvas.palette,
3700
+ className: "fixed right-4 bottom-4 z-20 md:hidden",
3701
+ onClick: () => setPaletteOpen(true)
3702
+ }),
3703
+ /* @__PURE__ */ jsx(Drawer, {
3704
+ open: isMobile && paletteOpen,
3705
+ onClose: () => setPaletteOpen(false),
3706
+ direction: "bottom",
3707
+ size: "large",
3708
+ title: messages.canvas.palette,
3709
+ children: palette ?? /* @__PURE__ */ jsx(PaletteItems, { subject: "<Live.Dnd.Layout>" })
3710
+ }),
3711
+ /* @__PURE__ */ jsx(Drawer, {
3712
+ open: isMobile && Boolean(selectedId),
3713
+ onClose: clearSelection,
3714
+ direction: "bottom",
3715
+ size: "large",
3716
+ title: messages.canvas.properties,
3717
+ children: panel ?? /* @__PURE__ */ jsx(PanelFields, { subject: "<Live.Dnd.Layout>" })
3718
+ })
3719
+ ] });
3720
+ };
3721
+ //#endregion
3722
+ //#region src/components/dnd/state/edit-errors.ts
3723
+ const describeUpdateFailure = (failure, label, { editErrors: m }) => {
3724
+ switch (failure?.reason) {
3725
+ case "attribute-not-found": return {
3726
+ title: m.updateFailed(label),
3727
+ description: m.attributeNotFound(failure.property)
3728
+ };
3729
+ case "binding-not-declared": return {
3730
+ title: m.updateFailed(label),
3731
+ description: m.bindingNotDeclared({
3732
+ label,
3733
+ property: failure.property
3734
+ })
3735
+ };
3736
+ case "duplicate-binding": return {
3737
+ title: m.updateFailed(label),
3738
+ description: m.duplicateBinding({
3739
+ count: failure.count,
3740
+ label,
3741
+ property: failure.property
3742
+ })
3743
+ };
3744
+ case "reserved-property": return {
3745
+ title: m.cannotEdit(label),
3746
+ description: m.reservedProperty(failure.property)
3747
+ };
3748
+ case "required-property": return {
3749
+ title: m.cannotRemove(label),
3750
+ description: m.requiredProperty(failure.property)
3751
+ };
3752
+ case "no-binding": return {
3753
+ title: m.updateFailed(label),
3754
+ description: m.noBinding
3755
+ };
3756
+ case "element-not-found": return {
3757
+ title: m.updateFailed(label),
3758
+ description: m.elementNotFound
3759
+ };
3760
+ case "self-closing": return {
3761
+ title: m.cannotEdit(label),
3762
+ description: m.selfClosing(failure.property)
3763
+ };
3764
+ case "unsupported-syntax": return {
3765
+ title: m.cannotEdit(label),
3766
+ description: m.unsupportedSyntax(failure.property)
3767
+ };
3768
+ case "parse-error": return {
3769
+ title: m.updateFailed(label),
3770
+ description: failure.error instanceof Error ? failure.error.message : m.checkConsole
3771
+ };
3772
+ default: return { title: m.updateFailed(label) };
3773
+ }
3774
+ };
3775
+ const blockedEditError = (error, { editErrors: m }) => ({
3776
+ type: "parse",
3777
+ target: "document",
3778
+ reason: "parse-error",
3779
+ error,
3780
+ title: m.syntaxError,
3781
+ description: m.syntaxErrorDetail
3782
+ });
3783
+ const useEditErrors = (onEditError) => {
3784
+ const messages = useLiveMessages();
3785
+ const reportError = onEditError ?? toastEditError;
3786
+ const reportErrorRef = useLatest(reportError);
3787
+ const messagesRef = useLatest(messages);
3788
+ const reportLatest = useCallback((build) => reportErrorRef.current(build(messagesRef.current)), [reportErrorRef, messagesRef]);
3789
+ return {
3790
+ reportError,
3791
+ messages,
3792
+ reportLatest,
3793
+ onBlockedEdit: useCallback((problem) => {
3794
+ if (problem.reason === "parse-error") reportLatest((m) => blockedEditError(problem.error, m));
3795
+ }, [reportLatest])
3796
+ };
3797
+ };
3798
+ //#endregion
3799
+ //#region src/components/dnd/state/use-delete-flow.ts
3800
+ const useDeleteFlow = ({ sections, remove, onBeforeDelete }) => {
3801
+ const removeRef = useLatest(remove);
3802
+ return (id, onDeleted) => {
3803
+ const section = sections.find((candidate) => candidate.id === id);
3804
+ if (!onBeforeDelete || !section) {
3805
+ remove(id);
3806
+ onDeleted?.();
3807
+ return;
3808
+ }
3809
+ const decide = (allowed) => {
3810
+ if (allowed) {
3811
+ removeRef.current(id);
3812
+ onDeleted?.();
3813
+ }
3814
+ };
3815
+ try {
3816
+ const answer = onBeforeDelete(section);
3817
+ if (typeof answer === "boolean") {
3818
+ decide(answer);
3819
+ return;
3820
+ }
3821
+ answer.then(decide, (error) => {
3822
+ console.error("onBeforeDelete rejected; the section was kept", error);
3823
+ });
3824
+ } catch (error) {
3825
+ console.error("onBeforeDelete threw; the section was kept", error);
3826
+ }
3827
+ };
3828
+ };
3829
+ //#endregion
3830
+ //#region src/components/dnd/state/use-dnd-keyboard.ts
3831
+ const KEYBOARD_CODES = {
3832
+ start: ["Space"],
3833
+ cancel: ["Escape"],
3834
+ end: ["Space", "Enter"]
3835
+ };
3836
+ const createNodeRegistry = () => {
3837
+ const nodes = /* @__PURE__ */ new Map();
3838
+ const callbacks = /* @__PURE__ */ new Map();
3839
+ return {
3840
+ get: (id) => nodes.get(id),
3841
+ register: (id) => {
3842
+ let callback = callbacks.get(id);
3843
+ if (!callback) {
3844
+ callback = (node) => {
3845
+ if (node) nodes.set(id, node);
3846
+ else {
3847
+ nodes.delete(id);
3848
+ callbacks.delete(id);
3849
+ }
3850
+ };
3851
+ callbacks.set(id, callback);
3852
+ }
3853
+ return callback;
3854
+ }
3855
+ };
3856
+ };
3857
+ const useDndKeyboard = ({ sections, selectedId, selectedIndex, selectOnly, move, requestDelete }) => {
3858
+ const { announcements: say } = useLiveMessages();
3859
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }), useSensor(KeyboardSensor, {
3860
+ coordinateGetter: sortableKeyboardCoordinates,
3861
+ keyboardCodes: KEYBOARD_CODES
3862
+ }));
3863
+ const nameOf = (id, data) => {
3864
+ return paletteSectionOf(data)?.name ?? sections.find((section) => section.id === id)?.name ?? say.section;
3865
+ };
3866
+ const positionOf = (id) => sections.findIndex((section) => section.id === id) + 1;
3867
+ const announcements = {
3868
+ onDragStart: ({ active }) => say.pickedUp(nameOf(active.id, active.data)),
3869
+ onDragOver: ({ active, over }) => over && positionOf(over.id) > 0 ? say.movedTo(nameOf(active.id, active.data), positionOf(over.id), sections.length) : say.notOver(nameOf(active.id, active.data)),
3870
+ onDragEnd: ({ active, over }) => over && positionOf(over.id) > 0 ? say.droppedAt(nameOf(active.id, active.data), positionOf(over.id), sections.length) : say.dropped(nameOf(active.id, active.data)),
3871
+ onDragCancel: ({ active }) => say.cancelled(nameOf(active.id, active.data))
3872
+ };
3873
+ const screenReaderInstructions = { draggable: say.instructions };
3874
+ const [sectionNodes] = useState(createNodeRegistry);
3875
+ const focusSection = (id) => sectionNodes.get(id)?.focus();
3876
+ const onNavigate = (fromId, to) => {
3877
+ const from = sections.findIndex((section) => section.id === fromId);
3878
+ const target = sections[to === "first" ? 0 : to === "last" ? sections.length - 1 : from + (to === "next" ? 1 : -1)];
3879
+ if (from < 0 || !target || target.id === fromId) return;
3880
+ selectOnly(target.id);
3881
+ focusSection(target.id);
3882
+ };
3883
+ const onDeleteKey = (id) => {
3884
+ const index = sections.findIndex((section) => section.id === id);
3885
+ const neighbor = sections[index + 1] ?? sections[index - 1];
3886
+ requestDelete(id, () => {
3887
+ if (neighbor) focusSection(neighbor.id);
3888
+ });
3889
+ };
3890
+ const onMoveButton = (id, direction) => {
3891
+ const to = sections.findIndex((section) => section.id === id) + (direction === "up" ? -1 : 1);
3892
+ move(id, direction);
3893
+ if (to === 0 || to === sections.length - 1) focusSection(id);
3894
+ };
3895
+ useEffect(() => {
3896
+ if (selectedId === null) return;
3897
+ const frame = requestAnimationFrame(() => {
3898
+ sectionNodes.get(selectedId)?.scrollIntoView?.({ block: "nearest" });
3899
+ });
3900
+ return () => cancelAnimationFrame(frame);
3901
+ }, [
3902
+ sectionNodes,
3903
+ selectedId,
3904
+ selectedIndex
3905
+ ]);
3906
+ return {
3907
+ sensors,
3908
+ announcements,
3909
+ screenReaderInstructions,
3910
+ sectionNodes,
3911
+ onNavigate,
3912
+ onDeleteKey,
3913
+ onMoveButton
3914
+ };
3915
+ };
3916
+ //#endregion
3917
+ //#region src/components/dnd/state/use-inspector-state.ts
3918
+ const useInspectorState = ({ selectedId, selectOnly, sectionNodes, onNodePick }) => {
3919
+ const [inspecting, setInspecting] = useState(false);
3920
+ const [picked, setPicked] = useState(null);
3921
+ const [hoverRect, setHoverRect] = useState(null);
3922
+ const [fieldTarget, setFieldTarget] = useState(null);
3923
+ if (picked && picked.sectionId !== selectedId) setPicked(null);
3924
+ const stopInspecting = useCallback(() => {
3925
+ setInspecting(false);
3926
+ setHoverRect(null);
3927
+ }, []);
3928
+ useKeyPress("esc", stopInspecting, { enabled: inspecting });
3929
+ const fieldRect = useElementPosition(() => {
3930
+ const section = selectedId === null ? void 0 : sectionNodes.get(selectedId);
3931
+ return fieldTarget && section ? findElementById(section, fieldTarget) : null;
3932
+ }, { measure: viewportRect });
3933
+ const inspector = {
3934
+ active: inspecting,
3935
+ activate: () => setInspecting(true),
3936
+ deactivate: stopInspecting,
3937
+ toggle: () => inspecting ? stopInspecting() : setInspecting(true),
3938
+ picked,
3939
+ highlight: setFieldTarget
3940
+ };
3941
+ const onInspectMove = (section, overlay, x, y) => {
3942
+ const element = pickElement(section, overlay, x, y);
3943
+ setHoverRect(element ? viewportRect(element) : null);
3944
+ };
3945
+ const onInspectPick = (sectionId, section, overlay, x, y) => {
3946
+ const id = pickElement(section, overlay, x, y)?.getAttribute(DATA_ATTR.ID);
3947
+ if (!id) return;
3948
+ const pick = {
3949
+ id,
3950
+ sectionId
3951
+ };
3952
+ selectOnly(sectionId);
3953
+ setPicked(pick);
3954
+ stopInspecting();
3955
+ onNodePick?.(pick);
3956
+ };
3957
+ return {
3958
+ inspector,
3959
+ inspecting,
3960
+ onInspectMove,
3961
+ onInspectPick,
3962
+ onInspectLeave: () => setHoverRect(null),
3963
+ highlightRect: inspecting ? hoverRect : fieldTarget ? fieldRect : null
3964
+ };
3965
+ };
3966
+ //#endregion
3967
+ //#region src/components/dnd/state/use-section-document.ts
3968
+ const createLastParsed = () => {
3969
+ let last = null;
3970
+ return {
3971
+ read: (containerId) => last && last.containerId === containerId ? last.document : void 0,
3972
+ write: (containerId, document) => {
3973
+ last = {
3974
+ containerId,
3975
+ document
3976
+ };
3977
+ }
3978
+ };
3979
+ };
3980
+ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback, onBlockedEdit } = {}) => {
3981
+ const { setCode } = usePreview();
3982
+ const [selectedId, setSelectedId] = useState(null);
3983
+ const readSections = useCallback((code) => extractSections(code, {
3984
+ containerId,
3985
+ sectionNameFallback
3986
+ }), [containerId, sectionNameFallback]);
3987
+ const document = useMemo(() => fillSectionIds(value, void 0, { containerId }), [containerId, value]);
3988
+ const [lastParsed] = useState(createLastParsed);
3989
+ const view = useMemo(() => {
3990
+ const inspection = inspectDocument(document, { containerId });
3991
+ if (inspection.ok) {
3992
+ lastParsed.write(containerId, document);
3993
+ return {
3994
+ document,
3995
+ problem: null,
3996
+ stale: false
3997
+ };
3998
+ }
3999
+ const { ok: _ok, ...problem } = inspection;
4000
+ const last = problem.reason === "parse-error" ? lastParsed.read(containerId) : void 0;
4001
+ return {
4002
+ document: last ?? document,
4003
+ problem,
4004
+ stale: last !== void 0
4005
+ };
4006
+ }, [
4007
+ containerId,
4008
+ document,
4009
+ lastParsed
4010
+ ]);
4011
+ const sections = useMemo(() => readSections(view.document), [readSections, view.document]);
4012
+ const [previewCache] = useState(() => createSectionPreviewCache());
4013
+ const previews = useMemo(() => previewCache.compute(view.document, sections, { containerId }), [
4014
+ containerId,
4015
+ previewCache,
4016
+ sections,
4017
+ view.document
4018
+ ]);
4019
+ const selectedIndex = sections.findIndex((s) => s.id === selectedId);
4020
+ const selectedSection = selectedIndex >= 0 ? sections[selectedIndex] : void 0;
4021
+ const pending = usePendingCommit(document);
4022
+ const latestDocument = useCallback(() => pending.read() ?? document, [document, pending]);
4023
+ const latestSections = useCallback(() => readSections(latestDocument()), [latestDocument, readSections]);
4024
+ const commit = useCallback((nextSections) => {
4025
+ const nextCode = fillSectionIds(replaceSections(latestDocument(), nextSections.map((s) => s.code), { containerId }), void 0, { containerId });
4026
+ pending.write(nextCode);
4027
+ onChange?.(nextCode);
4028
+ setCode(nextCode);
4029
+ return readSections(nextCode);
4030
+ }, [
4031
+ containerId,
4032
+ latestDocument,
4033
+ onChange,
4034
+ pending,
4035
+ readSections,
4036
+ setCode
4037
+ ]);
4038
+ const getCommittedSection = useCallback((id) => {
4039
+ const code = pending.read();
4040
+ return code === void 0 ? void 0 : readSections(code).find((s) => s.id === id);
4041
+ }, [pending, readSections]);
4042
+ const { problem, stale } = view;
4043
+ const refuse = useCallback(() => {
4044
+ if (!stale || !problem) return false;
4045
+ onBlockedEdit?.(problem);
4046
+ return true;
4047
+ }, [
4048
+ onBlockedEdit,
4049
+ problem,
4050
+ stale
4051
+ ]);
4052
+ const mutate = useCallback((fn) => {
4053
+ if (refuse()) return;
4054
+ fn(latestSections());
4055
+ }, [latestSections, refuse]);
4056
+ return {
4057
+ sections,
4058
+ previews,
4059
+ selectedId,
4060
+ selectedSection,
4061
+ selectedIndex,
4062
+ select: useCallback((id) => {
4063
+ setSelectedId((prev) => prev === id ? null : id);
4064
+ }, []),
4065
+ selectOnly: useCallback((id) => setSelectedId(id), []),
4066
+ clearSelection: useCallback(() => setSelectedId(null), []),
4067
+ add: useCallback((section, atIndex) => mutate((current) => {
4068
+ const next = { code: section.code };
4069
+ commit(atIndex === void 0 || atIndex < 0 ? [...current, next] : [
4070
+ ...current.slice(0, atIndex),
4071
+ next,
4072
+ ...current.slice(atIndex)
4073
+ ]);
4074
+ }), [commit, mutate]),
4075
+ remove: useCallback((id) => mutate((current) => {
4076
+ const index = current.findIndex((s) => s.id === id);
4077
+ if (index < 0) return;
4078
+ if (id === selectedId) setSelectedId(null);
4079
+ commit(current.filter((_, i) => i !== index));
4080
+ }), [
4081
+ commit,
4082
+ mutate,
4083
+ selectedId
4084
+ ]),
4085
+ copy: useCallback((id) => mutate((current) => {
4086
+ const index = current.findIndex((s) => s.id === id);
4087
+ const source = current[index];
4088
+ if (!source) return;
4089
+ const committed = commit([
4090
+ ...current.slice(0, index + 1),
4091
+ { code: replaceIds(source.code) },
4092
+ ...current.slice(index + 1)
4093
+ ]);
4094
+ setSelectedId(committed[index + 1]?.id ?? null);
4095
+ }), [commit, mutate]),
4096
+ move: useCallback((id, direction) => mutate((current) => {
4097
+ const index = current.findIndex((s) => s.id === id);
4098
+ const targetIndex = direction === "up" ? index - 1 : index + 1;
4099
+ if (index < 0 || targetIndex < 0 || targetIndex >= current.length) return;
4100
+ commit(arrayMove(current, index, targetIndex));
4101
+ }), [commit, mutate]),
4102
+ reorder: useCallback((activeId, overId) => mutate((current) => {
4103
+ const prevIndex = current.findIndex((s) => s.id === activeId);
4104
+ const nextIndex = current.findIndex((s) => s.id === overId);
4105
+ if (prevIndex < 0 || nextIndex < 0 || prevIndex === nextIndex) return;
4106
+ commit(arrayMove(current, prevIndex, nextIndex));
4107
+ }), [commit, mutate]),
4108
+ patch: useCallback((next) => mutate((current) => {
4109
+ commit(current.map((s) => s.id === next.id ? {
4110
+ ...s,
4111
+ ...next
4112
+ } : s));
4113
+ }), [commit, mutate]),
4114
+ getCommittedSection,
4115
+ problem,
4116
+ stale
4117
+ };
4118
+ };
4119
+ //#endregion
4120
+ //#region src/components/dnd/state/use-section-editing.ts
4121
+ const useSectionEditing = ({ selectedSection, stale, problem, missingContainer, getCommittedSection, onChange, bindingOptions, reportError, messages, reportLatest }) => {
4122
+ const selectedCode = selectedSection?.code;
4123
+ const selectedSectionId = selectedSection?.id;
4124
+ const { fields, updatedCode, parseError } = useMemo(() => {
4125
+ if (!selectedCode) return {
4126
+ fields: [],
4127
+ updatedCode: "",
4128
+ parseError: null
4129
+ };
4130
+ try {
4131
+ const updated = fillIdsFrom(selectedCode, selectedSectionId ?? "");
4132
+ return {
4133
+ fields: extract(updated, bindingOptions),
4134
+ updatedCode: updated !== selectedCode ? updated : selectedCode,
4135
+ parseError: null
4136
+ };
4137
+ } catch (e) {
4138
+ console.warn("⚠️ Parsing error", e);
4139
+ return {
4140
+ fields: [],
4141
+ updatedCode: "",
4142
+ parseError: { error: e }
4143
+ };
4144
+ }
4145
+ }, [
4146
+ selectedCode,
4147
+ selectedSectionId,
4148
+ bindingOptions
4149
+ ]);
4150
+ useEffect(() => {
4151
+ if (missingContainer !== null) reportLatest((m) => ({
4152
+ type: "parse",
4153
+ target: "document",
4154
+ reason: "container-not-found",
4155
+ containerId: missingContainer,
4156
+ title: m.editErrors.missingContainer(missingContainer),
4157
+ description: m.editErrors.missingContainerDetail(missingContainer)
4158
+ }));
4159
+ }, [missingContainer, reportLatest]);
4160
+ useEffect(() => {
4161
+ if (parseError) reportLatest((m) => ({
4162
+ type: "parse",
4163
+ target: "section",
4164
+ error: parseError.error,
4165
+ title: m.editErrors.sectionParseFailed,
4166
+ description: m.editErrors.checkConsole
4167
+ }));
4168
+ }, [parseError, reportLatest]);
4169
+ const commitChanges = (changes) => {
4170
+ if (changes.length === 0) return;
4171
+ if (stale && problem?.reason === "parse-error") {
4172
+ reportError(blockedEditError(problem.error, messages));
4173
+ return;
4174
+ }
4175
+ const committed = selectedSection && getCommittedSection(selectedSection.id)?.code;
4176
+ const result = updateAll(committed && committed !== selectedCode ? committed : updatedCode, changes.map(({ id, label, property, value: changeValue }) => ({
4177
+ dataId: id,
4178
+ label,
4179
+ property,
4180
+ value: changeValue
4181
+ })), bindingOptions);
4182
+ if (!result.success) {
4183
+ const { id, label, property } = changes[result.index];
4184
+ reportError({
4185
+ type: "update",
4186
+ id,
4187
+ label,
4188
+ property,
4189
+ failure: result.failure,
4190
+ ...describeUpdateFailure(result.failure, label, messages)
4191
+ });
4192
+ return;
4193
+ }
4194
+ if (selectedSection) onChange({
4195
+ ...selectedSection,
4196
+ code: result.code
4197
+ });
4198
+ };
4199
+ const onFieldChange = (change) => commitChanges([change]);
4200
+ const bindingFields = useMemo(() => fields.flatMap((node) => resolvePanelBindings(node)?.bindings ?? []), [fields]);
4201
+ return {
4202
+ bindings: withPanelCommit(bindingFields, onFieldChange),
4203
+ onFieldChange,
4204
+ commitChanges
4205
+ };
4206
+ };
4207
+ //#endregion
4208
+ //#region src/components/dnd/dnd.tsx
4209
+ const conditionalModifiers = (args) => {
4210
+ const { active } = args;
4211
+ if (paletteSectionOf(active?.data)) return args.transform;
4212
+ return restrictToVerticalAxis(args);
4213
+ };
4214
+ const NO_MODULES = {};
4215
+ const emptyCanvasNotice = (messages, problem, isMobile) => {
4216
+ if (problem?.reason === "parse-error") return {
4217
+ title: messages.canvas.syntaxError,
4218
+ detail: messages.canvas.syntaxErrorDetail
4219
+ };
4220
+ if (problem?.reason === "container-not-found") return {
4221
+ title: messages.canvas.missingContainer(problem.containerId),
4222
+ detail: messages.canvas.missingContainerDetail(problem.containerId)
4223
+ };
4224
+ return {
4225
+ title: messages.canvas.empty,
4226
+ detail: isMobile ? messages.canvas.emptyHintTouch : messages.canvas.emptyHintDrag
4227
+ };
4228
+ };
4229
+ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange: _onChange, className, items = [], frame, dynamicTailwind = false, provider, renderField, onEditError, renderSectionFallback, shouldForceSectionFallback, onBeforeDelete, sectionNameFallback, containerId, bindings: bindingRegistry, bindingKeys, onNodePick, children, ...restProps }) => {
4230
+ const modules = useStableValue(_modules);
4231
+ const [mobilePaletteOpen, setMobilePaletteOpen] = useState(false);
4232
+ const { breakpoint } = useResponsiveSize();
4233
+ const isMobile = breakpoint.current === "xs" || breakpoint.current === "sm";
4234
+ const { code } = usePreview();
4235
+ const isForced = (section) => {
4236
+ try {
4237
+ return shouldForceSectionFallback?.(section) === true;
4238
+ } catch (error) {
4239
+ console.error("shouldForceSectionFallback threw", error);
4240
+ return false;
4241
+ }
4242
+ };
4243
+ const value = _value === void 0 ? code : _value;
4244
+ const { reportError, messages, onBlockedEdit, reportLatest } = useEditErrors(onEditError);
4245
+ const { sections, previews, selectedId, selectedSection, selectedIndex, select, selectOnly, clearSelection, add: addSection, remove, copy: onCopy, move: moveSection, reorder, patch, getCommittedSection, problem, stale } = useSectionDocument(value, _onChange, {
4246
+ containerId,
4247
+ sectionNameFallback,
4248
+ onBlockedEdit
4249
+ });
4250
+ const missingContainer = problem?.reason === "container-not-found" ? problem.containerId : null;
4251
+ const onDragEnd = (event) => {
4252
+ const { active, over } = event;
4253
+ if (!over) return;
4254
+ const section = paletteSectionOf(active.data);
4255
+ if (section) {
4256
+ const atBottom = over.id === "sortable-area" || over.id === "sortable-area-bottom";
4257
+ addSection(section, atBottom ? void 0 : sections.findIndex((s) => s.id === over.id));
4258
+ return;
4259
+ }
4260
+ if (active.id !== over.id && sections.some((s) => s.id === active.id)) reorder(String(active.id), String(over.id));
4261
+ };
4262
+ const onSelect = (id) => select(id);
4263
+ const requestDelete = useDeleteFlow({
4264
+ sections,
4265
+ remove,
4266
+ onBeforeDelete
4267
+ });
4268
+ const onDelete = (id) => requestDelete(id);
4269
+ const { sensors, announcements, screenReaderInstructions, sectionNodes, onNavigate, onDeleteKey, onMoveButton } = useDndKeyboard({
4270
+ sections,
4271
+ selectedId,
4272
+ selectedIndex,
4273
+ selectOnly,
4274
+ move: moveSection,
4275
+ requestDelete
4276
+ });
4277
+ const { inspector, inspecting, onInspectMove, onInspectPick, onInspectLeave, highlightRect } = useInspectorState({
4278
+ selectedId,
4279
+ selectOnly,
4280
+ sectionNodes,
4281
+ onNodePick
4282
+ });
4283
+ const onChange = (next) => {
4284
+ if (!next.id) return;
4285
+ patch(next);
4286
+ };
4287
+ const bindingOptions = useMemo(() => ({
4288
+ bindings: bindingRegistry,
4289
+ bindingKeys
4290
+ }), [bindingRegistry, bindingKeys]);
4291
+ const { bindings, onFieldChange, commitChanges } = useSectionEditing({
4292
+ selectedSection,
4293
+ stale,
4294
+ problem,
4295
+ missingContainer,
4296
+ getCommittedSection,
4297
+ onChange,
4298
+ bindingOptions,
4299
+ reportError,
4300
+ messages,
4301
+ reportLatest
4302
+ });
4303
+ useEffect(() => {
4304
+ if (frame?.scripts?.length) preloadScripts(frame.scripts);
4305
+ }, [frame?.scripts]);
4306
+ const palette = {
4307
+ items: items?.length ? items : PALETTE_SECTIONS,
4308
+ onAdd: (section) => {
4309
+ addSection(section);
4310
+ setMobilePaletteOpen(false);
4311
+ }
4312
+ };
4313
+ const panel = {
4314
+ item: selectedSection,
4315
+ onChange,
4316
+ onDelete,
4317
+ onMoveUp: () => moveSection(selectedId, "up"),
4318
+ onMoveDown: () => moveSection(selectedId, "down"),
4319
+ canMoveUp: selectedIndex > 0,
4320
+ canMoveDown: selectedIndex >= 0 && selectedIndex < sections.length - 1,
4321
+ bindings,
4322
+ onNodeChange: onFieldChange,
4323
+ onNodesChange: commitChanges,
4324
+ readOnly: stale,
4325
+ bindingOptions
4326
+ };
4327
+ const sectionHandlers = {
4328
+ inspecting,
4329
+ onInspectMove,
4330
+ onInspectPick,
4331
+ onInspectLeave,
4332
+ nodeRef: sectionNodes.register,
4333
+ onNavigate,
4334
+ onDeleteKey
4335
+ };
4336
+ const canvas = /* @__PURE__ */ jsxs(SectionHandlersContext.Provider, {
4337
+ value: sectionHandlers,
4338
+ children: [
4339
+ stale && /* @__PURE__ */ jsx("div", {
4340
+ role: "status",
4341
+ className: cn("sticky top-0 z-70", "border-b border-amber-200 bg-amber-50 px-3 py-2", "text-sm text-amber-900"),
4342
+ children: messages.canvas.stale
4343
+ }),
4344
+ /* @__PURE__ */ jsx(Droppable, {
4345
+ className: cn(!sections.length && "h-full"),
4346
+ children: !sections.length ? /* @__PURE__ */ jsx("div", {
4347
+ className: cn("flex items-center justify-center", "h-full", "text-gray-500"),
4348
+ children: /* @__PURE__ */ jsx(CanvasNotice, { ...emptyCanvasNotice(messages, problem, isMobile) })
4349
+ }) : /* @__PURE__ */ jsx(SortableContext, {
4350
+ items: sections.map((s) => s.id),
4351
+ strategy: verticalListSortingStrategy,
4352
+ children: sections.map((section, index) => /* @__PURE__ */ jsx(Sortable, {
4353
+ id: section.id,
4354
+ name: section.name,
4355
+ selected: selectedId === section.id,
4356
+ onClick: () => onSelect(section.id),
4357
+ onDelete,
4358
+ onCopy,
4359
+ onMoveUp: () => onMoveButton(section.id, "up"),
4360
+ onMoveDown: () => onMoveButton(section.id, "down"),
4361
+ canMoveUp: index > 0,
4362
+ canMoveDown: index < sections.length - 1,
4363
+ children: /* @__PURE__ */ jsx(renderer_default, {
4364
+ preview: previews[index],
4365
+ sectionId: section.id,
4366
+ sectionName: section.name,
4367
+ sectionCode: section.code,
4368
+ forceFallback: isForced(section),
4369
+ modules,
4370
+ frame,
4371
+ dynamicTailwind,
4372
+ provider,
4373
+ ...props
4374
+ })
4375
+ }, section.id))
4376
+ })
4377
+ }),
4378
+ /* @__PURE__ */ jsx(InspectorHighlight, { rect: highlightRect })
4379
+ ]
4380
+ });
4381
+ return /* @__PURE__ */ jsx(SectionFallbackContext.Provider, {
4382
+ value: renderSectionFallback,
4383
+ children: /* @__PURE__ */ jsxs(DndContext, {
4384
+ sensors,
4385
+ collisionDetection: closestCenter,
4386
+ accessibility: {
4387
+ announcements,
4388
+ screenReaderInstructions
4389
+ },
4390
+ modifiers: [conditionalModifiers],
4391
+ onDragEnd,
4392
+ children: [/* @__PURE__ */ jsx("div", {
4393
+ className: cn("relative flex h-full w-full", className),
4394
+ ...restProps,
4395
+ children: /* @__PURE__ */ jsx(DndEditOptionsContext.Provider, {
4396
+ value: {
4397
+ renderField,
4398
+ reportError,
4399
+ bindingOptions
4400
+ },
4401
+ children: /* @__PURE__ */ jsx(DndRegionContext.Provider, {
4402
+ value: {
4403
+ palette,
4404
+ panel,
4405
+ canvas,
4406
+ isMobile,
4407
+ selectedId,
4408
+ clearSelection,
4409
+ paletteOpen: mobilePaletteOpen,
4410
+ setPaletteOpen: setMobilePaletteOpen,
4411
+ documentError: problem?.reason ?? null
4412
+ },
4413
+ children: /* @__PURE__ */ jsx(DndInspectorContext.Provider, {
4414
+ value: inspector,
4415
+ children: Children.toArray(children).length ? children : /* @__PURE__ */ jsx(Layout, {})
4416
+ })
4417
+ })
4418
+ })
4419
+ }), /* @__PURE__ */ jsx(DragOverlay, { children: /* @__PURE__ */ jsx(Overlay, {
4420
+ sections,
4421
+ isForced,
4422
+ renderProps: {
4423
+ fullCode: value,
4424
+ modules,
4425
+ frame,
4426
+ dynamicTailwind,
4427
+ ...props,
4428
+ containerId
4429
+ }
4430
+ }) })]
4431
+ })
4432
+ });
4433
+ };
4434
+ //#endregion
4435
+ //#region src/components/dnd/index.ts
4436
+ const Dnd = Dnd$1;
4437
+ Dnd.DraggableItem = DraggableItem;
4438
+ Dnd.Field = Field;
4439
+ Dnd.Palette = Palette;
4440
+ Dnd.Canvas = Canvas;
4441
+ Dnd.Panel = Panel;
4442
+ Dnd.Layout = Layout;
4443
+ //#endregion
4444
+ export { DraggableItem as _, Panel as a, getFieldKind as c, resolvePanelBindings as d, withPanelCommit as f, useDndInspector as g, useDndPanel as h, Palette as i, isStructuralFieldKind as l, useDndPalette as m, Canvas as n, Field as o, useDndLayout as p, Layout as r, useDndItems as s, Dnd as t, useDndChildren as u };
4445
+
4446
+ //# sourceMappingURL=dnd-BlC1gqUy.js.map