@jbpark/live-editor 4.6.1 → 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 (81) 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-V8NAxd_R.js → dnd-BlC1gqUy.js} +2558 -867
  18. package/dist/dnd-BlC1gqUy.js.map +1 -0
  19. package/dist/document-BQhb7YR8.d.ts +17 -0
  20. package/dist/{document-CHO215iP.js → document-DcwIEHRV.js} +189 -104
  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-CiWB0zFb.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-jYuqS4KB.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-BXXtMUNI.js → update-DNInQSM7.js} +143 -392
  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 +4 -64
  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 +9 -9
  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-V8NAxd_R.js.map +0 -1
  70. package/dist/document-CHO215iP.js.map +0 -1
  71. package/dist/document-DqRSCtsx.d.ts +0 -1614
  72. package/dist/error-CN_GCnXB.js.map +0 -1
  73. package/dist/index-C_PfR4X9.d.ts +0 -734
  74. package/dist/items-BXXtMUNI.js.map +0 -1
  75. package/dist/messages-CSrMI4Rm.js.map +0 -1
  76. package/dist/preview-CtyG5UbH.js.map +0 -1
  77. package/dist/sections-jYuqS4KB.js.map +0 -1
  78. package/dist/use-dynamic-tailwind-BP1toJkW.js.map +0 -1
  79. package/dist/utils/index.js.map +0 -1
  80. package/dist/validate-iLo8IONY.js +0 -64
  81. package/dist/validate-iLo8IONY.js.map +0 -1
@@ -1,24 +1,1434 @@
1
- import { A as isJSXElement, B as isObjectExpression, D as isExpression, N as isJSXFragment, O as isIdentifier, V as isObjectProperty, c as inspectDocument, h as replaceIds, m as fillIdsFrom, n as createSectionPreviewCache, r as fillSectionIds, x as nanoid } from "./document-CHO215iP.js";
2
- import { c as RESERVED_BINDING_PROPERTIES, o as PALETTE_SECTIONS, r as DATA_ATTR, t as BINDING_PROP } from "./constants-DU17Tdwo.js";
3
- import { n as validateBindingValue } from "./validate-iLo8IONY.js";
4
- import { i as useLiveMessages } from "./messages-CSrMI4Rm.js";
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
5
  import { i as usePreview } from "./states-Ci1AvoQ9.js";
6
- import { A as extractNodeValue, C as getStructuredValue, D as readNodeBindings, M as parseArrayExpression, N as parseValue, b as getCurrentValue, c as updateArrayItemValue, f as updateAll, g as moveSelectedIndices, h as getChildrenSignatures, i as moveArrayItems, j as extractObjectProperties, k as canLosslesslyEvaluateSource, l as canStructurallyEditArray, n as duplicateArrayItems, o as removeArrayItems, r as moveArrayItem, s as updateArrayItemProperty, t as appendArrayItem, v as extract, y as findEditableChildren } from "./items-BXXtMUNI.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
7
  import { t as cn } from "./cn-CAnad2vL.js";
8
- import { o as preloadScripts } from "./compile-Dxas-D05.js";
9
- import { a as useStableModules, i as Frame, n as useCompiledModule, t as useDynamicTailwind } from "./use-dynamic-tailwind-BP1toJkW.js";
10
- import { i as generateSection, o as replaceSections, r as extractSections } from "./sections-jYuqS4KB.js";
11
- import { n as Error$1, t as ErrorBoundary } from "./boundary-BLB80tyw.js";
12
- import { t as Core } from "./core-Bgfek364.js";
13
- import { Children, createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
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
14
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
15
  import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, PointerSensor, TouchSensor, closestCenter, useDndContext, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
16
16
  import { restrictToParentElement, restrictToVerticalAxis } from "@dnd-kit/modifiers";
17
17
  import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
18
- import { Button, Card, Checkbox, ColorPicker, DatePicker, Drawer, Input, RichTextEditor, Select, Space, Splitter, Toast, Typography } from "@jbpark/ui-kit";
19
- import { useDebounce, useMultiSelect, useResponsiveSize } from "@jbpark/use-hooks";
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
20
  import { CSS as CSS$1 } from "@dnd-kit/utilities";
21
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
22
1432
  //#region src/components/dnd/palette/palette-drag.ts
23
1433
  const PALETTE_SECTION = "palette-section";
24
1434
  const paletteDragData = (section) => ({
@@ -220,6 +1630,10 @@ const arePropsEqual = (prev, next) => {
220
1630
  };
221
1631
  var renderer_default = memo(Renderer, arePropsEqual);
222
1632
  //#endregion
1633
+ //#region src/components/dnd/canvas/section-handlers.ts
1634
+ const SectionHandlersContext = createContext(null);
1635
+ const useSectionHandlers = () => useContext(SectionHandlersContext);
1636
+ //#endregion
223
1637
  //#region src/components/dnd/canvas/sortable.tsx
224
1638
  const NAVIGATION_KEYS = {
225
1639
  ArrowUp: "previous",
@@ -227,7 +1641,9 @@ const NAVIGATION_KEYS = {
227
1641
  Home: "first",
228
1642
  End: "last"
229
1643
  };
230
- const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy: _onCopy, onMoveUp, onMoveDown, canMoveUp = false, canMoveDown = false, nodeRef, onNavigate, onDeleteKey, inspecting = false, onInspectMove, onInspectPick, onInspectLeave }) => {
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;
231
1647
  const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging, isOver, active } = useSortable({
232
1648
  id,
233
1649
  disabled: inspecting
@@ -249,28 +1665,20 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
249
1665
  return;
250
1666
  }
251
1667
  const to = NAVIGATION_KEYS[e.key];
252
- if (to && onNavigate) {
1668
+ if (to && handlers) {
253
1669
  e.preventDefault();
254
- onNavigate(to);
1670
+ handlers.onNavigate(id, to);
255
1671
  return;
256
1672
  }
257
- if ((e.key === "Delete" || e.key === "Backspace") && onDeleteKey) {
1673
+ if ((e.key === "Delete" || e.key === "Backspace") && handlers) {
258
1674
  e.preventDefault();
259
- onDeleteKey();
1675
+ handlers.onDeleteKey(id);
260
1676
  }
261
1677
  };
262
1678
  const setRefs = (node) => {
263
1679
  setNodeRef(node);
264
1680
  setActivatorNodeRef(node);
265
- nodeRef?.(node);
266
- };
267
- const onDelete = (e) => {
268
- e.stopPropagation();
269
- _onDelete?.(id);
270
- };
271
- const onCopy = (e) => {
272
- e.stopPropagation();
273
- _onCopy?.(id);
1681
+ handlers?.nodeRef(id)(node);
274
1682
  };
275
1683
  const press = (action) => (e) => {
276
1684
  e.stopPropagation();
@@ -287,11 +1695,11 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
287
1695
  children: [
288
1696
  /* @__PURE__ */ jsx("div", {
289
1697
  className: cn("absolute inset-0 z-50"),
290
- onPointerMove: inspecting ? (e) => onInspectMove?.(e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY) : void 0,
291
- onPointerLeave: inspecting ? onInspectLeave : void 0,
1698
+ onPointerMove: inspecting ? (e) => handlers?.onInspectMove(e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY) : void 0,
1699
+ onPointerLeave: inspecting ? handlers?.onInspectLeave : void 0,
292
1700
  onClick: inspecting ? (e) => {
293
1701
  e.stopPropagation();
294
- onInspectPick?.(e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY);
1702
+ handlers?.onInspectPick(id, e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY);
295
1703
  } : void 0
296
1704
  }),
297
1705
  children,
@@ -312,13 +1720,13 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
312
1720
  }),
313
1721
  /* @__PURE__ */ jsx(Button, {
314
1722
  icon: /* @__PURE__ */ jsx(Copy, {}),
315
- onClick: onCopy,
1723
+ onClick: press(() => onCopy?.(id)),
316
1724
  "aria-label": messages.section.duplicate
317
1725
  }),
318
1726
  /* @__PURE__ */ jsx(Button, {
319
1727
  danger: true,
320
1728
  icon: /* @__PURE__ */ jsx(Trash, {}),
321
- onClick: onDelete,
1729
+ onClick: press(() => onDelete?.(id)),
322
1730
  "aria-label": messages.section.delete
323
1731
  })
324
1732
  ]
@@ -427,6 +1835,9 @@ const useDndPalette = () => useDndRegions("useDndPalette()").palette;
427
1835
  const useDndPanel = () => useDndRegions("useDndPanel()").panel;
428
1836
  const useDndLayout = () => useDndRegions("useDndLayout()");
429
1837
  //#endregion
1838
+ //#region src/components/dnd/panel/element-selection.ts
1839
+ const ElementSelectionContext = createContext({ ids: /* @__PURE__ */ new Set() });
1840
+ //#endregion
430
1841
  //#region src/components/dnd/panel-binding.ts
431
1842
  const readAttribute = (node, name) => node.dataAttributes.find((attribute) => attribute.name === name)?.value;
432
1843
  const canEditBindingValue = (node, binding) => {
@@ -446,7 +1857,16 @@ const toBindingFields = (binding) => ({
446
1857
  min: binding.min,
447
1858
  max: binding.max,
448
1859
  pattern: binding.pattern,
1860
+ patternMessage: binding.patternMessage,
449
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,
450
1870
  meta: binding.meta
451
1871
  });
452
1872
  const resolveRenderEntry = (render, key) => {
@@ -504,8 +1924,38 @@ const withPanelCommit = (bindings, commit) => bindings.map((binding) => ({
504
1924
  })
505
1925
  }));
506
1926
  //#endregion
507
- //#region src/components/dnd/panel/bulk-actions-bar.tsx
508
- const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
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 }) => {
509
1959
  const messages = useLiveMessages();
510
1960
  if (count === 0) return null;
511
1961
  return /* @__PURE__ */ jsxs("div", {
@@ -520,7 +1970,7 @@ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMove
520
1970
  size: "small",
521
1971
  icon: /* @__PURE__ */ jsx(Copy, {}),
522
1972
  title: messages.selection.duplicate,
523
- disabled,
1973
+ disabled: disabled || duplicateDisabled,
524
1974
  onClick: onDuplicate
525
1975
  }),
526
1976
  /* @__PURE__ */ jsx(Button, {
@@ -542,7 +1992,7 @@ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMove
542
1992
  size: "small",
543
1993
  icon: /* @__PURE__ */ jsx(X, {}),
544
1994
  title: messages.selection.delete,
545
- disabled,
1995
+ disabled: disabled || deleteDisabled,
546
1996
  onClick: onDelete
547
1997
  }),
548
1998
  /* @__PURE__ */ jsx(Button, {
@@ -555,36 +2005,33 @@ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMove
555
2005
  });
556
2006
  };
557
2007
  //#endregion
558
- //#region src/components/dnd/panel/node.tsx
559
- const Node = ({ data, onChange }) => {
560
- const source = resolvePanelBindings(data);
561
- if (!source) return null;
562
- return /* @__PURE__ */ jsx("div", {
563
- className: "space-y-2 rounded",
564
- children: /* @__PURE__ */ jsx("div", {
565
- className: "space-y-1",
566
- children: withPanelCommit(source.bindings, onChange).map((binding) => /* @__PURE__ */ jsxs("div", {
567
- className: "space-y-1",
568
- children: [/* @__PURE__ */ jsxs("label", {
569
- className: "block text-xs font-semibold text-gray-700",
570
- children: [binding.label, /* @__PURE__ */ jsxs("span", {
571
- className: "ml-1 text-gray-400",
572
- children: [
573
- "(",
574
- binding.property,
575
- ")"
576
- ]
577
- })]
578
- }), /* @__PURE__ */ jsx(Field, {
579
- binding,
580
- onNodeChange: onChange
581
- })]
582
- }, `${binding.id}-${binding.property}`))
583
- })
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
584
2019
  });
585
2020
  };
586
2021
  //#endregion
587
- //#region src/components/dnd/panel/use-structural-selection.ts
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
588
2035
  const selectionAfter = (command, selected, count) => {
589
2036
  switch (command.type) {
590
2037
  case "append":
@@ -618,7 +2065,7 @@ const useStructuralSelection = (count, revision) => {
618
2065
  };
619
2066
  };
620
2067
  //#endregion
621
- //#region src/components/dnd/panel/use-dnd-children.ts
2068
+ //#region src/components/dnd/panel/children/use-dnd-children.ts
622
2069
  const producedBy = (expected, action) => {
623
2070
  if (action.type === "move-selected") {
624
2071
  const moved = JSON.stringify(moveSelectedIndices(expected, new Set(action.indices), action.direction).items);
@@ -675,7 +2122,11 @@ const useDndChildren = (items, { onChange } = {}) => {
675
2122
  };
676
2123
  };
677
2124
  //#endregion
678
- //#region src/components/dnd/panel/children.tsx
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
+ };
679
2130
  const Children$1 = ({ value, onChange, onNodeChange }) => {
680
2131
  const items = useMemo(() => Array.isArray(value) ? value : [], [value]);
681
2132
  const { selection, actions } = useDndChildren(items, { onChange });
@@ -704,6 +2155,12 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
704
2155
  children: messages.children.add
705
2156
  })]
