@jbpark/live-editor 3.0.2 → 3.2.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 (44) hide show
  1. package/README.ko.md +3 -2
  2. package/README.md +3 -2
  3. package/dist/{ast-D6R-dsk0.js → ast-Bx2Avo4d.js} +416 -177
  4. package/dist/ast-Bx2Avo4d.js.map +1 -0
  5. package/dist/{boundary-BF7iZp6e.js → boundary-BTP1nfG6.js} +2 -2
  6. package/dist/{boundary-BF7iZp6e.js.map → boundary-BTP1nfG6.js.map} +1 -1
  7. package/dist/{context-DH70wj9d.js → context-649REFsj.js} +2 -2
  8. package/dist/{context-DH70wj9d.js.map → context-649REFsj.js.map} +1 -1
  9. package/dist/{core-CTsQ9LiQ.js → core-D3q5rpuz.js} +2 -2
  10. package/dist/{core-CTsQ9LiQ.js.map → core-D3q5rpuz.js.map} +1 -1
  11. package/dist/dnd/index.d.ts +2 -2
  12. package/dist/dnd/index.js +2 -2
  13. package/dist/{dnd-C4iBiSkv.js → dnd-DTT37EbA.js} +487 -449
  14. package/dist/dnd-DTT37EbA.js.map +1 -0
  15. package/dist/document-C6bNHl7l.js.map +1 -1
  16. package/dist/editor/index.d.ts +1 -1
  17. package/dist/editor/index.js +2 -2
  18. package/dist/{editor-C0lphNg6.js → editor-LzqHe_-F.js} +3 -3
  19. package/dist/editor-LzqHe_-F.js.map +1 -0
  20. package/dist/error/index.js +2 -2
  21. package/dist/{error-lsJey-it.js → error-CO0EbMlA.js} +2 -2
  22. package/dist/{error-lsJey-it.js.map → error-CO0EbMlA.js.map} +1 -1
  23. package/dist/{index-Ck3akHag.d.ts → index-Ba8jGXSM.d.ts} +3 -2
  24. package/dist/{index-Bqc5kUAG.d.ts → index-DswWDEXZ.d.ts} +27 -3
  25. package/dist/{index-GuEiafMN.d.ts → index-DxbG789f.d.ts} +25 -2
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +5 -5
  28. package/dist/preview/index.js +1 -1
  29. package/dist/{preview-CNVXyDgY.js → preview-CWT0lgwq.js} +6 -6
  30. package/dist/preview-CWT0lgwq.js.map +1 -0
  31. package/dist/provider/index.js +1 -1
  32. package/dist/{use-dynamic-tailwind-C0Onss1A.js → use-dynamic-tailwind-DAhKGsY4.js} +70 -78
  33. package/dist/use-dynamic-tailwind-DAhKGsY4.js.map +1 -0
  34. package/dist/utils/ast/index.d.ts +2 -2
  35. package/dist/utils/ast/index.js +2 -2
  36. package/dist/utils/index.js +1 -1
  37. package/dist/{utils-BwEgY5pe.js → utils-WyxPYZGh.js} +12 -10
  38. package/dist/{utils-BwEgY5pe.js.map → utils-WyxPYZGh.js.map} +1 -1
  39. package/package.json +3 -2
  40. package/dist/ast-D6R-dsk0.js.map +0 -1
  41. package/dist/dnd-C4iBiSkv.js.map +0 -1
  42. package/dist/editor-C0lphNg6.js.map +0 -1
  43. package/dist/preview-CNVXyDgY.js.map +0 -1
  44. package/dist/use-dynamic-tailwind-C0Onss1A.js.map +0 -1
@@ -1,10 +1,10 @@
1
- import { C as __toESM, b as DRAGGABLE_ITEMS, f as nanoid, g as BINDING_PROP, m as require_lib, n as createSectionPreviewCache, r as fillSectionIds, y as DEFAULT_TEMPLATE } from "./document-C6bNHl7l.js";
2
- import { d as replaceSections, o as extractSections, r as cn, s as generateSection, u as preloadScripts } from "./utils-BwEgY5pe.js";
1
+ import { C as __toESM, b as DRAGGABLE_ITEMS, f as nanoid, g as BINDING_PROP, m as require_lib, n as createSectionPreviewCache, r as fillSectionIds } from "./document-C6bNHl7l.js";
2
+ import { d as replaceSections, o as extractSections, r as cn, s as generateSection, u as preloadScripts } from "./utils-WyxPYZGh.js";
3
3
  import { i as usePreview } from "./states-Ci1AvoQ9.js";
