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