706
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
+ }),
707
2164
  /* @__PURE__ */ jsx(BulkActionsBar, {
708
2165
  count: selection.selected.size,
709
2166
  onDuplicate: actions.duplicateSelected,
@@ -719,43 +2176,19 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
719
2176
  className: "flex items-center justify-between",
720
2177
  children: [/* @__PURE__ */ jsxs("div", {
721
2178
  className: "flex items-center space-x-2",
722
- children: [/* @__PURE__ */ jsx("div", {
723
- onClick: (e) => selection.toggle(itemIndex, e.shiftKey),
724
- className: "inline-flex",
725
- children: /* @__PURE__ */ jsx(Checkbox, {
726
- checked: selection.isSelected(itemIndex),
727
- onChange: () => {}
728
- })
2179
+ children: [/* @__PURE__ */ jsx(SelectionCheckbox, {
2180
+ checked: selection.isSelected(itemIndex),
2181
+ onToggle: (shiftKey) => selection.toggle(itemIndex, shiftKey)
729
2182
  }), /* @__PURE__ */ jsx("div", {
730
2183
  className: "text-xs font-medium text-green-800",
731
2184
  children: messages.children.child(itemIndex + 1, item.tagName || "fragment")
732
2185
  })]
733
- }), /* @__PURE__ */ jsxs("div", {
734
- className: "flex space-x-1",
735
- children: [
736
- /* @__PURE__ */ jsx(Button, {
737
- size: "small",
738
- icon: /* @__PURE__ */ jsx(ArrowUp, {}),
739
- "aria-label": messages.children.moveUp,
740
- disabled: itemIndex === 0,
741
- onClick: () => actions.move(itemIndex, itemIndex - 1)
742
- }),
743
- /* @__PURE__ */ jsx(Button, {
744
- size: "small",
745
- icon: /* @__PURE__ */ jsx(ArrowDown, {}),
746
- "aria-label": messages.children.moveDown,
747
- disabled: itemIndex === items.length - 1,
748
- onClick: () => actions.move(itemIndex, itemIndex + 1)
749
- }),
750
- /* @__PURE__ */ jsx(Button, {
751
- "aria-label": messages.children.delete,
752
- title: messages.children.delete,
753
- danger: true,
754
- size: "small",
755
- icon: /* @__PURE__ */ jsx(X, {}),
756
- onClick: () => actions.remove(itemIndex)
757
- })
758
- ]
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)
759
2192
  })]
760
2193
  }),
761
2194
  editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
@@ -765,16 +2198,15 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
765
2198
  children: messages.children.editableBindings
766
2199
  }), editableChildrenMap.get(itemIndex).map((editableNode, idx) => {
767
2200
  const nodeId = editableNode.dataAttributes.find((a) => a.name === "data-id")?.value;
768
- return /* @__PURE__ */ jsxs("div", {
769
- "data-node-id": nodeId,
2201
+ return /* @__PURE__ */ jsx(ElementFields, {
2202
+ id: nodeId ?? void 0,
770
2203
  className: "rounded border border-green-100 bg-white p-2\n data-[picked]:ring-2 data-[picked]:ring-blue-400",
771
- children: [/* @__PURE__ */ jsx("div", {
2204
+ header: /* @__PURE__ */ jsx("div", {
772
2205
  className: "mb-1 text-xs text-green-600",
773
2206
  children: editableNode.tagName
774
- }), /* @__PURE__ */ jsx(Node, {
775
- data: editableNode,
776
- onChange: onNodeChange
777
- })]
2207
+ }),
2208
+ bindings: bindingsOf(editableNode, onNodeChange),
2209
+ onNodeChange
778
2210
  }, `editable-${nodeId || idx}`);
779
2211
  })]
780
2212
  }),
@@ -783,12 +2215,10 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
783
2215
  children: [/* @__PURE__ */ jsx("div", {
784
2216
  className: "text-xs font-medium text-green-700",
785
2217
  children: messages.children.childNodes
786
- }), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx("div", {
2218
+ }), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx(ElementFields, {
787
2219
  className: "ml-2 rounded border border-green-100 bg-white p-2",
788
- children: /* @__PURE__ */ jsx(Node, {
789
- data: node,
790
- onChange: onNodeChange
791
- })
2220
+ bindings: bindingsOf(node, onNodeChange),
2221
+ onNodeChange
792
2222
  }, `node-${itemIndex}-${nodeIndex}`))]
793
2223
  })
794
2224
  ]
@@ -797,7 +2227,70 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
797
2227
  });
798
2228
  };
799
2229
  //#endregion
800
- //#region src/components/dnd/panel/field-kind.ts
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
801
2294
  const isColorProperty = (property) => property.toLowerCase().includes("color");
802
2295
  const getFieldKind = (binding) => {
803
2296
  const { property, type, value } = binding;
@@ -812,13 +2305,63 @@ const getFieldKind = (binding) => {
812
2305
  if (binding.options && Array.isArray(binding.options)) return "select";
813
2306
  if (type === "color" || isColorProperty(property)) return "color";
814
2307
  if (type === "date") return "date";
2308
+ if (type === "time") return "time";
2309
+ if (type === "datetime") return "datetime";
815
2310
  if (type === "url") return "url";
2311
+ if (type === "email") return "email";
2312
+ if (type === "tel") return "tel";
816
2313
  if (type === "number" || typeof value === "number") return "number";
817
2314
  return "text";
818
2315
  };
819
2316
  const isStructuralFieldKind = (kind) => kind === "items" || kind === "children";
820
2317
  //#endregion
821
- //#region src/components/dnd/panel/sortable-items.tsx
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
822
2365
  const label = (messages, items, id) => {
823
2366
  const index = items.findIndex((item) => item.id === id);
824
2367
  return index >= 0 ? messages.items.item(index + 1) : String(id);
@@ -900,19 +2443,334 @@ const ItemCard = ({ id, title, disabled, expanded, onToggle, checkbox, controls,
900
2443
  });
901
2444
  };
902
2445
  //#endregion
903
- //#region src/components/dnd/panel/use-dnd-items.ts
904
- const createPendingCommit = () => {
905
- let current = null;
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);
906
2470
  return {
907
- read: () => current,
908
- write: (next) => {
909
- current = next;
910
- },
911
- clear: () => {
912
- current = null;
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)
913
2728
  }
2729
+ }))];
2730
+ return {
2731
+ id,
2732
+ index: item.index,
2733
+ elementIndex: item.elementIndex,
2734
+ properties,
2735
+ nested
914
2736
  };