4
- import { A as parseValue, D as extractObjectProperties, E as extractNodeValue, M as generateCode, S as parseBinding, _ as moveSelectedIndices, a as extract, b as getCurrentValue, c as moveArrayItem, d as removeArrayItems, f as updateArrayItemProperty, g as replaceIds, h as fillIds, k as parseArrayExpression, l as moveArrayItems, o as appendArrayItem, p as updateArrayItemValue, r as update, s as duplicateArrayItems, t as validateBindingValue, v as removeIndices, x as getStructuredValue, y as findEditableChildren } from "./ast-D6R-dsk0.js";
5
- import { n as useCompiledModule, r as Frame, t as useDynamicTailwind } from "./use-dynamic-tailwind-C0Onss1A.js";
6
- import { t as Core } from "./core-CTsQ9LiQ.js";
7
- import { n as Error$1, t as ErrorBoundary } from "./boundary-BF7iZp6e.js";
4
+ import { A as parseValue, D as extractObjectProperties, E as extractNodeValue, S as parseBinding, _ as updateArrayItemValue, b as getCurrentValue, d as duplicateArrayItems, f as moveArrayItem, g as updateArrayItemProperty, h as removeArrayItems, i as replaceIds, k as parseArrayExpression, l as extract, o as update, p as moveArrayItems, r as fillIds, s as getChildrenSignatures, t as validateBindingValue, u as appendArrayItem, v as moveSelectedIndices, x as getStructuredValue, y as findEditableChildren } from "./ast-Bx2Avo4d.js";
5
+ import { n as useCompiledModule, r as Frame, t as useDynamicTailwind } from "./use-dynamic-tailwind-DAhKGsY4.js";
6
+ import { t as Core } from "./core-D3q5rpuz.js";
7
+ import { n as Error$1, t as ErrorBoundary } from "./boundary-BTP1nfG6.js";
8
8
  import { Children, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
9
9
  import { Button, Card, Checkbox, ColorPicker, DatePicker, Drawer, Input, RichTextEditor, Select, Space, Splitter, Toast, Typography, Upload } from "@jbpark/ui-kit";
10
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -78,6 +78,332 @@ const useDndPalette = () => useDndRegions("useDndPalette()").palette;
78
78
  const useDndPanel = () => useDndRegions("useDndPanel()").panel;
79
79
  const useDndLayout = () => useDndRegions("useDndLayout()");
80
80
  //#endregion
81
+ //#region src/components/dnd/panel/bulk-actions-bar.tsx
82
+ const BulkActionsBar = ({ count, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
83
+ if (count === 0) return null;
84
+ return /* @__PURE__ */ jsxs("div", {
85
+ className: "flex items-center justify-between rounded border\n border-blue-200 bg-blue-50 p-2",
86
+ children: [/* @__PURE__ */ jsxs("div", {
87
+ className: "text-xs font-medium text-blue-700",
88
+ children: [count, " selected"]
89
+ }), /* @__PURE__ */ jsxs("div", {
90
+ className: "flex items-center space-x-1",
91
+ children: [
92
+ /* @__PURE__ */ jsx(Button, {
93
+ size: "small",
94
+ icon: /* @__PURE__ */ jsx(Copy, {}),
95
+ title: "Duplicate selected",
96
+ onClick: onDuplicate
97
+ }),
98
+ /* @__PURE__ */ jsx(Button, {
99
+ size: "small",
100
+ icon: /* @__PURE__ */ jsx(ArrowUp, {}),
101
+ title: "Move selected up",
102
+ onClick: onMoveUp
103
+ }),
104
+ /* @__PURE__ */ jsx(Button, {
105
+ size: "small",
106
+ icon: /* @__PURE__ */ jsx(ArrowDown, {}),
107
+ title: "Move selected down",
108
+ onClick: onMoveDown
109
+ }),
110
+ /* @__PURE__ */ jsx(Button, {
111
+ danger: true,
112
+ size: "small",
113
+ icon: /* @__PURE__ */ jsx(X, {}),
114
+ title: "Delete selected",
115
+ onClick: onDelete
116
+ }),
117
+ /* @__PURE__ */ jsx(Button, {
118
+ size: "small",
119
+ onClick: onClear,
120
+ children: "Clear"
121
+ })
122
+ ]
123
+ })]
124
+ });
125
+ };
126
+ //#endregion
127
+ //#region src/components/dnd/panel/node.tsx
128
+ const Node = ({ data, onChange }) => {
129
+ const idAttr = data.dataAttributes.find((a) => a.name === "data-id");
130
+ const bindingAttr = data.dataAttributes.find((a) => a.name === "data-binding");
131
+ if (!idAttr?.value || !bindingAttr?.value) return null;
132
+ const dataId = idAttr.value;
133
+ const bindings = data.bindings || parseBinding(bindingAttr.value);
134
+ if (!bindings.length) return null;
135
+ return /* @__PURE__ */ jsx("div", {
136
+ className: "space-y-2 rounded",
137
+ children: /* @__PURE__ */ jsx("div", {
138
+ className: "space-y-1",
139
+ children: bindings.map((binding) => {
140
+ const currentValue = getCurrentValue(data, binding.property);
141
+ const structuredValue = getStructuredValue(data, binding.property, binding.type);
142
+ return /* @__PURE__ */ jsxs("div", {
143
+ className: "space-y-1",
144
+ children: [/* @__PURE__ */ jsxs("label", {
145
+ className: "block text-xs font-semibold text-gray-700",
146
+ children: [binding.label, /* @__PURE__ */ jsxs("span", {
147
+ className: "ml-1 text-gray-400",
148
+ children: [
149
+ "(",
150
+ binding.property,
151
+ ")"
152
+ ]
153
+ })]
154
+ }), /* @__PURE__ */ jsx(Field, {
155
+ binding: {
156
+ id: dataId,
157
+ label: binding.label,
158
+ property: binding.property,
159
+ type: binding.type,
160
+ widget: binding.widget,
161
+ options: binding.options,
162
+ render: binding.render,
163
+ min: binding.min,
164
+ max: binding.max,
165
+ pattern: binding.pattern,
166
+ required: binding.required,
167
+ meta: binding.meta,
168
+ value: structuredValue,
169
+ rawValue: currentValue,
170
+ onChange: (next) => onChange?.({
171
+ id: dataId,
172
+ label: binding.label,
173
+ property: binding.property,
174
+ value: next
175
+ })
176
+ },
177
+ onNodeChange: onChange
178
+ })]
179
+ }, `${binding.property}-${binding.label}`);
180
+ })
181
+ })
182
+ });
183
+ };
184
+ //#endregion
185
+ //#region src/components/dnd/panel/use-children-editor.ts
186
+ const useChildrenEditor = (items, { onChange } = {}) => {
187
+ const selection = useMultiSelect(items.length);
188
+ const expected = useMemo(() => getChildrenSignatures(items), [items]);
189
+ const revision = JSON.stringify(expected);
190
+ const clear = selection.clear;
191
+ useEffect(() => {
192
+ clear();
193
+ }, [revision, clear]);
194
+ const commit = (action) => {
195
+ onChange?.(JSON.stringify({
196
+ kind: "children-edit",
197
+ expected,
198
+ action
199
+ }));
200
+ };
201
+ return {
202
+ items,
203
+ selection,
204
+ actions: {
205
+ move: (from, to) => commit({
206
+ type: "move",
207
+ from,
208
+ to
209
+ }),
210
+ remove: (index) => commit({
211
+ type: "remove",
212
+ indices: [index]
213
+ }),
214
+ add: () => commit({ type: "append" }),
215
+ duplicateSelected: () => commit({
216
+ type: "duplicate",
217
+ indices: [...selection.selected]
218
+ }),
219
+ moveSelected: (direction) => commit({
220
+ type: "move-selected",
221
+ indices: [...selection.selected],
222
+ direction
223
+ }),
224
+ removeSelected: () => commit({
225
+ type: "remove",
226
+ indices: [...selection.selected]
227
+ })
228
+ }
229
+ };
230
+ };
231
+ //#endregion
232
+ //#region src/components/dnd/panel/children.tsx
233
+ const Children$1 = ({ value, onChange, onNodeChange }) => {
234
+ const items = useMemo(() => Array.isArray(value) ? value : [], [value]);
235
+ const { selection, actions } = useChildrenEditor(items, { onChange });
236
+ const editableChildrenMap = useMemo(() => {
237
+ const map = /* @__PURE__ */ new Map();
238
+ items.forEach((item, index) => {
239
+ const editableNodes = findEditableChildren(item);
240
+ if (editableNodes.length > 0) map.set(index, editableNodes);
241
+ });
242
+ return map;
243
+ }, [items]);
244
+ return /* @__PURE__ */ jsxs("div", {
245
+ className: "space-y-3",
246
+ children: [
247
+ /* @__PURE__ */ jsxs("div", {
248
+ className: "flex items-center justify-between",
249
+ children: [/* @__PURE__ */ jsxs("div", {
250
+ className: "text-sm font-semibold text-green-700",
251
+ children: [
252
+ "Children Items (",
253
+ items.length,
254
+ ")"
255
+ ]
256
+ }), /* @__PURE__ */ jsx(Button, {
257
+ size: "small",
258
+ color: "green",
259
+ icon: /* @__PURE__ */ jsx(Plus, {}),
260
+ onClick: actions.add,
261
+ children: "Add Child"
262
+ })]
263
+ }),
264
+ /* @__PURE__ */ jsx(BulkActionsBar, {
265
+ count: selection.selected.size,
266
+ onDuplicate: actions.duplicateSelected,
267
+ onMoveUp: () => actions.moveSelected("up"),
268
+ onMoveDown: () => actions.moveSelected("down"),
269
+ onDelete: actions.removeSelected,
270
+ onClear: selection.clear
271
+ }),
272
+ items.map((item, itemIndex) => /* @__PURE__ */ jsxs("div", {
273
+ className: "space-y-2 rounded border border-green-200 bg-green-50 p-3",
274
+ children: [
275
+ /* @__PURE__ */ jsxs("div", {
276
+ className: "flex items-center justify-between",
277
+ children: [/* @__PURE__ */ jsxs("div", {
278
+ className: "flex items-center space-x-2",
279
+ children: [/* @__PURE__ */ jsx("div", {
280
+ onClick: (e) => selection.toggle(itemIndex, e.shiftKey),
281
+ className: "inline-flex",
282
+ children: /* @__PURE__ */ jsx(Checkbox, {
283
+ checked: selection.isSelected(itemIndex),
284
+ onChange: () => {}
285
+ })
286
+ }), /* @__PURE__ */ jsxs("div", {
287
+ className: "text-xs font-medium text-green-800",
288
+ children: [
289
+ "Child ",
290
+ itemIndex + 1,
291
+ " (",
292
+ item.tagName || "fragment",
293
+ ")"
294
+ ]
295
+ })]
296
+ }), /* @__PURE__ */ jsxs("div", {
297
+ className: "flex space-x-1",
298
+ children: [
299
+ /* @__PURE__ */ jsx(Button, {
300
+ size: "small",
301
+ icon: /* @__PURE__ */ jsx(ArrowUp, {}),
302
+ disabled: itemIndex === 0,
303
+ onClick: () => actions.move(itemIndex, itemIndex - 1)
304
+ }),
305
+ /* @__PURE__ */ jsx(Button, {
306
+ size: "small",
307
+ icon: /* @__PURE__ */ jsx(ArrowDown, {}),
308
+ disabled: itemIndex === items.length - 1,
309
+ onClick: () => actions.move(itemIndex, itemIndex + 1)
310
+ }),
311
+ /* @__PURE__ */ jsx(Button, {
312
+ title: "Delete item",
313
+ danger: true,
314
+ size: "small",
315
+ icon: /* @__PURE__ */ jsx(X, {}),
316
+ onClick: () => actions.remove(itemIndex)
317
+ })
318
+ ]
319
+ })]
320
+ }),
321
+ editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
322
+ className: "space-y-2",
323
+ children: [/* @__PURE__ */ jsx("div", {
324
+ className: "text-xs font-medium text-green-700",
325
+ children: "Editable Bindings:"
326
+ }), editableChildrenMap.get(itemIndex).map((editableNode, idx) => {
327
+ const nodeId = editableNode.dataAttributes.find((a) => a.name === "data-id")?.value;
328
+ return /* @__PURE__ */ jsxs("div", {
329
+ className: "rounded border border-green-100 bg-white p-2",
330
+ children: [/* @__PURE__ */ jsx("div", {
331
+ className: "mb-1 text-xs text-green-600",
332
+ children: editableNode.tagName
333
+ }), /* @__PURE__ */ jsx(Node, {
334
+ data: editableNode,
335
+ onChange: onNodeChange
336
+ })]
337
+ }, `editable-${nodeId || idx}`);
338
+ })]
339
+ }),
340
+ !!item.children && !editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
341
+ className: "space-y-1",
342
+ children: [/* @__PURE__ */ jsx("div", {
343
+ className: "text-xs font-medium text-green-700",
344
+ children: "Child Nodes:"
345
+ }), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx("div", {
346
+ className: "ml-2 rounded border border-green-100 bg-white p-2",
347
+ children: /* @__PURE__ */ jsx(Node, {
348
+ data: node,
349
+ onChange: onNodeChange
350
+ })
351
+ }, `node-${itemIndex}-${nodeIndex}`))]
352
+ })
353
+ ]
354
+ }, item.id || itemIndex))
355
+ ]
356
+ });
357
+ };
358
+ //#endregion
359
+ //#region src/components/dnd/panel/icon-map.tsx
360
+ const ICON_MAP = {
361
+ AlertCircle,
362
+ ArrowDown,
363
+ ArrowLeft,
364
+ ArrowRight,
365
+ ArrowUp,
366
+ Bell,
367
+ Bookmark,
368
+ Calendar,
369
+ Camera,
370
+ Check,
371
+ ChevronDown,
372
+ ChevronRight,
373
+ Clock,
374
+ Download,
375
+ Edit,
376
+ Eye,
377
+ Filter,
378
+ Heart,
379
+ Home,
380
+ Image,
381
+ Info,
382
+ Link,
383
+ Mail,
384
+ Map: Map$1,
385
+ Menu,
386
+ MessageCircle,
387
+ Phone,
388
+ Plus,
389
+ Search,
390
+ Settings,
391
+ Share,
392
+ ShoppingCart,
393
+ Star,
394
+ Trash,
395
+ Upload: Upload$1,
396
+ User,
397
+ X
398
+ };
399
+ const ICON_OPTIONS = Object.entries(ICON_MAP).map(([name, Icon]) => ({
400
+ label: /* @__PURE__ */ jsxs("span", {
401
+ className: "flex items-center gap-2",
402
+ children: [/* @__PURE__ */ jsx(Icon, { size: 14 }), name]
403
+ }),
404
+ value: name
405
+ }));
406
+ //#endregion
81
407
  //#region src/components/dnd/panel/use-items-editor.ts