915
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
916
2774
  const bindingsInJSX = (source, bindingOptions) => {
917
2775
  const nodes = extract(source, bindingOptions);
918
2776
  const container = nodes.find((n) => n.bindings?.some((b) => b.property === "children"));
@@ -978,49 +2836,29 @@ const parseSource = (value, bindingOptions) => {
978
2836
  parseError: false
979
2837
  };
980
2838
  };
981
- const createIdentityState = (value, kind, items, ids = items.map((item) => item.id)) => ({
982
- value,
983
- kind,
984
- ids,
985
- signatures: items.map((item) => item.source)
986
- });
987
- const reconcileIdentityState = (current, value, kind, items) => {
988
- if (current && current.value === value && current.kind === kind && current.ids.length === items.length) return current;
989
- if (!current || current.kind !== kind) return createIdentityState(value, kind, items);
990
- const previousCounts = /* @__PURE__ */ new Map();
991
- const nextCounts = /* @__PURE__ */ new Map();
992
- current.signatures.forEach((signature) => {
993
- previousCounts.set(signature, (previousCounts.get(signature) ?? 0) + 1);
994
- });
995
- items.forEach((item) => {
996
- nextCounts.set(item.source, (nextCounts.get(item.source) ?? 0) + 1);
997
- });
998
- const ids = items.map((item) => {
999
- const previousIndex = current.signatures.indexOf(item.source);
1000
- return previousIndex >= 0 && previousCounts.get(item.source) === 1 && nextCounts.get(item.source) === 1 ? current.ids[previousIndex] : item.id;
1001
- });
1002
- return createIdentityState(value, kind, items, ids);
1003
- };
1004
- const moveId = (ids, from, to) => {
1005
- const next = [...ids];
1006
- const [moved] = next.splice(from, 1);
1007
- next.splice(to, 0, moved);
1008
- return next;
1009
- };
1010
- const removeIds = (ids, indices) => {
1011
- return ids.filter((_, index) => !indices.has(index));
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
+ };
1012
2852
  };
1013
- const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
2853
+ //#endregion
2854
+ //#region src/components/dnd/panel/items/use-dnd-items.ts
2855
+ const useDndItems = (value, { render, onChange, onNodeChange, minItems, maxItems } = {}) => {
1014
2856
  const { reportError, bindingOptions } = useDndEditOptions();
1015
2857
  const messages = useLiveMessages();
1016
2858
  const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value, bindingOptions), [value, bindingOptions]);
1017
2859
  const canEditStructure = useMemo(() => canStructurallyEditArray(value), [value]);
1018
- const reportErrorRef = useRef(reportError);
1019
- const messagesRef = useRef(messages);
1020
- useEffect(() => {
1021
- reportErrorRef.current = reportError;
1022
- messagesRef.current = messages;
1023
- });
2860
+ const reportErrorRef = useLatest(reportError);
2861
+ const messagesRef = useLatest(messages);
1024
2862
  useEffect(() => {
1025
2863
  if (parseError) reportErrorRef.current({
1026
2864
  type: "parse",
@@ -1028,7 +2866,11 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
1028
2866
  title: messagesRef.current.editErrors.itemsParseFailed,
1029
2867
  description: messagesRef.current.editErrors.checkConsole
1030
2868
  });
1031
- }, [parseError]);
2869
+ }, [
2870
+ parseError,
2871
+ reportErrorRef,
2872
+ messagesRef
2873
+ ]);
1032
2874
  const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
1033
2875
  const isPrimitive = kind === "primitive";
1034
2876
  const rawItems = isPrimitive ? primitiveItems : objectItems;
@@ -1039,15 +2881,11 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
1039
2881
  rawItems,
1040
2882
  value
1041
2883
  ]);
1042
- const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
1043
2884
  const { selection, record } = useStructuralSelection(isPrimitive ? primitiveItems.length : objectItems.length, value);
1044
- const [pending] = useState(createPendingCommit);
1045
- useEffect(() => {
1046
- pending.clear();
1047
- });
2885
+ const pending = usePendingCommit(value);
1048
2886
  const latest = () => {
1049
2887
  const last = pending.read();
1050
- if (last?.from !== value) return {
2888
+ if (!last) return {
1051
2889
  value,
1052
2890
  items: rawItems,
1053
2891
  ids: identity.ids,
@@ -1080,7 +2918,6 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
1080
2918
  const nextItems = kind === "primitive" ? parsed.primitiveItems : parsed.objectItems;
1081
2919
  const ids = nextIds ?? snapshot.ids;
1082
2920
  pending.write({
1083
- from: value,
1084
2921
  value: next,
1085
2922
  ids,
1086
2923
  selected: nextSelection
@@ -1090,6 +2927,17 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
1090
2927
  onChange?.(next);
1091
2928
  return true;
1092
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
+ };
1093
2941
  const elementIndicesOf = (snapshot, indices) => new Set(snapshot.items.filter((item) => indices.has(item.index)).map((item) => item.elementIndex));
1094
2942
  const updateProperty = (elementIndex, propertyKey, next) => {
1095
2943
  const snapshot = latest();
@@ -1106,6 +2954,7 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
1106
2954
  const actions = {
1107
2955
  add: () => {
1108
2956
  const snapshot = latest();
2957
+ if (refuseCount(snapshot.items.length + 1)) return;
1109
2958
  const next = appendArrayItem(snapshot.value, kind);
1110
2959
  commit(snapshot, next, next ? [...snapshot.ids, nanoid(6)] : void 0, selectionAfter({ type: "append" }, snapshot.selected, snapshot.items.length));
1111
2960
  },
@@ -1119,12 +2968,13 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
1119
2968
  remove: (elementIndex) => {
1120
2969
  const snapshot = latest();
1121
2970
  const removed = locate(snapshot, elementIndex);
1122
- if (removed === null) return;
2971
+ if (removed === null || refuseCount(snapshot.items.length - 1)) return;
1123
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());
1124
2973
  },
1125
2974
  duplicateSelected: () => {
1126
2975
  const snapshot = latest();
1127
2976
  const selected = elementIndicesOf(snapshot, snapshot.selected);
2977
+ if (refuseCount(snapshot.items.length + selected.size)) return;
1128
2978
  const next = duplicateArrayItems(snapshot.value, selected);
1129
2979
  const copied = snapshot.items.filter((item) => selected.has(item.elementIndex)).sort((a, b) => a.elementIndex - b.elementIndex).map(() => nanoid(6));
1130
2980
  commit(snapshot, next, next ? [...snapshot.ids, ...copied] : void 0, selectionAfter({
@@ -1145,99 +2995,33 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
1145
2995
  },
1146
2996
  removeSelected: () => {
1147
2997
  const snapshot = latest();
1148
- commit(snapshot, removeArrayItems(snapshot.value, elementIndicesOf(snapshot, snapshot.selected), kind), removeIds(snapshot.ids, snapshot.selected), /* @__PURE__ */ new Set());
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());
1149
3001
  }
1150
3002
  };
1151
- const items = isPrimitive ? primitiveItems.map((item) => ({
3003
+ const items = rawItems.map((item) => toItemBindings(item, {
1152
3004
  id: identity.ids[item.index] ?? item.id,
1153
- index: item.index,
1154
- elementIndex: item.elementIndex,
1155
- properties: [],
1156
- nested: [],
1157
- value: {
1158
- id: `primitive-${item.id}`,
1159
- label: `item-${item.index}`,
1160
- property: item.type,
1161
- value: item.value ?? "",
1162
- rawValue: item.type === "unknown" ? item.source : String(item.value ?? ""),
1163
- canEditValue: canLosslesslyEvaluateSource(item.source),
1164
- onChange: (next) => updateValue(item.elementIndex, next)
1165
- }
1166
- })) : objectItems.map((item) => {
1167
- const id = identity.ids[item.index] ?? item.id;
1168
- const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
1169
- const binding = resolveRenderEntry(render, key);
1170
- const propertySource = value.slice(prop.astNode.start, prop.astNode.end);
1171
- return {
1172
- ...toBindingFields(binding),
1173
- id: `item-${id}-${key}`,
1174
- meta: {
1175
- ...binding.meta,
1176
- valueType: prop.type
1177
- },
1178
- value: parseValue(String(prop.value)),
1179
- rawValue: prop.type === "unknown" ? propertySource : String(prop.value),
1180
- canEditValue: canLosslesslyEvaluateSource(propertySource),
1181
- onChange: (next) => updateProperty(item.elementIndex, key, next)
1182
- };
1183
- });
1184
- const nested = [...Object.entries(item.jsxBindings).map(([property, nodes]) => ({
1185
- property,
1186
- elements: nodes.flatMap((node) => {
1187
- const source = resolvePanelBindings(node);
1188
- if (!source) return [];
1189
- return [{
1190
- id: source.id,
1191
- tagName: source.tagName,
1192
- bindings: withPanelCommit(source.bindings, onNodeChange)
1193
- }];
1194
- })
1195
- })), ...Object.entries(item.jsxFallbacks).map(([property, code]) => ({
1196
- property,
1197
- elements: [],
1198
- fallback: {
1199
- id: `item-${id}-${property}-jsx`,
1200
- label: property,
1201
- property,
1202
- type: "jsx",
1203
- value: code,
1204
- rawValue: code,
1205
- canEditValue: true,
1206
- onChange: (next) => updateProperty(item.elementIndex, property, next)
1207
- }
1208
- }))];
1209
- return {
1210
- id,
1211
- index: item.index,
1212
- elementIndex: item.elementIndex,
1213
- properties,
1214
- nested
1215
- };
1216
- });
3005
+ value,
3006
+ render,
3007
+ onNodeChange,
3008
+ onValueChange: (next) => updateValue(item.elementIndex, next),
3009
+ onPropertyChange: (key, next) => updateProperty(item.elementIndex, key, next)
3010
+ }));
1217
3011
  return {
1218
3012
  kind,
1219
3013
  items,
1220
3014
  selection,
1221
- expansion: {
1222
- expandedIds: items.flatMap((item) => collapsed.has(item.id) ? [] : [item.id]),
1223
- isExpanded: (id) => !collapsed.has(id),
1224
- toggle: (id) => setCollapsed((previous) => {
1225
- const next = new Set(previous);
1226
- if (!next.delete(id)) next.add(id);
1227
- return next;
1228
- }),
1229
- setExpanded: (ids) => {
1230
- const expanded = new Set(ids);
1231
- setCollapsed(new Set(items.flatMap((item) => expanded.has(item.id) ? [] : [item.id])));
1232
- }
1233
- },
3015
+ expansion: useItemExpansion(items.map((item) => item.id)),
1234
3016
  actions,
1235
3017
  canEditStructure,
3018
+ canAddItems: (count = 1) => maxItems === void 0 || items.length + count <= maxItems,
3019
+ canRemoveItems: (count = 1) => items.length - count >= fewestItems,
1236
3020
  parseError
1237
3021
  };
1238
3022
  };
1239
3023
  //#endregion
1240
- //#region src/components/dnd/panel/items.tsx
3024
+ //#region src/components/dnd/panel/items/items.tsx
1241
3025
  const NestedGroup = ({ group, onNodeChange }) => {
1242
3026
  if (group.fallback) return /* @__PURE__ */ jsxs("div", {
1243
3027
  className: "space-y-2",
@@ -1259,48 +3043,30 @@ const NestedGroup = ({ group, onNodeChange }) => {
1259
3043
  group.elements.length,
1260
3044
  "):"
1261
3045
  ]
1262
- }), group.elements.map((element) => /* @__PURE__ */ jsxs("div", {
1263
- "data-node-id": element.id,
3046
+ }), group.elements.map((element) => /* @__PURE__ */ jsx(ElementFields, {
3047
+ id: element.id,
1264
3048
  className: "rounded border border-blue-100 bg-blue-50 p-2\n data-[picked]:ring-2 data-[picked]:ring-blue-400",
1265
- children: [/* @__PURE__ */ jsxs("div", {
3049
+ header: /* @__PURE__ */ jsxs("div", {
1266
3050
  className: "mb-1 text-xs text-blue-600",
1267
3051
  children: [
1268
3052
  "<",
1269
3053
  element.tagName,
1270
3054
  ">"
1271
3055
  ]
1272
- }), /* @__PURE__ */ jsx("div", {
1273
- className: "space-y-2 rounded",
1274
- children: /* @__PURE__ */ jsx("div", {
1275
- className: "space-y-1",
1276
- children: element.bindings.map((binding) => /* @__PURE__ */ jsxs("div", {
1277
- className: "space-y-1",
1278
- children: [/* @__PURE__ */ jsxs("label", {
1279
- className: "block text-xs font-semibold text-gray-700",
1280
- children: [binding.label, /* @__PURE__ */ jsxs("span", {
1281
- className: "ml-1 text-gray-400",
1282
- children: [
1283
- "(",
1284
- binding.property,
1285
- ")"
1286
- ]
1287
- })]
1288
- }), /* @__PURE__ */ jsx(Field, {
1289
- binding,
1290
- onNodeChange
1291
- })]
1292
- }, `${binding.property}-${binding.label}`))
1293
- })
1294
- })]
3056
+ }),
3057
+ bindings: element.bindings,
3058
+ onNodeChange
1295
3059
  }, `${group.property}-${element.id}`))]
1296
3060
  });
1297
3061
  };
1298
- const Items = ({ value, render, onChange, onChildChange }) => {
3062
+ const Items = ({ value, render, minItems, maxItems, onChange, onChildChange }) => {
1299
3063
  const messages = useLiveMessages();
1300
- const { kind, items, selection, expansion, actions, canEditStructure, parseError } = useDndItems(value, {
3064
+ const { kind, items, selection, expansion, actions, canEditStructure, canAddItems, canRemoveItems, parseError } = useDndItems(value, {
1301
3065
  render,
1302
3066
  onChange,
1303
- onNodeChange: onChildChange
3067
+ onNodeChange: onChildChange,
3068
+ minItems,
3069
+ maxItems
1304
3070
  });
1305
3071
  const header = /* @__PURE__ */ jsxs("div", {
1306
3072
  className: "flex items-center justify-between",
@@ -1312,6 +3078,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1312
3078
  icon: /* @__PURE__ */ jsx(Plus, {}),
1313
3079
  variant: "solid",
1314
3080
  color: "green",
3081
+ disabled: !canAddItems(),
1315
3082
  onClick: actions.add,
1316
3083
  children: messages.items.add
1317
3084
  })]
@@ -1333,59 +3100,27 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1333
3100
  const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
1334
3101
  count: selection.selected.size,
1335
3102
  disabled: !canEditStructure,
3103
+ duplicateDisabled: !canAddItems(selection.selected.size),
3104
+ deleteDisabled: !canRemoveItems(selection.selected.size),
1336
3105
  onDuplicate: actions.duplicateSelected,
1337
3106
  onMoveUp: () => actions.moveSelected("up"),
1338
3107
  onMoveDown: () => actions.moveSelected("down"),
1339
3108
  onDelete: actions.removeSelected,
1340
3109
  onClear: selection.clear
1341
3110
  });
1342
- const itemControls = (item) => /* @__PURE__ */ jsxs("div", {
1343
- className: "flex space-x-1",
1344
- children: [
1345
- /* @__PURE__ */ jsx(Button, {
1346
- size: "small",
1347
- icon: /* @__PURE__ */ jsx(ArrowUp, {}),
1348
- "aria-label": messages.items.moveUp,
1349
- disabled: !canEditStructure || item.index === 0,
1350
- onClick: () => actions.move(item.elementIndex, item.index - 1)
1351
- }),
1352
- /* @__PURE__ */ jsx(Button, {
1353
- size: "small",
1354
- icon: /* @__PURE__ */ jsx(ArrowDown, {}),
1355
- "aria-label": messages.items.moveDown,
1356
- disabled: !canEditStructure || item.index === items.length - 1,
1357
- onClick: () => actions.move(item.elementIndex, item.index + 1)
1358
- }),
1359
- /* @__PURE__ */ jsx(Button, {
1360
- danger: true,
1361
- size: "small",
1362
- icon: /* @__PURE__ */ jsx(X, {}),
1363
- "aria-label": messages.items.delete,
1364
- disabled: !canEditStructure || items.length <= 1,
1365
- onClick: () => actions.remove(item.elementIndex)
1366
- })
1367
- ]
1368
- });
1369
- const itemCheckbox = (item) => /* @__PURE__ */ jsx("div", {
1370
- onClick: (e) => canEditStructure && selection.toggle(item.index, e.shiftKey),
1371
- className: "inline-flex",
1372
- children: /* @__PURE__ */ jsx(Checkbox, {
1373
- checked: selection.isSelected(item.index),
1374
- disabled: !canEditStructure,
1375
- onChange: () => {}
1376
- })
1377
- });
1378
3111
  const objectBody = (item) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
1379
3112
  className: "space-y-2",
1380
- children: item.properties.map((binding) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
3113
+ children: item.properties.map((binding) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
1381
3114
  className: "flex flex-col space-y-2",
1382
- children: [/* @__PURE__ */ jsx("label", {
1383
- className: "w-20 shrink-0 text-xs font-medium",
1384
- children: binding.label
1385
- }), /* @__PURE__ */ jsx(Field, {
3115
+ children: /* @__PURE__ */ jsx(LabeledField, {
1386
3116
  binding,
1387
- onNodeChange: onChildChange
1388
- })]
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
+ })
1389
3124
  }), /* @__PURE__ */ jsxs("span", {
1390
3125
  className: "text-right text-xs text-gray-500",
1391
3126
  children: [
@@ -1416,14 +3151,20 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1416
3151
  className: "space-y-4",
1417
3152
  children: [
1418
3153
  header,
1419
- items.length > 1 && /* @__PURE__ */ jsx("div", {
1420
- className: "flex justify-end",
1421
- children: /* @__PURE__ */ jsx(Button, {
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, {
1422
3163
  size: "small",
1423
3164
  variant: "text",
1424
3165
  onClick: () => expansion.setExpanded(allExpanded ? [] : items.map((item) => item.id)),
1425
3166
  children: allExpanded ? messages.items.collapseAll : messages.items.expandAll
1426
- })
3167
+ })]
1427
3168
  }),
1428
3169
  bulkBar,
1429
3170
  !canEditStructure && /* @__PURE__ */ jsx("div", {
@@ -1440,8 +3181,20 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1440
3181
  disabled: !canEditStructure,
1441
3182
  expanded: expansion.isExpanded(item.id),
1442
3183
  onToggle: () => expansion.toggle(item.id),
1443
- checkbox: itemCheckbox(item),
1444
- controls: itemControls(item),
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
+ }),
1445
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",
1446
3199
  children: kind === "primitive" ? /* @__PURE__ */ jsx(Field, {
1447
3200
  binding: item.value,
@@ -1454,22 +3207,18 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1454
3207
  };
1455
3208
  //#endregion
1456
3209
  //#region src/components/dnd/panel/field.tsx
1457
- const normalizeToHex = (value) => {
1458
- const trimmed = value.trim();
1459
- if (/^#([0-9A-Fa-f]{3}){1,2}$/.test(trimmed)) return trimmed;
1460
- return "#000000";
1461
- };
1462
3210
  const parseDateValue = (value) => {
1463
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
3211
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?$/.exec(value.trim());
1464
3212
  if (!match) return;
1465
- const [, year, month, day] = match;
1466
- const date = new Date(Number(year), Number(month) - 1, Number(day));
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));
1467
3215
  return Number.isNaN(date.getTime()) ? void 0 : date;
1468
3216
  };
1469
- const formatDateValue = (date) => {
1470
- return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
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;
1471
3221
  };
1472
- const COLOR_COMMIT_DELAY = 75;
1473
3222
  const RICHTEXT_TOOLBAR = [
1474
3223
  "bold",
1475
3224
  "italic",
@@ -1478,69 +3227,9 @@ const RICHTEXT_TOOLBAR = [
1478
3227
  "orderedList",
1479
3228
  "link"
1480
3229
  ];
1481
- const JSXEditorField = ({ value, isHTML, onSave }) => {
1482
- const selectionRef = useRef(null);
1483
- const focusedRef = useRef(false);
1484
- const handleUpdate = useCallback((update) => {
1485
- selectionRef.current = update.state.selection;
1486
- }, []);
1487
- const handleCreateEditor = useCallback((view) => {
1488
- if (selectionRef.current) view.dispatch({ selection: selectionRef.current });
1489
- if (focusedRef.current) view.focus();
1490
- }, []);
1491
- const handleFocus = useCallback(() => {
1492
- focusedRef.current = true;
1493
- }, []);
1494
- const handleBlur = useCallback((event) => {
1495
- if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) focusedRef.current = false;
1496
- }, []);
1497
- return /* @__PURE__ */ jsx("div", {
1498
- onFocusCapture: handleFocus,
1499
- onBlurCapture: handleBlur,
1500
- children: /* @__PURE__ */ jsx(Core, {
1501
- value,
1502
- height: "150px",
1503
- fragment: !isHTML,
1504
- raw: isHTML,
1505
- onSave,
1506
- onUpdate: handleUpdate,
1507
- onCreateEditor: handleCreateEditor
1508
- })
1509
- });
1510
- };
1511
- const ColorPickerField = ({ value, onChange }) => {
1512
- const [liveValue, setLiveValue] = useState(value);
1513
- const [prevValue, setPrevValue] = useState(value);
1514
- const lastCommittedRef = useRef(value);
1515
- if (value !== prevValue) {
1516
- setPrevValue(value);
1517
- setLiveValue(value);
1518
- }
1519
- useEffect(() => {
1520
- lastCommittedRef.current = value;
1521
- }, [value]);
1522
- const commit = (next) => {
1523
- if (next === lastCommittedRef.current) return;
1524
- lastCommittedRef.current = next;
1525
- onChange(next);
1526
- };
1527
- const debouncedCommit = useDebounce(() => commit(liveValue), {
1528
- delay: COLOR_COMMIT_DELAY,
1529
- autoInvoke: false
1530
- });
1531
- return /* @__PURE__ */ jsx(ColorPicker, {
1532
- showText: true,
1533
- value: liveValue,
1534
- onChange: (next) => {
1535
- setLiveValue(next);
1536
- debouncedCommit();
1537
- },
1538
- onOpenChange: (open) => {
1539
- if (!open) commit(liveValue);
1540
- }
1541
- });
1542
- };
1543
- const FieldError = ({ message }) => message ? /* @__PURE__ */ jsx("p", {
3230
+ const FieldError = ({ id, message }) => message ? /* @__PURE__ */ jsx("p", {
3231
+ id,
3232
+ role: "alert",
1544
3233
  className: "mt-1 mb-0 text-xs text-red-500",
1545
3234
  children: message
1546
3235
  }) : null;
@@ -1549,6 +3238,13 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1549
3238
  const validationOptions = { messages: messages.validation };
1550
3239
  const { id, value, rawValue, onChange } = binding;
1551
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
+ };
1552
3248
  const [text, setText] = useState(rawValue);
1553
3249
  const [prevRawValue, setPrevRawValue] = useState(rawValue);
1554
3250
  if (rawValue !== prevRawValue) {
@@ -1570,9 +3266,20 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1570
3266
  const commitIfChanged = (next) => {
1571
3267
  if (next !== (typeof next === "string" ? rawValue : value)) onChange(next);
1572
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
+ };
1573
3278
  if (kind === "items") return /* @__PURE__ */ jsx(Items, {
1574
3279
  value: rawValue,
1575
3280
  render: binding.render,
3281
+ minItems: binding.minItems,
3282
+ maxItems: binding.maxItems,
1576
3283
  onChange,
1577
3284
  onChildChange: onNodeChange
1578
3285
  });
@@ -1597,26 +3304,31 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1597
3304
  className: "space-y-2 rounded border border-gray-200 bg-gray-50 p-2",
1598
3305
  children: Object.entries(objectValue).map(([key, val]) => {
1599
3306
  const nested = resolveRenderEntry(binding.render, key);
1600
- return /* @__PURE__ */ jsxs("div", {
3307
+ return /* @__PURE__ */ jsx("div", {
1601
3308
  className: "space-y-1",
1602
- children: [/* @__PURE__ */ jsx("label", {
1603
- className: "block text-xs font-medium text-gray-600",
1604
- children: nested.label
1605
- }), /* @__PURE__ */ jsx(Field, {
3309
+ children: /* @__PURE__ */ jsx(LabeledField, {
1606
3310
  binding: {
1607
- ...toBindingFields(nested),
1608
- id,
1609
- value: val,
1610
- rawValue: typeof val === "object" && val !== null ? JSON.stringify(val) : String(val),
1611
- onChange: (next) => {
1612
- onChange({
1613
- ...objectValue,
1614
- [key]: next
1615
- });
1616
- }
3311
+ ...nested,
3312
+ value: val
1617
3313
  },
1618
- onNodeChange
1619
- })]
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
+ })
1620
3332
  }, key);
1621
3333
  })
1622
3334
  });
@@ -1629,76 +3341,76 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1629
3341
  });
1630
3342
  const stringValue = rawValue;
1631
3343
  if (kind === "select") return /* @__PURE__ */ jsx(Select, {
3344
+ "aria-label": binding.label,
1632
3345
  value: stringValue,
1633
3346
  options: binding.options ?? [],
1634
3347
  onChange: commitIfChanged
1635
3348
  });
1636
3349
  if (kind === "color") return /* @__PURE__ */ jsx(ColorPickerField, {
1637
- value: normalizeToHex(stringValue),
3350
+ value: stringValue,
3351
+ label: binding.label,
1638
3352
  onChange: commitIfChanged
1639
3353
  });
1640
- if (kind === "date") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DatePicker, {
1641
- value: parseDateValue(stringValue),
1642
- onChange: (date) => {
1643
- const next = date ? formatDateValue(date) : "";
1644
- const result = validateBindingValue(binding, next, validationOptions);
1645
- if (!result.valid) {
1646
- setValidationError(result.message ?? messages.panel.invalidValue);
1647
- return;
1648
- }
1649
- setValidationError(null);
1650
- commitIfChanged(next);
1651
- }
1652
- }), /* @__PURE__ */ jsx(FieldError, { message: validationError })] });
1653
- if (kind === "url") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
1654
- type: "url",
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,
1655
3376
  value: text,