82
408
  const resolveLeaf = (render, key) => {
83
409
  const leaf = render?.[key];
@@ -87,8 +413,8 @@ const resolveMap = (render, key) => {
87
413
  const leaf = render?.[key];
88
414
  return leaf && !("type" in leaf) ? leaf : void 0;
89
415
  };
90
- const bindingsInJSX = (node) => {
91
- const nodes = extract(generateCode(node));
416
+ const bindingsInJSX = (source) => {
417
+ const nodes = extract(source);
92
418
  const container = nodes.find((n) => n.bindings?.some((b) => b.property === "children"));
93
419
  if (container) return [container];
94
420
  const bindings = [];
@@ -107,13 +433,15 @@ const parseSource = (value) => {
107
433
  };
108
434
  const objectItems = [];
109
435
  const primitiveItems = [];
110
- ast.elements.filter((element) => Boolean(element)).forEach((element, elementIndex) => {
436
+ ast.elements.forEach((element, elementIndex) => {
437
+ if (!import_lib.isExpression(element)) return;
111
438
  if (!import_lib.isObjectExpression(element)) {
112
439
  const extracted = extractNodeValue(element);
113
440
  primitiveItems.push({
114
441
  id: nanoid(6),
115
442
  index: primitiveItems.length,
116
443
  elementIndex,
444
+ source: value.slice(element.start, element.end),
117
445
  value: extracted.value,
118
446
  type: extracted.type
119
447
  });
@@ -125,18 +453,21 @@ const parseSource = (value) => {
125
453
  if (!import_lib.isObjectProperty(prop) || !import_lib.isIdentifier(prop.key) || !(import_lib.isJSXElement(prop.value) || import_lib.isJSXFragment(prop.value))) return;
126
454
  const propertyName = prop.key.name;
127
455
  try {
128
- const found = bindingsInJSX(prop.value);
456
+ const found = bindingsInJSX(value.slice(prop.value.start, prop.value.end));
129
457
  if (found.length > 0) jsxBindings[propertyName] = found;
130
- else jsxFallbacks[propertyName] = generateCode(prop.value);
458
+ else jsxFallbacks[propertyName] = value.slice(prop.value.start, prop.value.end);
131
459
  } catch (error) {
132
460
  console.error(`Failed to parse JSX in property '${propertyName}':`, error);
133
461
  }
134
462
  });
463
+ const editableProperties = extractObjectProperties(element);
464
+ for (const property of Object.values(editableProperties)) if (property.type === "array" || property.type === "object") property.value = value.slice(property.astNode.start, property.astNode.end);
135
465
  objectItems.push({
136
466
  id: nanoid(6),
137
467
  index: objectItems.length,
138
468
  elementIndex,
139
- editableProperties: extractObjectProperties(element),
469
+ source: value.slice(element.start, element.end),
470
+ editableProperties,
140
471
  jsxBindings,
141
472
  jsxFallbacks
142
473
  });
@@ -147,6 +478,38 @@ const parseSource = (value) => {
147
478
  parseError: false
148
479
  };
149
480
  };
481
+ const createIdentityState = (value, kind, items, ids = items.map((item) => item.id)) => ({
482
+ value,
483
+ kind,
484
+ ids,
485
+ signatures: items.map((item) => item.source)
486
+ });
487
+ const reconcileIdentityState = (current, value, kind, items) => {
488
+ if (current && current.value === value && current.kind === kind && current.ids.length === items.length) return current;
489
+ if (!current || current.kind !== kind) return createIdentityState(value, kind, items);
490
+ const previousCounts = /* @__PURE__ */ new Map();
491
+ const nextCounts = /* @__PURE__ */ new Map();
492
+ current.signatures.forEach((signature) => {
493
+ previousCounts.set(signature, (previousCounts.get(signature) ?? 0) + 1);
494
+ });
495
+ items.forEach((item) => {
496
+ nextCounts.set(item.source, (nextCounts.get(item.source) ?? 0) + 1);
497
+ });
498
+ const ids = items.map((item) => {
499
+ const previousIndex = current.signatures.indexOf(item.source);
500
+ return previousIndex >= 0 && previousCounts.get(item.source) === 1 && nextCounts.get(item.source) === 1 ? current.ids[previousIndex] : item.id;
501
+ });
502
+ return createIdentityState(value, kind, items, ids);
503
+ };
504
+ const moveId = (ids, from, to) => {
505
+ const next = [...ids];
506
+ const [moved] = next.splice(from, 1);
507
+ next.splice(to, 0, moved);
508
+ return next;
509
+ };
510
+ const removeIds = (ids, indices) => {
511
+ return ids.filter((_, index) => !indices.has(index));
512
+ };
150
513
  const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
151
514
  const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value), [value]);
152
515
  useEffect(() => {
@@ -154,13 +517,26 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
154
517
  }, [parseError]);
155
518
  const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
156
519
  const isPrimitive = kind === "primitive";
520
+ const rawItems = isPrimitive ? primitiveItems : objectItems;
521
+ const [identityState, setIdentityState] = useState(() => createIdentityState(value, kind, rawItems));
522
+ const identity = useMemo(() => reconcileIdentityState(identityState, value, kind, rawItems), [
523
+ identityState,
524
+ kind,
525
+ rawItems,
526
+ value
527
+ ]);
157
528
  const selection = useMultiSelect(isPrimitive ? primitiveItems.length : objectItems.length);
158
- const commit = (next) => {
529
+ const commit = (next, nextIds) => {
159
530
  if (next === null) {
160
- Toast.error("Failed to update this item", { description: "Check the console for details." });
161
- return;
531
+ Toast.error("Failed to update this item", { description: "The source was preserved. Structural edits require a dense array without spreads; use the code editor for unsupported syntax." });
532
+ return false;
162
533
  }
534
+ const parsed = parseSource(next);
535
+ const nextItems = kind === "primitive" ? parsed.primitiveItems : parsed.objectItems;
536
+ if (nextIds) setIdentityState(createIdentityState(next, kind, nextItems, nextIds));
537
+ else setIdentityState(createIdentityState(next, kind, nextItems, identity.ids));
163
538
  onChange?.(next);
539
+ return true;
164
540
  };
165
541
  const elementIndicesOf = (indices) => {
166
542
  return new Set((isPrimitive ? primitiveItems : objectItems).filter((item) => indices.has(item.index)).map((item) => item.elementIndex));
@@ -171,7 +547,7 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
171
547
  return {
172
548
  kind,
173
549
  items: isPrimitive ? primitiveItems.map((item) => ({
174
- id: item.id,
550
+ id: identity.ids[item.index] ?? item.id,
175
551
  index: item.index,
176
552
  elementIndex: item.elementIndex,
177
553
  properties: [],
@@ -185,10 +561,11 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
185
561
  onChange: (next) => commit(updateArrayItemValue(value, item.elementIndex, next))
186
562
  }
187
563
  })) : objectItems.map((item) => {
564
+ const id = identity.ids[item.index] ?? item.id;
188
565
  const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
189
566
  const leaf = resolveLeaf(render, key);
190
567
  return {
191
- id: `item-${item.id}-${key}`,
568
+ id: `item-${id}-${key}`,
192
569
  label: key,
193
570
  property: leaf ? leaf.property ?? leaf.type : key,
194
571
  type: leaf?.type,
@@ -248,7 +625,7 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
248
625
  }
249
626
  }))];
250
627
  return {
251
- id: item.id,
628
+ id,
252
629
  index: item.index,
253
630
  elementIndex: item.elementIndex,
254
631
  properties,
@@ -257,30 +634,40 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
257
634
  }),
258
635
  selection,
259
636
  actions: {
260
- add: () => commit(appendArrayItem(value, kind)),
637
+ add: () => {
638
+ const next = appendArrayItem(value, kind);
639
+ commit(next, next ? [...identity.ids, nanoid(6)] : void 0);
640
+ },
261
641
  move: (elementIndex, toIndex) => {
262
- const target = (isPrimitive ? primitiveItems : objectItems).find((item) => item.index === toIndex);
263
- if (!target) return;
264
- commit(moveArrayItem(value, elementIndex, target.elementIndex));
265
- selection.clear();
642
+ const items = isPrimitive ? primitiveItems : objectItems;
643
+ const from = items.find((item) => item.elementIndex === elementIndex);
644
+ const target = items.find((item) => item.index === toIndex);
645
+ if (!from || !target) return;
646
+ if (commit(moveArrayItem(value, elementIndex, target.elementIndex), moveId(identity.ids, from.index, target.index))) selection.clear();
266
647
  },
267
648
  remove: (elementIndex) => {
268
- commit(removeArrayItems(value, /* @__PURE__ */ new Set([elementIndex]), kind));
269
- selection.clear();
649
+ const removed = (isPrimitive ? primitiveItems : objectItems).find((item) => item.elementIndex === elementIndex);
650
+ if (commit(removeArrayItems(value, /* @__PURE__ */ new Set([elementIndex]), kind), removed ? removeIds(identity.ids, /* @__PURE__ */ new Set([removed.index])) : void 0)) selection.clear();
651
+ },
652
+ duplicateSelected: () => {
653
+ const selected = elementIndicesOf(selection.selected);
654
+ const next = duplicateArrayItems(value, selected);
655
+ const copied = rawItems.filter((item) => selected.has(item.elementIndex)).sort((a, b) => a.elementIndex - b.elementIndex).map(() => nanoid(6));
656
+ commit(next, next ? [...identity.ids, ...copied] : void 0);
270
657
  },
271
- duplicateSelected: () => commit(duplicateArrayItems(value, elementIndicesOf(selection.selected))),
272
658
  moveSelected: (direction) => {
659
+ const nextIdentity = moveSelectedIndices(identity.ids, selection.selected, direction);
273
660
  const result = moveArrayItems(value, elementIndicesOf(selection.selected), direction);
274
661
  if (!result) {
275
662
  commit(null);
276
663
  return;
277
664
  }
278
- selection.replace(result.indices);
279
- commit(result.code);
665
+ const visible = parseArrayExpression(result.code)?.elements.flatMap((node, index) => import_lib.isExpression(node) && import_lib.isObjectExpression(node) === !isPrimitive ? [index] : []) ?? [];
666
+ selection.replace(new Set(visible.flatMap((position, index) => result.indices.has(position) ? [index] : [])));
667
+ commit(result.code, nextIdentity.items);
280
668
  },
281
669
  removeSelected: () => {
282
- commit(removeArrayItems(value, elementIndicesOf(selection.selected), kind));
283
- selection.clear();
670
+ if (commit(removeArrayItems(value, elementIndicesOf(selection.selected), kind), removeIds(identity.ids, selection.selected))) selection.clear();
284
671
  }
285
672
  },
286
673
  parseError
@@ -288,50 +675,6 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
288
675
  };
289
676
  //#endregion
290
677
  //#region src/components/dnd/panel/items.tsx
291
- const BulkActionsBar = ({ count, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
292
- if (count === 0) return null;
293
- return /* @__PURE__ */ jsxs("div", {
294
- className: "flex items-center justify-between rounded border\n border-blue-200 bg-blue-50 p-2",
295
- children: [/* @__PURE__ */ jsxs("div", {
296
- className: "text-xs font-medium text-blue-700",
297
- children: [count, " selected"]
298
- }), /* @__PURE__ */ jsxs("div", {
299
- className: "flex items-center space-x-1",
300
- children: [
301
- /* @__PURE__ */ jsx(Button, {
302
- size: "small",
303
- icon: /* @__PURE__ */ jsx(Copy, {}),
304
- title: "Duplicate selected",
305
- onClick: onDuplicate
306
- }),
307
- /* @__PURE__ */ jsx(Button, {
308
- size: "small",
309
- icon: /* @__PURE__ */ jsx(ArrowUp, {}),
310
- title: "Move selected up",
311
- onClick: onMoveUp
312
- }),
313
- /* @__PURE__ */ jsx(Button, {
314
- size: "small",
315
- icon: /* @__PURE__ */ jsx(ArrowDown, {}),
316
- title: "Move selected down",
317
- onClick: onMoveDown
318
- }),
319
- /* @__PURE__ */ jsx(Button, {
320
- danger: true,
321
- size: "small",
322
- icon: /* @__PURE__ */ jsx(X, {}),
323
- title: "Delete selected",
324
- onClick: onDelete
325
- }),
326
- /* @__PURE__ */ jsx(Button, {
327
- size: "small",
328
- onClick: onClear,
329
- children: "Clear"
330
- })
331
- ]
332
- })]
333
- });
334
- };
335
678
  const NestedGroup = ({ group, onNodeChange }) => {
336
679
  if (group.fallback) return /* @__PURE__ */ jsxs("div", {
337
680
  className: "space-y-2",
@@ -382,18 +725,50 @@ const NestedGroup = ({ group, onNodeChange }) => {
382
725
  binding,
383
726
  onNodeChange
384
727
  })]
385
- }, binding.label))
728
+ }, `${binding.property}-${binding.label}`))
386
729
  })