1656
3377
  onChange: (e) => setText(e.target.value),
1657
- placeholder: messages.panel.urlPlaceholder,
1658
- onBlur: (e) => {
1659
- const next = e.target.value.trim();
1660
- const result = validateBindingValue(binding, next, validationOptions);
1661
- if (!result.valid) {
1662
- setValidationError(result.message ?? messages.panel.invalidValue);
1663
- return;
1664
- }
1665
- setValidationError(null);
1666
- commitIfChanged(next);
1667
- }
1668
- }), /* @__PURE__ */ jsx(FieldError, { message: validationError })] });
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
+ })] });
1669
3384
  if (kind === "number") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
3385
+ ...controlProps,
1670
3386
  type: "number",
3387
+ step: binding.step,
1671
3388
  value: text,
1672
3389
  onChange: (e) => setText(e.target.value),
1673
3390
  placeholder: messages.panel.numberPlaceholder,
1674
3391
  onBlur: (e) => {
1675
3392
  const raw = e.target.value.trim();
1676
3393
  const next = raw !== "" && !Number.isNaN(Number(raw)) ? Number(raw) : raw;
1677
- const result = validateBindingValue(binding, next, validationOptions);
1678
- if (!result.valid) {
1679
- setValidationError(result.message ?? messages.panel.invalidValue);
1680
- return;
1681
- }
1682
- setValidationError(null);
1683
- commitIfChanged(next);
3394
+ validateAndCommit(next);
1684
3395
  }