387
730
  })]
388
731
  }, `${group.property}-${element.id}`))]
389
732
  });
390
733
  };
391
734
  const Items = ({ value, render, onChange, onChildChange }) => {
392
- const { kind, items, selection, actions } = useItemsEditor(value, {
735
+ const { kind, items, selection, actions, parseError } = useItemsEditor(value, {
393
736
  render,
394
737
  onChange,
395
738
  onNodeChange: onChildChange
396
739
  });
740
+ const header = /* @__PURE__ */ jsxs("div", {
741
+ className: "flex items-center justify-between",
742
+ children: [/* @__PURE__ */ jsxs("div", {
743
+ className: "text-sm font-semibold",
744
+ children: [
745
+ "Items (",
746
+ items.length,
747
+ ")"
748
+ ]
749
+ }), items.length > 0 && /* @__PURE__ */ jsx(Button, {
750
+ size: "small",
751
+ icon: /* @__PURE__ */ jsx(Plus, {}),
752
+ variant: "solid",
753
+ color: "green",
754
+ onClick: actions.add,
755
+ children: "Add Item"
756
+ })]
757
+ });
758
+ if (parseError) return /* @__PURE__ */ jsxs("div", {
759
+ className: "space-y-4",
760
+ children: [header, /* @__PURE__ */ jsx("div", {
761
+ className: "rounded border border-dashed border-red-200 p-3 text-xs\n text-red-600",
762
+ children: "This value could not be read as a list. Edit it in the code editor."
763
+ })]
764
+ });
765
+ if (items.length === 0) return /* @__PURE__ */ jsxs("div", {
766
+ className: "space-y-4",
767
+ children: [header, /* @__PURE__ */ jsx("div", {
768
+ className: "rounded border border-dashed border-gray-200 p-3 text-xs\n text-gray-500",
769
+ children: "No editable items. The panel copies an existing item rather than guessing the shape of a new one — add the first item in the code editor."
770
+ })]
771
+ });
397
772
  const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
398
773
  count: selection.selected.size,
399
774
  onDuplicate: actions.duplicateSelected,
@@ -426,27 +801,10 @@ const Items = ({ value, render, onChange, onChildChange }) => {
426
801
  })
427
802
  ]
428
803
  });
429
- if (kind === "primitive") return /* @__PURE__ */ jsxs("div", {
430
- className: "space-y-4",
431
- children: [
432
- /* @__PURE__ */ jsxs("div", {
433
- className: "flex items-center justify-between",
434
- children: [/* @__PURE__ */ jsxs("div", {
435
- className: "text-sm font-semibold",
436
- children: [
437
- "Items (",
438
- items.length,
439
- ")"
440
- ]
441
- }), /* @__PURE__ */ jsx(Button, {
442
- size: "small",
443
- icon: /* @__PURE__ */ jsx(Plus, {}),
444
- variant: "solid",
445
- color: "green",
446
- onClick: actions.add,
447
- children: "Add Item"
448
- })]
449
- }),
804
+ if (kind === "primitive") return /* @__PURE__ */ jsxs("div", {
805
+ className: "space-y-4",
806
+ children: [
807
+ header,
450
808
  bulkBar,
451
809
  items.map((item) => /* @__PURE__ */ jsxs("div", {
452
810
  className: "space-y-2 rounded border border-gray-100 bg-gray-50 p-2",
@@ -470,25 +828,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
470
828
  return /* @__PURE__ */ jsxs("div", {
471
829
  className: "space-y-4",
472
830
  children: [
473
- /* @__PURE__ */ jsxs("div", {
474
- className: "flex items-center justify-between",
475
- children: [/* @__PURE__ */ jsxs("div", {
476
- className: "text-sm font-semibold",
477
- children: [
478
- "Items (",
479
- items.length,
480
- ")"
481
- ]
482
- }), /* @__PURE__ */ jsx(Button, {
483
- size: "small",
484
- icon: /* @__PURE__ */ jsx(Plus, {}),
485
- variant: "solid",
486
- color: "green",
487
- disabled: items.length === 0,
488
- onClick: actions.add,
489
- children: "Add Item"
490
- })]
491
- }),
831
+ header,
492
832
  bulkBar,
493
833
  items.map((item) => /* @__PURE__ */ jsxs("div", {
494
834
  className: "space-y-3 rounded border bg-gray-50 p-3",
@@ -528,7 +868,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
528
868
  String(binding.meta?.valueType),
529
869
  ")"
530
870
  ]
531
- })] }, `${item.id}-${binding.label}`))
871
+ })] }, `${item.id}-${binding.property}-${binding.label}`))
532
872
  }),