1685
- }), /* @__PURE__ */ jsx(FieldError, { message: validationError })] });
3396
+ }), /* @__PURE__ */ jsx(FieldError, {
3397
+ id: errorId,
3398
+ message: validationError
3399
+ })] });
1686
3400
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input.TextArea, {
3401
+ ...controlProps,
1687
3402
  value: text,
1688
3403
  onChange: (e) => setText(e.target.value),
1689
3404
  placeholder: messages.panel.textPlaceholder,
1690
3405
  onBlur: (e) => {
1691
3406
  const raw = e.target.value.trim();
1692
3407
  const next = binding.type ? raw : parseValue(raw);
1693
- const result = validateBindingValue(binding, next, validationOptions);
1694
- if (!result.valid) {
1695
- setValidationError(result.message ?? messages.panel.invalidValue);
1696
- return;
1697
- }
1698
- setValidationError(null);
1699
- commitIfChanged(next);
3408
+ validateAndCommit(next);
1700
3409
  }
1701
- }), /* @__PURE__ */ jsx(FieldError, { message: validationError })] });
3410
+ }), /* @__PURE__ */ jsx(FieldError, {
3411
+ id: errorId,
3412
+ message: validationError
3413
+ })] });
1702
3414
  };
1703
3415
  const Field = (props) => {
1704
3416
  const { renderField } = useDndEditOptions();
@@ -1708,30 +3420,63 @@ const Field = (props) => {
1708
3420
  return custom === void 0 ? builtin : /* @__PURE__ */ jsx(Fragment, { children: custom });
1709
3421
  };
1710
3422
  //#endregion
1711
- //#region src/components/dnd/panel/field-group.tsx
1712
- const FieldGroup = ({ bindings, onNodeChange }) => /* @__PURE__ */ jsx("div", {
1713
- className: "space-y-2 rounded",
1714
- children: /* @__PURE__ */ jsx("div", {
1715
- className: "space-y-1",
1716
- children: bindings.map((binding) => /* @__PURE__ */ jsxs("div", {
1717
- className: "space-y-1",
1718
- children: [/* @__PURE__ */ jsxs("label", {
1719
- className: "block text-xs font-semibold text-gray-700",
1720
- children: [binding.label, /* @__PURE__ */ jsxs("span", {
1721
- className: "ml-1 text-gray-400",
1722
- children: [
1723
- "(",
1724
- binding.property,
1725
- ")"
1726
- ]
1727
- })]
1728
- }), /* @__PURE__ */ jsx(Field, {
1729
- binding,
1730
- onNodeChange
1731
- })]
1732
- }, `${binding.property}-${binding.label}`))
1733
- })
1734
- });
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
+ };
1735
3480
  //#endregion
1736
3481
  //#region src/components/dnd/panel/panel.tsx
1737
3482
  const groupBindingsById = (bindings) => {
@@ -1767,26 +3512,10 @@ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canM
1767
3512
  highlight(targetId);
1768
3513
  return () => highlight(null);
1769
3514
  }, [highlight, targetId]);
1770
- useEffect(() => {
1771
- const root = rootRef.current;
1772
- if (!root || !pickedId) return;
1773
- const target = root.querySelector(`[data-node-id="${CSS.escape(pickedId)}"]`);
1774
- if (!target) return;
1775
- target.setAttribute("data-picked", "");
1776
- const until = performance.now() + 600;
1777
- let frame = 0;
1778
- const keepInView = () => {
1779
- const box = target.getBoundingClientRect();
1780
- const view = root.getBoundingClientRect();
1781
- if (box.top < view.top || box.bottom > view.bottom) target.scrollIntoView?.({ block: "nearest" });
1782
- if (performance.now() < until) frame = requestAnimationFrame(keepInView);
1783
- };
1784
- keepInView();
1785
- return () => {
1786
- cancelAnimationFrame(frame);
1787
- target.removeAttribute("data-picked");
1788
- };
1789
- }, [pickedId, groups.length]);
3515
+ const selection = useMemo(() => ({
3516
+ ids: new Set(pickedId ? [pickedId] : []),
3517
+ scrollRootRef: rootRef
3518
+ }), [pickedId]);
1790
3519
  if (!item) return /* @__PURE__ */ jsx(Typography.Paragraph, {
1791
3520
  className: cn("p-4 text-sm text-gray-500"),
1792
3521
  children: messages.panel.selectSection
@@ -1839,27 +3568,29 @@ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canM
1839
3568
  className: "text-xs text-gray-400",
1840
3569
  children: messages.panel.noEditableElements
1841
3570
  }),
1842
- groups.map((group) => {
1843
- const element = describeElement(group[0]);
1844
- return /* @__PURE__ */ jsxs("div", {
1845
- "data-node-id": group[0].id,
1846
- role: "group",
1847
- "aria-label": element.label,
1848
- className: "rounded data-[picked]:ring-2 data-[picked]:ring-blue-400\n data-[picked]:ring-offset-2",
1849
- children: [groups.length > 1 && /* @__PURE__ */ jsxs("div", {
1850
- className: "mb-2 flex min-w-0 items-baseline gap-1.5 border-b\n border-gray-100 pb-1 text-xs",
1851
- children: [/* @__PURE__ */ jsx("code", {
1852
- className: "shrink-0 text-gray-500",
1853
- children: element.tagName
1854
- }), element.text && /* @__PURE__ */ jsx("span", {
1855
- className: "truncate text-gray-400",
1856
- children: element.text
1857
- })]
1858
- }), /* @__PURE__ */ jsx(FieldGroup, {
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
+ }),
1859
3590
  bindings: group,
1860
3591
  onNodeChange
1861
- })]
1862
- }, group[0].id);
3592
+ }, group[0].id);
3593
+ })
1863
3594
  })
1864
3595
  ]
1865
3596
  });
@@ -1988,38 +3719,112 @@ const Layout = ({ palette, panel }) => {
1988
3719
  ] });
1989
3720
  };
1990
3721
  //#endregion
1991
- //#region src/components/dnd/state/use-delete-flow.ts
1992
- const useDeleteFlow = ({ sections, remove, onBeforeDelete }) => {
1993
- const removeRef = useRef(remove);
1994
- useEffect(() => {
1995
- removeRef.current = remove;
1996
- });
1997
- return (id, onDeleted) => {
1998
- const section = sections.find((candidate) => candidate.id === id);
1999
- if (!onBeforeDelete || !section) {
2000
- remove(id);
2001
- onDeleted?.();
2002
- return;
2003
- }
2004
- const decide = (allowed) => {
2005
- if (allowed) {
2006
- removeRef.current(id);
2007
- onDeleted?.();
2008
- }
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)
2009
3728
  };
2010
- try {
2011
- const answer = onBeforeDelete(section);
2012
- if (typeof answer === "boolean") {
2013
- decide(answer);
2014
- return;
2015
- }
2016
- answer.then(decide, (error) => {
2017
- console.error("onBeforeDelete rejected; the section was kept", error);
2018
- });
2019
- } catch (error) {
2020
- console.error("onBeforeDelete threw; the section was kept", error);
2021
- }
2022
- };
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
+ };
2023
3828
  };
2024
3829
  //#endregion
2025
3830
  //#region src/components/dnd/state/use-dnd-keyboard.ts
@@ -2115,44 +3920,16 @@ const useInspectorState = ({ selectedId, selectOnly, sectionNodes, onNodePick })
2115
3920
  const [picked, setPicked] = useState(null);
2116
3921
  const [hoverRect, setHoverRect] = useState(null);
2117
3922
  const [fieldTarget, setFieldTarget] = useState(null);
2118
- const [fieldRect, setFieldRect] = useState(null);
2119
3923
  if (picked && picked.sectionId !== selectedId) setPicked(null);
2120
3924
  const stopInspecting = useCallback(() => {
2121
3925
  setInspecting(false);
2122
3926
  setHoverRect(null);
2123
3927
  }, []);
2124
- useEffect(() => {
2125
- if (!inspecting) return;
2126
- const onKeyDown = (e) => {
2127
- if (e.key === "Escape") stopInspecting();
2128
- };
2129
- window.addEventListener("keydown", onKeyDown);
2130
- return () => window.removeEventListener("keydown", onKeyDown);
2131
- }, [inspecting, stopInspecting]);
2132
- useEffect(() => {
2133
- const section = selectedId === null ? null : sectionNodes.get(selectedId);
2134
- if (!fieldTarget || !section) return;
2135
- let frame = 0;
2136
- const measure = () => {
2137
- cancelAnimationFrame(frame);
2138
- frame = requestAnimationFrame(() => {
2139
- const element = findElementById(section, fieldTarget);
2140
- setFieldRect(element ? viewportRect(element) : null);
2141
- });
2142
- };
2143
- measure();
2144
- window.addEventListener("scroll", measure, true);
2145
- window.addEventListener("resize", measure);
2146
- return () => {
2147
- cancelAnimationFrame(frame);
2148
- window.removeEventListener("scroll", measure, true);
2149
- window.removeEventListener("resize", measure);
2150
- };
2151
- }, [
2152
- fieldTarget,
2153
- selectedId,
2154
- sectionNodes
2155
- ]);
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 });
2156
3933
  const inspector = {
2157
3934
  active: inspecting,
2158
3935
  activate: () => setInspecting(true),
@@ -2241,37 +4018,27 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
2241
4018
  ]);
2242
4019
  const selectedIndex = sections.findIndex((s) => s.id === selectedId);
2243
4020
  const selectedSection = selectedIndex >= 0 ? sections[selectedIndex] : void 0;
2244
- const pendingRef = useRef(null);
2245
- useEffect(() => {
2246
- pendingRef.current = null;
2247
- });
2248
- const latestDocument = useCallback(() => {
2249
- const pending = pendingRef.current;
2250
- return pending?.from === document ? pending.code : document;
2251
- }, [document]);
4021
+ const pending = usePendingCommit(document);
4022
+ const latestDocument = useCallback(() => pending.read() ?? document, [document, pending]);
2252
4023
  const latestSections = useCallback(() => readSections(latestDocument()), [latestDocument, readSections]);