533
873
  /* @__PURE__ */ jsx("div", {
534
874
  className: "space-y-3 border-t pt-2",
@@ -546,305 +886,6 @@ const Items = ({ value, render, onChange, onChildChange }) => {
546
886
  });
547
887
  };
548
888
  //#endregion
549
- //#region src/components/dnd/panel/node.tsx
550
- const Node = ({ data, onChange }) => {
551
- const idAttr = data.dataAttributes.find((a) => a.name === "data-id");
552
- const bindingAttr = data.dataAttributes.find((a) => a.name === "data-binding");
553
- if (!idAttr?.value || !bindingAttr?.value) return null;
554
- const dataId = idAttr.value;
555
- const bindings = data.bindings || parseBinding(bindingAttr.value);
556
- if (!bindings.length) return null;
557
- return /* @__PURE__ */ jsx("div", {
558
- className: "space-y-2 rounded",
559
- children: /* @__PURE__ */ jsx("div", {
560
- className: "space-y-1",
561
- children: bindings.map((binding) => {
562
- const currentValue = getCurrentValue(data, binding.property);
563
- const structuredValue = getStructuredValue(data, binding.property, binding.type);
564
- return /* @__PURE__ */ jsxs("div", {
565
- className: "space-y-1",
566
- children: [/* @__PURE__ */ jsxs("label", {
567
- className: "block text-xs font-semibold text-gray-700",
568
- children: [binding.label, /* @__PURE__ */ jsxs("span", {
569
- className: "ml-1 text-gray-400",
570
- children: [
571
- "(",
572
- binding.property,
573
- ")"
574
- ]
575
- })]
576
- }), /* @__PURE__ */ jsx(Field, {
577
- binding: {
578
- id: dataId,
579
- label: binding.label,
580
- property: binding.property,
581
- type: binding.type,
582
- widget: binding.widget,
583
- options: binding.options,
584
- render: binding.render,
585
- min: binding.min,
586
- max: binding.max,
587
- pattern: binding.pattern,
588
- required: binding.required,
589
- meta: binding.meta,
590
- value: structuredValue,
591
- rawValue: currentValue,
592
- onChange: (next) => onChange?.({
593
- id: dataId,
594
- label: binding.label,
595
- property: binding.property,
596
- value: next
597
- })
598
- },
599
- onNodeChange: onChange
600
- })]
601
- }, binding.label);
602
- })
603
- })
604
- });
605
- };
606
- //#endregion
607
- //#region src/components/dnd/panel/children.tsx
608
- const Children$1 = ({ value, onChange, onNodeChange }) => {
609
- const items = useMemo(() => Array.isArray(value) ? value : [], [value]);
610
- const selection = useMultiSelect(items.length);
611
- const editableChildrenMap = useMemo(() => {
612
- const map = /* @__PURE__ */ new Map();
613
- items.forEach((item, index) => {
614
- const editableNodes = findEditableChildren(item);
615
- if (editableNodes.length > 0) map.set(index, editableNodes);
616
- });
617
- return map;
618
- }, [items]);
619
- const moveItem = (fromIndex, toIndex) => {
620
- const nextItems = [...items];
621
- const [movedItem] = nextItems.splice(fromIndex, 1);
622
- nextItems.splice(toIndex, 0, movedItem);
623
- onChange?.(JSON.stringify(nextItems));
624
- };
625
- const moveSelectedItems = (indices, direction) => {
626
- const { items: nextItems, indices: nextIndices } = moveSelectedIndices(items, indices, direction);
627
- selection.replace(nextIndices);
628
- onChange?.(JSON.stringify(nextItems));
629
- };
630
- const deleteSelectedItems = (indices) => {
631
- if (items.length - indices.size < 1) return;
632
- const nextItems = removeIndices(items, indices);
633
- onChange?.(JSON.stringify(nextItems));
634
- };
635
- const deleteItem = (index) => deleteSelectedItems(/* @__PURE__ */ new Set([index]));
636
- const addItem = () => {
637
- const template = items[0] || createDefaultItem();
638
- const newItem = cloneDataAttrNode(template);
639
- const nextItems = [...items, newItem];
640
- onChange?.(JSON.stringify(nextItems));
641
- };
642
- const duplicateSelectedItems = (indices) => {
643
- const sources = [...indices].sort((a, b) => a - b).map((index) => items[index]).filter((item) => Boolean(item));
644
- if (sources.length === 0) return;
645
- const clones = sources.map(cloneDataAttrNode);
646
- onChange?.(JSON.stringify([...items, ...clones]));
647
- };
648
- const createDefaultItem = () => ({
649
- id: nanoid(6),
650
- tagName: "div",
651
- attributes: [{
652
- name: "data-id",
653
- value: nanoid(6)
654
- }, {
655
- name: "data-item",
656
- value: "true"
657
- }],
658
- dataAttributes: [{
659
- name: "data-id",
660
- value: nanoid(6)
661
- }, {
662
- name: "data-item",
663
- value: "true"
664
- }],
665
- textContent: "",
666
- children: []
667
- });
668
- const cloneDataAttrNode = (node) => ({
669
- ...node,
670
- id: nanoid(6),
671
- attributes: node.attributes.map((attr) => ({
672
- ...attr,
673
- value: attr.name === "data-id" ? nanoid(6) : attr.value
674
- })),
675
- dataAttributes: node.dataAttributes.map((attr) => ({
676
- ...attr,
677
- value: attr.name === "data-id" ? nanoid(6) : attr.value
678
- })),
679
- children: node.children?.map(cloneDataAttrNode)
680
- });
681
- return /* @__PURE__ */ jsxs("div", {
682
- className: "space-y-3",
683
- children: [
684
- /* @__PURE__ */ jsxs("div", {
685
- className: "flex items-center justify-between",
686
- children: [/* @__PURE__ */ jsxs("div", {
687
- className: "text-sm font-semibold text-green-700",
688
- children: [
689
- "Children Items (",
690
- items.length,
691
- ")"
692
- ]
693
- }), /* @__PURE__ */ jsx(Button, {
694
- size: "small",
695
- color: "green",
696
- icon: /* @__PURE__ */ jsx(Plus, {}),
697
- onClick: addItem,
698
- children: "Add Child"
699
- })]
700
- }),
701
- /* @__PURE__ */ jsx(BulkActionsBar, {
702
- count: selection.selected.size,
703
- onDuplicate: () => duplicateSelectedItems(selection.selected),
704
- onMoveUp: () => moveSelectedItems(selection.selected, "up"),
705
- onMoveDown: () => moveSelectedItems(selection.selected, "down"),
706
- onDelete: () => {
707
- deleteSelectedItems(selection.selected);
708
- selection.clear();
709
- },
710
- onClear: selection.clear
711
- }),
712
- items.map((item, itemIndex) => /* @__PURE__ */ jsxs("div", {
713
- className: "space-y-2 rounded border border-green-200 bg-green-50 p-3",
714
- children: [
715
- /* @__PURE__ */ jsxs("div", {
716
- className: "flex items-center justify-between",
717
- children: [/* @__PURE__ */ jsxs("div", {
718
- className: "flex items-center space-x-2",
719
- children: [/* @__PURE__ */ jsx("div", {
720
- onClick: (e) => selection.toggle(itemIndex, e.shiftKey),
721
- className: "inline-flex",
722
- children: /* @__PURE__ */ jsx(Checkbox, {
723
- checked: selection.isSelected(itemIndex),
724
- onChange: () => {}
725
- })
726
- }), /* @__PURE__ */ jsxs("div", {
727
- className: "text-xs font-medium text-green-800",
728
- children: [
729
- "Child ",
730
- itemIndex + 1,
731
- " (",
732
- item.tagName || "fragment",
733
- ")"
734
- ]
735
- })]
736
- }), /* @__PURE__ */ jsxs("div", {
737
- className: "flex space-x-1",
738
- children: [
739
- /* @__PURE__ */ jsx(Button, {
740
- size: "small",
741
- icon: /* @__PURE__ */ jsx(ArrowUp, {}),
742
- disabled: itemIndex === 0,
743
- onClick: () => moveItem(itemIndex, itemIndex - 1)
744
- }),
745
- /* @__PURE__ */ jsx(Button, {
746
- size: "small",
747
- icon: /* @__PURE__ */ jsx(ArrowDown, {}),
748
- disabled: itemIndex === items.length - 1,
749
- onClick: () => moveItem(itemIndex, itemIndex + 1)
750
- }),
751
- /* @__PURE__ */ jsx(Button, {
752
- title: items.length <= 1 ? "At least 1 item is required" : "Delete item",
753
- danger: true,
754
- size: "small",
755
- icon: /* @__PURE__ */ jsx(X, {}),
756
- disabled: items.length <= 1,
757
- onClick: () => deleteItem(itemIndex)
758
- })
759
- ]
760
- })]
761
- }),
762
- editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
763
- className: "space-y-2",
764
- children: [/* @__PURE__ */ jsx("div", {
765
- className: "text-xs font-medium text-green-700",
766
- children: "Editable Bindings:"
767
- }), editableChildrenMap.get(itemIndex).map((editableNode, idx) => {
768
- const nodeId = editableNode.dataAttributes.find((a) => a.name === "data-id")?.value;
769
- return /* @__PURE__ */ jsxs("div", {
770
- className: "rounded border border-green-100 bg-white p-2",
771
- children: [/* @__PURE__ */ jsx("div", {
772
- className: "mb-1 text-xs text-green-600",
773
- children: editableNode.tagName
774
- }), /* @__PURE__ */ jsx(Node, {
775
- data: editableNode,
776
- onChange: onNodeChange
777
- })]
778
- }, `editable-${itemIndex}-${nodeId || idx}`);
779
- })]
780
- }),
781
- !!item.children && !editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
782
- className: "space-y-1",
783
- children: [/* @__PURE__ */ jsx("div", {
784
- className: "text-xs font-medium text-green-700",
785
- children: "Child Nodes:"
786
- }), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx("div", {
787
- className: "ml-2 rounded border border-green-100 bg-white p-2",
788
- children: /* @__PURE__ */ jsx(Node, {
789
- data: node,
790
- onChange: onNodeChange
791
- })
792
- }, `node-${itemIndex}-${nodeIndex}`))]
793
- })
794
- ]
795
- }, item.id || itemIndex))
796
- ]
797
- });
798
- };
799
- //#endregion
800
- //#region src/components/dnd/panel/icon-map.tsx
801
- const ICON_MAP = {
802
- AlertCircle,
803
- ArrowDown,
804
- ArrowLeft,
805
- ArrowRight,
806
- ArrowUp,
807
- Bell,
808
- Bookmark,
809
- Calendar,
810
- Camera,
811
- Check,
812
- ChevronDown,
813
- ChevronRight,
814
- Clock,
815
- Download,
816
- Edit,
817
- Eye,
818
- Filter,
819
- Heart,
820
- Home,
821
- Image,
822
- Info,
823
- Link,
824
- Mail,
825
- Map: Map$1,
826
- Menu,
827
- MessageCircle,
828
- Phone,
829
- Plus,
830
- Search,
831
- Settings,
832
- Share,
833
- ShoppingCart,
834
- Star,
835
- Trash,
836
- Upload: Upload$1,
837
- User,
838
- X
839
- };
840
- const ICON_OPTIONS = Object.entries(ICON_MAP).map(([name, Icon]) => ({
841
- label: /* @__PURE__ */ jsxs("span", {
842
- className: "flex items-center gap-2",
843
- children: [/* @__PURE__ */ jsx(Icon, { size: 14 }), name]
844
- }),
845
- value: name
846
- }));
847
- //#endregion
848
889
  //#region src/components/dnd/panel/field.tsx