2253
4024
  const commit = useCallback((nextSections) => {
2254
4025
  const nextCode = fillSectionIds(replaceSections(latestDocument(), nextSections.map((s) => s.code), { containerId }), void 0, { containerId });
2255
- pendingRef.current = {
2256
- from: document,
2257
- code: nextCode
2258
- };
4026
+ pending.write(nextCode);
2259
4027
  onChange?.(nextCode);
2260
4028
  setCode(nextCode);
2261
4029
  return readSections(nextCode);
2262
4030
  }, [
2263
4031
  containerId,
2264
- document,
2265
4032
  latestDocument,
2266
4033
  onChange,
4034
+ pending,
2267
4035
  readSections,
2268
4036
  setCode
2269
4037
  ]);
2270
4038
  const getCommittedSection = useCallback((id) => {
2271
- const pending = pendingRef.current;
2272
- if (pending?.from !== document) return;
2273
- return readSections(pending.code).find((s) => s.id === id);
2274
- }, [document, readSections]);
4039
+ const code = pending.read();
4040
+ return code === void 0 ? void 0 : readSections(code).find((s) => s.id === id);
4041
+ }, [pending, readSections]);
2275
4042
  const { problem, stale } = view;
2276
4043
  const refuse = useCallback(() => {
2277
4044
  if (!stale || !problem) return false;
@@ -2282,6 +4049,10 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
2282
4049
  problem,
2283
4050
  stale
2284
4051
  ]);
4052
+ const mutate = useCallback((fn) => {
4053
+ if (refuse()) return;
4054
+ fn(latestSections());
4055
+ }, [latestSections, refuse]);
2285
4056
  return {
2286
4057
  sections,
2287
4058
  previews,
@@ -2293,36 +4064,25 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
2293
4064
  }, []),
2294
4065
  selectOnly: useCallback((id) => setSelectedId(id), []),
2295
4066
  clearSelection: useCallback(() => setSelectedId(null), []),
2296
- add: useCallback((section, atIndex) => {
2297
- if (refuse()) return;
2298
- const current = latestSections();
4067
+ add: useCallback((section, atIndex) => mutate((current) => {
2299
4068
  const next = { code: section.code };
2300
4069
  commit(atIndex === void 0 || atIndex < 0 ? [...current, next] : [
2301
4070
  ...current.slice(0, atIndex),
2302
4071
  next,
2303
4072
  ...current.slice(atIndex)
2304
4073
  ]);
2305
- }, [
2306
- commit,
2307
- refuse,
2308
- latestSections
2309
- ]),
2310
- remove: useCallback((id) => {
2311
- if (refuse()) return;
2312
- const current = latestSections();
4074
+ }), [commit, mutate]),
4075
+ remove: useCallback((id) => mutate((current) => {
2313
4076
  const index = current.findIndex((s) => s.id === id);
2314
4077
  if (index < 0) return;
2315
4078
  if (id === selectedId) setSelectedId(null);
2316
4079
  commit(current.filter((_, i) => i !== index));
2317
- }, [
4080
+ }), [
2318
4081
  commit,
2319
- refuse,
2320
- latestSections,
4082
+ mutate,
2321
4083
  selectedId
2322
4084
  ]),
2323
- copy: useCallback((id) => {
2324
- if (refuse()) return;
2325
- const current = latestSections();
4085
+ copy: useCallback((id) => mutate((current) => {
2326
4086
  const index = current.findIndex((s) => s.id === id);
2327
4087
  const source = current[index];
2328
4088
  if (!source) return;
@@ -2332,192 +4092,33 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
2332
4092
  ...current.slice(index + 1)
2333
4093
  ]);
2334
4094
  setSelectedId(committed[index + 1]?.id ?? null);
2335
- }, [
2336
- commit,
2337
- refuse,
2338
- latestSections
2339
- ]),
2340
- move: useCallback((id, direction) => {
2341
- if (refuse()) return;
2342
- const current = latestSections();
4095
+ }), [commit, mutate]),
4096
+ move: useCallback((id, direction) => mutate((current) => {
2343
4097
  const index = current.findIndex((s) => s.id === id);
2344
4098
  const targetIndex = direction === "up" ? index - 1 : index + 1;
2345
4099
  if (index < 0 || targetIndex < 0 || targetIndex >= current.length) return;
2346
4100
  commit(arrayMove(current, index, targetIndex));
2347
- }, [
2348
- commit,
2349
- refuse,
2350
- latestSections
2351
- ]),
2352
- reorder: useCallback((activeId, overId) => {
2353
- if (refuse()) return;
2354
- const current = latestSections();
4101
+ }), [commit, mutate]),
4102
+ reorder: useCallback((activeId, overId) => mutate((current) => {
2355
4103
  const prevIndex = current.findIndex((s) => s.id === activeId);
2356
4104
  const nextIndex = current.findIndex((s) => s.id === overId);
2357
4105
  if (prevIndex < 0 || nextIndex < 0 || prevIndex === nextIndex) return;
2358
4106
  commit(arrayMove(current, prevIndex, nextIndex));
2359
- }, [
2360
- commit,
2361
- refuse,
2362
- latestSections
2363
- ]),
2364
- patch: useCallback((next) => {
2365
- if (refuse()) return;
2366
- const current = latestSections();
4107
+ }), [commit, mutate]),
4108
+ patch: useCallback((next) => mutate((current) => {
2367
4109
  commit(current.map((s) => s.id === next.id ? {
2368
4110
  ...s,
2369
4111
  ...next
2370
4112
  } : s));
2371
- }, [
2372
- commit,
2373
- refuse,
2374
- latestSections
2375
- ]),
4113
+ }), [commit, mutate]),
2376
4114
  getCommittedSection,
2377
4115
  problem,
2378
4116
  stale
2379
4117
  };
2380
4118
  };
2381
4119
  //#endregion