849
890
  const isColorProperty = (propertyName) => {
850
891
  return propertyName.toLowerCase().includes("color");
@@ -914,6 +955,9 @@ const Field = ({ binding, onNodeChange }) => {
914
955
  setPrevRawValue(rawValue);
915
956
  setText(rawValue);
916
957
  }
958
+ const commitIfChanged = (next) => {
959
+ if (next !== (typeof next === "string" ? rawValue : value)) onChange(next);
960
+ };
917
961
  if (binding.property === "items" || binding.property === "data" || binding.type === "array") return /* @__PURE__ */ jsx(Items, {
918
962
  value: rawValue,
919
963
  render: binding.render,
@@ -923,9 +967,7 @@ const Field = ({ binding, onNodeChange }) => {
923
967
  if (binding.type === "richtext") return /* @__PURE__ */ jsx(RichTextEditor, {
924
968
  value: rawValue,
925
969
  toolbar: RICHTEXT_TOOLBAR,
926
- onChange: (next) => {
927
- if (next !== rawValue) onChange(next);
928
- }
970
+ onChange: commitIfChanged
929
971
  });
930
972
  if (binding.property === BINDING_PROP.INNER_HTML || binding.type === "jsx") {
931
973
  const isHTML = binding.property === BINDING_PROP.INNER_HTML;
@@ -934,9 +976,7 @@ const Field = ({ binding, onNodeChange }) => {
934
976
  height: "150px",
935
977
  fragment: !isHTML,
936
978
  raw: isHTML,
937
- onSave: (next) => {
938
- if (next !== rawValue) onChange(next);
939
- }
979
+ onSave: commitIfChanged
940
980
  });
941
981
  }
942
982
  if (binding.property === "children" && Array.isArray(value)) return /* @__PURE__ */ jsx(Children$1, {
@@ -984,13 +1024,11 @@ const Field = ({ binding, onNodeChange }) => {
984
1024
  if (binding.options && Array.isArray(binding.options)) return /* @__PURE__ */ jsx(Select, {
985
1025
  value: stringValue,
986
1026
  options: binding.options,
987
- onChange: (next) => {
988
- if (next !== value) onChange(next);
989
- }
1027
+ onChange: commitIfChanged
990
1028
  });
991
1029
  if (binding.type === "color" || isColorProperty(binding.property)) return /* @__PURE__ */ jsx(ColorPickerField, {
992
1030
  value: normalizeToHex(stringValue),
993
- onChange
1031
+ onChange: commitIfChanged
994
1032
  });
995
1033
  if (binding.type === "date") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DatePicker, {
996
1034
  value: parseDateValue(stringValue),
@@ -1002,7 +1040,7 @@ const Field = ({ binding, onNodeChange }) => {
1002
1040
  return;
1003
1041
  }
1004
1042
  setValidationError(null);
1005
- if (next !== value) onChange(next);
1043
+ commitIfChanged(next);
1006
1044
  }
1007
1045
  }), validationError && /* @__PURE__ */ jsx("p", {
1008
1046
  className: "mt-1 text-xs text-red-500",
@@ -1021,7 +1059,7 @@ const Field = ({ binding, onNodeChange }) => {
1021
1059
  return;
1022
1060
  }
1023
1061
  setValidationError(null);
1024
- if (next !== value) onChange(next);
1062
+ commitIfChanged(next);
1025
1063
  }
1026
1064
  }), validationError && /* @__PURE__ */ jsx("p", {
1027
1065
  className: "mt-1 text-xs text-red-500",
@@ -1034,9 +1072,7 @@ const Field = ({ binding, onNodeChange }) => {
1034
1072
  children: [/* @__PURE__ */ jsx(Select, {
1035
1073
  value: stringValue,
1036
1074
  options: ICON_OPTIONS,
1037
- onChange: (next) => {
1038
- if (next !== value) onChange(next);
1039
- }
1075
+ onChange: commitIfChanged
1040
1076
  }), SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, {
1041
1077
  size: 18,
1042
1078
  className: "shrink-0"
@@ -1064,7 +1100,7 @@ const Field = ({ binding, onNodeChange }) => {
1064
1100
  return;
1065
1101
  }
1066
1102
  setValidationError(null);
1067
- if (next !== value) onChange(next);
1103
+ commitIfChanged(next);
1068
1104
  }
1069
1105
  }),
1070
1106
  /* @__PURE__ */ jsx(Upload, {
@@ -1074,7 +1110,7 @@ const Field = ({ binding, onNodeChange }) => {
1074
1110
  defaultValue: defaultUploadValue,
1075
1111
  onChange: (files) => {
1076
1112
  const next = files[0]?.url ?? "";
1077
- if (next !== value) onChange(next);
1113
+ commitIfChanged(next);
1078
1114
  }
1079
1115
  }),
1080
1116
  validationError && /* @__PURE__ */ jsx("p", {
@@ -1098,7 +1134,7 @@ const Field = ({ binding, onNodeChange }) => {
1098
1134
  return;
1099
1135
  }
1100
1136
  setValidationError(null);
1101
- if (next !== value) onChange(next);
1137
+ commitIfChanged(next);
1102
1138
  }
1103
1139
  }), validationError && /* @__PURE__ */ jsx("p", {
1104
1140
  className: "mt-1 text-xs text-red-500",
@@ -1117,7 +1153,7 @@ const Field = ({ binding, onNodeChange }) => {
1117
1153
  return;
1118
1154
  }
1119
1155
  setValidationError(null);
1120
- if (next !== rawValue) onChange(next);
1156
+ commitIfChanged(next);
1121
1157
  }
1122
1158
  }), validationError && /* @__PURE__ */ jsx("p", {
1123
1159
  className: "mt-1 text-xs text-red-500",
@@ -1146,7 +1182,7 @@ const FieldGroup = ({ bindings, onNodeChange }) => /* @__PURE__ */ jsx("div", {
1146
1182
  binding,
1147
1183
  onNodeChange
1148
1184
  })]
1149
- }, binding.label))
1185
+ }, `${binding.property}-${binding.label}`))
1150
1186
  })