2382
- //#region src/components/dnd/dnd.tsx
2383
- const describeUpdateFailure = (failure, label, { editErrors: m }) => {
2384
- switch (failure?.reason) {
2385
- case "attribute-not-found": return {
2386
- title: m.updateFailed(label),
2387
- description: m.attributeNotFound(failure.property)
2388
- };
2389
- case "binding-not-declared": return {
2390
- title: m.updateFailed(label),
2391
- description: m.bindingNotDeclared({
2392
- label,
2393
- property: failure.property
2394
- })
2395
- };
2396
- case "duplicate-binding": return {
2397
- title: m.updateFailed(label),
2398
- description: m.duplicateBinding({
2399
- count: failure.count,
2400
- label,
2401
- property: failure.property
2402
- })
2403
- };
2404
- case "reserved-property": return {
2405
- title: m.cannotEdit(label),
2406
- description: m.reservedProperty(failure.property)
2407
- };
2408
- case "required-property": return {
2409
- title: m.cannotRemove(label),
2410
- description: m.requiredProperty(failure.property)
2411
- };
2412
- case "no-binding": return {
2413
- title: m.updateFailed(label),
2414
- description: m.noBinding
2415
- };
2416
- case "element-not-found": return {
2417
- title: m.updateFailed(label),
2418
- description: m.elementNotFound
2419
- };
2420
- case "self-closing": return {
2421
- title: m.cannotEdit(label),
2422
- description: m.selfClosing(failure.property)
2423
- };
2424
- case "unsupported-syntax": return {
2425
- title: m.cannotEdit(label),
2426
- description: m.unsupportedSyntax(failure.property)
2427
- };
2428
- case "parse-error": return {
2429
- title: m.updateFailed(label),
2430
- description: failure.error instanceof Error ? failure.error.message : m.checkConsole
2431
- };
2432
- default: return { title: m.updateFailed(label) };
2433
- }
2434
- };
2435
- const blockedEditError = (error, { editErrors: m }) => ({
2436
- type: "parse",
2437
- target: "document",
2438
- reason: "parse-error",
2439
- error,
2440
- title: m.syntaxError,
2441
- description: m.syntaxErrorDetail
2442
- });
2443
- const conditionalModifiers = (args) => {
2444
- const { active } = args;
2445
- if (paletteSectionOf(active?.data)) return args.transform;
2446
- return restrictToVerticalAxis(args);
2447
- };
2448
- const NO_MODULES = {};
2449
- 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 }) => {
2450
- const modules = useStableModules(_modules);
2451
- const [mobilePaletteOpen, setMobilePaletteOpen] = useState(false);
2452
- const { breakpoint } = useResponsiveSize();
2453
- const isMobile = breakpoint.current === "xs" || breakpoint.current === "sm";
2454
- const { code } = usePreview();
2455
- const isForced = (section) => {
2456
- try {
2457
- return shouldForceSectionFallback?.(section) === true;
2458
- } catch (error) {
2459
- console.error("shouldForceSectionFallback threw", error);
2460
- return false;
2461
- }
2462
- };
2463
- const value = _value === void 0 ? code : _value;
2464
- const messages = useLiveMessages();
2465
- const reportError = onEditError ?? toastEditError;
2466
- const reportErrorRef = useRef(reportError);
2467
- const messagesRef = useRef(messages);
2468
- useEffect(() => {
2469
- reportErrorRef.current = reportError;
2470
- messagesRef.current = messages;
2471
- });
2472
- const onBlockedEdit = useCallback((problem) => {
2473
- if (problem.reason === "parse-error") reportErrorRef.current(blockedEditError(problem.error, messagesRef.current));
2474
- }, []);
2475
- const { sections, previews, selectedId, selectedSection, selectedIndex, select, selectOnly, clearSelection, add: addSection, remove, copy: onCopy, move: moveSection, reorder, patch, getCommittedSection, problem, stale } = useSectionDocument(value, _onChange, {
2476
- containerId,
2477
- sectionNameFallback,
2478
- onBlockedEdit
2479
- });
2480
- const missingContainer = problem?.reason === "container-not-found" ? problem.containerId : null;
2481
- const onDragEnd = (event) => {
2482
- const { active, over } = event;
2483
- if (!over) return;
2484
- const section = paletteSectionOf(active.data);
2485
- if (section) {
2486
- const atBottom = over.id === "sortable-area" || over.id === "sortable-area-bottom";
2487
- addSection(section, atBottom ? void 0 : sections.findIndex((s) => s.id === over.id));
2488
- return;
2489
- }
2490
- if (active.id !== over.id && sections.some((s) => s.id === active.id)) reorder(String(active.id), String(over.id));
2491
- };
2492
- const onSelect = (id) => select(id);
2493
- const requestDelete = useDeleteFlow({
2494
- sections,
2495
- remove,
2496
- onBeforeDelete
2497
- });
2498
- const onDelete = (id) => requestDelete(id);
2499
- const { sensors, announcements, screenReaderInstructions, sectionNodes, onNavigate, onDeleteKey, onMoveButton } = useDndKeyboard({
2500
- sections,
2501
- selectedId,
2502
- selectedIndex,
2503
- selectOnly,
2504
- move: moveSection,
2505
- requestDelete
2506
- });
2507
- const { inspector, inspecting, onInspectMove, onInspectPick, onInspectLeave, highlightRect } = useInspectorState({
2508
- selectedId,
2509
- selectOnly,
2510
- sectionNodes,
2511
- onNodePick
2512
- });
2513
- const onChange = (next) => {
2514
- if (!next.id) return;
2515
- patch(next);
2516
- };
2517
- const bindingOptions = useMemo(() => ({
2518
- bindings: bindingRegistry,
2519
- bindingKeys
2520
- }), [bindingRegistry, bindingKeys]);
4120
+ //#region src/components/dnd/state/use-section-editing.ts
4121
+ const useSectionEditing = ({ selectedSection, stale, problem, missingContainer, getCommittedSection, onChange, bindingOptions, reportError, messages, reportLatest }) => {
2521
4122
  const selectedCode = selectedSection?.code;
2522
4123
  const selectedSectionId = selectedSection?.id;
2523
4124
  const { fields, updatedCode, parseError } = useMemo(() => {
@@ -2547,24 +4148,24 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2547
4148
  bindingOptions
2548
4149
  ]);
2549
4150
  useEffect(() => {
2550
- if (missingContainer !== null) reportErrorRef.current({
4151
+ if (missingContainer !== null) reportLatest((m) => ({
2551
4152
  type: "parse",
2552
4153
  target: "document",
2553
4154
  reason: "container-not-found",
2554
4155
  containerId: missingContainer,
2555
- title: messagesRef.current.editErrors.missingContainer(missingContainer),
2556
- description: messagesRef.current.editErrors.missingContainerDetail(missingContainer)
2557
- });
2558
- }, [missingContainer]);
4156
+ title: m.editErrors.missingContainer(missingContainer),
4157
+ description: m.editErrors.missingContainerDetail(missingContainer)
4158
+ }));
4159
+ }, [missingContainer, reportLatest]);
2559
4160
  useEffect(() => {
2560
- if (parseError) reportErrorRef.current({
4161
+ if (parseError) reportLatest((m) => ({
2561
4162
  type: "parse",
2562
4163
  target: "section",
2563
4164
  error: parseError.error,
2564
- title: messagesRef.current.editErrors.sectionParseFailed,
2565
- description: messagesRef.current.editErrors.checkConsole
2566
- });
2567
- }, [parseError]);
4165
+ title: m.editErrors.sectionParseFailed,
4166
+ description: m.editErrors.checkConsole
4167
+ }));
4168
+ }, [parseError, reportLatest]);
2568
4169
  const commitChanges = (changes) => {
2569
4170
  if (changes.length === 0) return;
2570
4171
  if (stale && problem?.reason === "parse-error") {
@@ -2597,7 +4198,108 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2597
4198
  };
2598
4199
  const onFieldChange = (change) => commitChanges([change]);
2599
4200
  const bindingFields = useMemo(() => fields.flatMap((node) => resolvePanelBindings(node)?.bindings ?? []), [fields]);
2600
- const bindings = withPanelCommit(bindingFields, onFieldChange);
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
+ });
2601
4303
  useEffect(() => {
2602
4304
  if (frame?.scripts?.length) preloadScripts(frame.scripts);
2603
4305
  }, [frame?.scripts]);
@@ -2622,67 +4324,60 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2622
4324
  readOnly: stale,
2623
4325
  bindingOptions
2624
4326
  };
2625
- const canvas = /* @__PURE__ */ jsxs(Fragment, { children: [
2626
- stale && /* @__PURE__ */ jsx("div", {
2627
- role: "status",
2628
- className: cn("sticky top-0 z-70", "border-b border-amber-200 bg-amber-50 px-3 py-2", "text-sm text-amber-900"),
2629
- children: messages.canvas.stale
2630
- }),
2631
- /* @__PURE__ */ jsx(Droppable, {
2632
- className: cn(!sections.length && "h-full"),
2633
- children: !sections.length ? /* @__PURE__ */ jsx("div", {
2634
- className: cn("flex items-center justify-center", "h-full", "text-gray-500"),
2635
- children: problem?.reason === "parse-error" ? /* @__PURE__ */ jsxs(Space, {
2636
- orientation: "vertical",
2637
- align: "center",
2638
- children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: messages.canvas.syntaxError }), /* @__PURE__ */ jsx(Typography.Text, { children: messages.canvas.syntaxErrorDetail })]
2639
- }) : missingContainer !== null ? /* @__PURE__ */ jsxs(Space, {
2640
- orientation: "vertical",
2641
- align: "center",
2642
- children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: messages.canvas.missingContainer(missingContainer) }), /* @__PURE__ */ jsx(Typography.Text, { children: messages.canvas.missingContainerDetail(missingContainer) })]
2643
- }) : /* @__PURE__ */ jsxs(Space, {
2644
- orientation: "vertical",
2645
- align: "center",
2646
- children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: messages.canvas.empty }), /* @__PURE__ */ jsx(Typography.Text, { children: isMobile ? messages.canvas.emptyHintTouch : messages.canvas.emptyHintDrag })]
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))
2647
4376
  })
2648
- }) : /* @__PURE__ */ jsx(SortableContext, {
2649
- items: sections.map((s) => s.id),
2650
- strategy: verticalListSortingStrategy,
2651
- children: sections.map((section, index) => /* @__PURE__ */ jsx(Sortable, {
2652
- id: section.id,
2653
- name: section.name,
2654
- selected: selectedId === section.id,
2655
- onClick: () => onSelect(section.id),
2656
- onDelete,
2657
- onCopy,
2658
- onMoveUp: () => onMoveButton(section.id, "up"),
2659
- onMoveDown: () => onMoveButton(section.id, "down"),
2660
- canMoveUp: index > 0,
2661
- canMoveDown: index < sections.length - 1,
2662
- nodeRef: sectionNodes.register(section.id),
2663
- onNavigate: (to) => onNavigate(section.id, to),
2664
- onDeleteKey: () => onDeleteKey(section.id),
2665
- inspecting,
2666
- onInspectMove,
2667
- onInspectPick: (...args) => onInspectPick(section.id, ...args),
2668
- onInspectLeave,
2669
- children: /* @__PURE__ */ jsx(renderer_default, {
2670
- preview: previews[index],
2671
- sectionId: section.id,
2672
- sectionName: section.name,
2673
- sectionCode: section.code,
2674
- forceFallback: isForced(section),
2675
- modules,
2676
- frame,
2677
- dynamicTailwind,
2678
- provider,
2679
- ...props
2680
- })
2681
- }, section.id))
2682
- })
2683
- }),
2684
- /* @__PURE__ */ jsx(InspectorHighlight, { rect: highlightRect })
2685
- ] });
4377
+ }),
4378
+ /* @__PURE__ */ jsx(InspectorHighlight, { rect: highlightRect })
4379
+ ]
4380
+ });
2686
4381
  return /* @__PURE__ */ jsx(SectionFallbackContext.Provider, {
2687
4382
  value: renderSectionFallback,
2688
4383
  children: /* @__PURE__ */ jsxs(DndContext, {
@@ -2745,11 +4440,7 @@ Dnd.Palette = Palette;
2745
4440
  Dnd.Canvas = Canvas;
2746
4441
  Dnd.Panel = Panel;
2747
4442
  Dnd.Layout = Layout;
2748
- /** @deprecated Renamed to `useDndItems`. */
2749
- const useItemsEditor = useDndItems;
2750
- /** @deprecated Renamed to `useDndChildren`. */
2751
- const useChildrenEditor = useDndChildren;
2752
4443
  //#endregion
2753
- export { useDndPanel as _, Layout as a, Field as c, isStructuralFieldKind as d, useDndChildren as f, useDndPalette as g, useDndLayout as h, Canvas as i, useDndItems as l, withPanelCommit as m, useChildrenEditor as n, Palette as o, resolvePanelBindings as p, useItemsEditor as r, Panel as s, Dnd as t, getFieldKind as u, useDndInspector as v, DraggableItem as y };
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 };
2754
4445
 
2755
- //# sourceMappingURL=dnd-V8NAxd_R.js.map
4446
+ //# sourceMappingURL=dnd-BlC1gqUy.js.map