1151
1187
  });
1152
1188
  //#endregion
@@ -1161,7 +1197,7 @@ const groupBindingsById = (bindings) => {
1161
1197
  return [...groups.values()];
1162
1198
  };
1163
1199
  const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canMoveDown = false, bindings, onNodeChange }) => {
1164
- const groups = useMemo(() => groupBindingsById(bindings), [bindings]);
1200
+ const groups = groupBindingsById(bindings);
1165
1201
  if (!item) return /* @__PURE__ */ jsx(Typography.Paragraph, {
1166
1202
  className: cn("p-4 text-sm text-gray-500"),
1167
1203
  children: "Please select a section."
@@ -1524,7 +1560,7 @@ const describeUpdateFailure = (failure, label) => {
1524
1560
  };
1525
1561
  case "parse-error": return {
1526
1562
  title: `Failed to update "${label}"`,
1527
- description: "Check the console for details."
1563
+ description: failure.error instanceof Error ? failure.error.message : "Check the console for details."
1528
1564
  };
1529
1565
  default: return { title: `Failed to update "${label}"` };
1530
1566
  }
@@ -1540,7 +1576,7 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1540
1576
  const isMobile = breakpoint.current === "xs" || breakpoint.current === "sm";
1541
1577
  const { code } = usePreview();
1542
1578
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
1543
- const value = _value || code || DEFAULT_TEMPLATE;
1579
+ const value = _value === void 0 ? code : _value;
1544
1580
  const { sections, previews, selectedId, selectedItem, selectedIndex, select, clearSelection, add: addItem, remove: onDelete, copy: onCopy, move: moveSection, reorder, patch } = useSectionDocument(value, _onChange);
1545
1581
  const onDragStart = (_) => {};
1546
1582
  const onDragOver = (_e) => {};
@@ -1617,16 +1653,18 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1617
1653
  required: binding.required,
1618
1654
  meta: binding.meta,
1619
1655
  value: getStructuredValue(node, binding.property, binding.type),
1620
- rawValue: getCurrentValue(node, binding.property),
1621
- onChange: (value) => onFieldChange({
1622
- id: dataId,
1623
- label: binding.label,
1624
- property: binding.property,
1625
- value
1626
- })
1656
+ rawValue: getCurrentValue(node, binding.property)
1627
1657
  }));
1628
1658
  });
1629
- }, [fields]);
1659
+ }, [fields]).map((field) => ({
1660
+ ...field,
1661
+ onChange: (value) => onFieldChange({
1662
+ id: field.id,
1663
+ label: field.label,
1664
+ property: field.property,
1665
+ value
1666
+ })
1667
+ }));
1630
1668
  useEffect(() => {
1631
1669
  if (frame?.scripts?.length) preloadScripts(frame.scripts);
1632
1670
  }, [frame?.scripts]);
@@ -1723,6 +1761,6 @@ Dnd.Canvas = Canvas;
1723
1761
  Dnd.Panel = Panel;
1724
1762
  Dnd.Layout = Layout;
1725
1763
  //#endregion
1726
- export { Panel as a, ICON_OPTIONS as c, useDndPalette as d, useDndPanel as f, Palette as i, useItemsEditor as l, Canvas as n, Field as o, DraggableItem as p, Layout as r, ICON_MAP as s, Dnd as t, useDndLayout as u };
1764
+ export { Panel as a, ICON_MAP as c, useDndLayout as d, useDndPalette as f, Palette as i, ICON_OPTIONS as l, DraggableItem as m, Canvas as n, Field as o, useDndPanel as p, Layout as r, useItemsEditor as s, Dnd as t, useChildrenEditor as u };
1727
1765
 
1728
- //# sourceMappingURL=dnd-C4iBiSkv.js.map
1766
+ //# sourceMappingURL=dnd-DTT37EbA.js.map