@jbpark/live-editor 3.2.1 → 4.0.1

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 (53) hide show
  1. package/dist/ast-CZwpVrJv.js +851 -0
  2. package/dist/ast-CZwpVrJv.js.map +1 -0
  3. package/dist/{boundary-BTP1nfG6.js → boundary-C90q3YsT.js} +8 -7
  4. package/dist/boundary-C90q3YsT.js.map +1 -0
  5. package/dist/{context-649REFsj.js → context-Y-M9fViD.js} +4 -8
  6. package/dist/context-Y-M9fViD.js.map +1 -0
  7. package/dist/{core-D3q5rpuz.js → core-Co8Nlq16.js} +2 -2
  8. package/dist/{core-D3q5rpuz.js.map → core-Co8Nlq16.js.map} +1 -1
  9. package/dist/dnd/index.d.ts +2 -2
  10. package/dist/dnd/index.js +2 -2
  11. package/dist/{dnd-D90jLjLH.js → dnd-_0jfnnVB.js} +419 -338
  12. package/dist/dnd-_0jfnnVB.js.map +1 -0
  13. package/dist/editor/index.d.ts +1 -1
  14. package/dist/editor/index.js +2 -2
  15. package/dist/editor-aNRx7hRY.js +66 -0
  16. package/dist/editor-aNRx7hRY.js.map +1 -0
  17. package/dist/error/index.d.ts +1 -1
  18. package/dist/error/index.js +2 -2
  19. package/dist/{error-CO0EbMlA.js → error-o0SBwtTv.js} +2 -2
  20. package/dist/{error-CO0EbMlA.js.map → error-o0SBwtTv.js.map} +1 -1
  21. package/dist/{document-C6bNHl7l.js → extract-abHYmNsu.js} +927 -42
  22. package/dist/extract-abHYmNsu.js.map +1 -0
  23. package/dist/{index-BQdZ6LrR.d.ts → index-13BDSrQc.d.ts} +2 -2
  24. package/dist/{index-DxbG789f.d.ts → index-B_p74BRM.d.ts} +29 -14
  25. package/dist/{index-CCynrS6c.d.ts → index-DNNj_0ba.d.ts} +3 -2
  26. package/dist/{index-lgHdCda5.d.ts → index-DQkL37eL.d.ts} +10 -2
  27. package/dist/{index-DswWDEXZ.d.ts → index-Doh4VTXr.d.ts} +65 -35
  28. package/dist/index.d.ts +8 -8
  29. package/dist/index.js +5 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/preview/index.d.ts +2 -2
  32. package/dist/preview/index.js +3 -2
  33. package/dist/{preview-CWT0lgwq.js → preview-DKyKKcu7.js} +4 -4
  34. package/dist/{preview-CWT0lgwq.js.map → preview-DKyKKcu7.js.map} +1 -1
  35. package/dist/provider/index.js +1 -1
  36. package/dist/{use-dynamic-tailwind-DAhKGsY4.js → use-dynamic-tailwind-BF8TQ3Yy.js} +83 -27
  37. package/dist/use-dynamic-tailwind-BF8TQ3Yy.js.map +1 -0
  38. package/dist/utils/ast/index.d.ts +2 -2
  39. package/dist/utils/ast/index.js +3 -3
  40. package/dist/utils/index.d.ts +4 -7
  41. package/dist/utils/index.js +3 -3
  42. package/dist/{utils-WyxPYZGh.js → utils-BQ7WL7Jh.js} +33 -16
  43. package/dist/{utils-WyxPYZGh.js.map → utils-BQ7WL7Jh.js.map} +1 -1
  44. package/package.json +4 -2
  45. package/dist/ast-Bx2Avo4d.js +0 -1530
  46. package/dist/ast-Bx2Avo4d.js.map +0 -1
  47. package/dist/boundary-BTP1nfG6.js.map +0 -1
  48. package/dist/context-649REFsj.js.map +0 -1
  49. package/dist/dnd-D90jLjLH.js.map +0 -1
  50. package/dist/document-C6bNHl7l.js.map +0 -1
  51. package/dist/editor-LzqHe_-F.js +0 -51
  52. package/dist/editor-LzqHe_-F.js.map +0 -1
  53. package/dist/use-dynamic-tailwind-DAhKGsY4.js.map +0 -1
@@ -1,18 +1,18 @@
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";
1
+ import { D as fillSectionIds, E as createSectionPreviewCache, I as nanoid, K as __toESM, L as require_lib, U as DRAGGABLE_ITEMS, _ as parseArrayExpression, a as findEditableChildren, c as parseBinding, d as canLosslesslyEvaluateSource, m as extractObjectProperties, n as extract, o as getCurrentValue, p as extractNodeValue, s as getStructuredValue, v as parseValue, z as BINDING_PROP } from "./extract-abHYmNsu.js";
2
+ import { c as generateSection, d as preloadScripts, i as cn, p as replaceSections, s as extractSections } from "./utils-BQ7WL7Jh.js";
3
3
  import { i as usePreview } from "./states-Ci1AvoQ9.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";
4
+ import { _ as moveSelectedIndices, c as appendArrayItem, d as moveArrayItems, g as canStructurallyEditArray, h as updateArrayItemValue, i as replaceIds, l as duplicateArrayItems, m as updateArrayItemProperty, o as update, p as removeArrayItems, r as fillIds, s as getChildrenSignatures, t as validateBindingValue, u as moveArrayItem } from "./ast-CZwpVrJv.js";
5
+ import { i as Frame, n as useCompiledModule, t as useDynamicTailwind } from "./use-dynamic-tailwind-BF8TQ3Yy.js";
6
+ import { t as Core } from "./core-Co8Nlq16.js";
7
+ import { n as Error$1, t as ErrorBoundary } from "./boundary-C90q3YsT.js";
8
8
  import { Children, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
9
- import { Button, Card, Checkbox, ColorPicker, DatePicker, Drawer, Input, RichTextEditor, Select, Space, Splitter, Toast, Typography, Upload } from "@jbpark/ui-kit";
10
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
10
  import { DndContext, DragOverlay, PointerSensor, closestCenter, useDndContext, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
12
11
  import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
13
12
  import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
13
+ import { Button, Card, Checkbox, ColorPicker, DatePicker, Drawer, Input, RichTextEditor, Select, Space, Splitter, Toast, Typography } from "@jbpark/ui-kit";
14
14
  import { useDebounce, useMultiSelect, useResponsiveSize } from "@jbpark/use-hooks";
15
- import { AlertCircle, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Bell, Bookmark, Calendar, Camera, Check, ChevronDown, ChevronRight, ChevronUp, Clock, Copy, Download, Edit, Eye, Filter, Heart, Home, Image, Info, LayoutGrid, Link, Mail, Map as Map$1, Menu, MessageCircle, Phone, Plus, Search, Settings, Share, ShoppingCart, Star, Trash, Upload as Upload$1, User, X } from "lucide-react";
15
+ import { ArrowDown, ArrowUp, ChevronDown, ChevronUp, Copy, LayoutGrid, Plus, Trash, X } from "lucide-react";
16
16
  import { CSS } from "@dnd-kit/utilities";
17
17
  //#region src/components/dnd/droppable.tsx
18
18
  var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1);
@@ -36,6 +36,13 @@ const Droppable = ({ children, className }) => {
36
36
  });
37
37
  };
38
38
  //#endregion
39
+ //#region src/components/dnd/edit-options.ts
40
+ const toastEditError = (error) => {
41
+ Toast.error(error.title, error.description ? { description: error.description } : void 0);
42
+ };
43
+ const DndEditOptionsContext = createContext({ reportError: toastEditError });
44
+ const useDndEditOptions = () => useContext(DndEditOptionsContext);
45
+ //#endregion
39
46
  //#region src/components/dnd/draggable.tsx
40
47
  const DraggableItem = ({ item, children }) => {
41
48
  const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
@@ -78,8 +85,73 @@ const useDndPalette = () => useDndRegions("useDndPalette()").palette;
78
85
  const useDndPanel = () => useDndRegions("useDndPanel()").panel;
79
86
  const useDndLayout = () => useDndRegions("useDndLayout()");
80
87
  //#endregion
88
+ //#region src/components/dnd/panel-binding.ts
89
+ const readAttribute = (node, name) => node.dataAttributes.find((attribute) => attribute.name === name)?.value;
90
+ const canEditBindingValue = (node, binding) => {
91
+ 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;
92
+ const attribute = node.attributes.find((candidate) => candidate.name === binding.property);
93
+ return attribute?.isStringLiteral === true || canLosslesslyEvaluateSource(attribute?.value ?? "");
94
+ };
95
+ const toBindingFields = (binding) => ({
96
+ label: binding.label,
97
+ property: binding.property,
98
+ type: binding.type,
99
+ widget: binding.widget,
100
+ options: binding.options,
101
+ render: binding.render,
102
+ min: binding.min,
103
+ max: binding.max,
104
+ pattern: binding.pattern,
105
+ required: binding.required,
106
+ meta: binding.meta
107
+ });
108
+ const resolveRenderEntry = (render, key) => {
109
+ const entry = render?.[key];
110
+ if (entry && "type" in entry) {
111
+ const leaf = entry;
112
+ return {
113
+ ...leaf,
114
+ label: leaf.label ?? key,
115
+ property: leaf.property ?? key
116
+ };
117
+ }
118
+ return {
119
+ label: key,
120
+ property: key,
121
+ render: entry
122
+ };
123
+ };
124
+ const toPanelBindingData = (node, id, binding) => ({
125
+ ...toBindingFields(binding),
126
+ id,
127
+ value: getStructuredValue(node, binding.property, binding.type),
128
+ rawValue: getCurrentValue(node, binding.property),
129
+ ...!canEditBindingValue(node, binding) && { canEditValue: false }
130
+ });
131
+ const resolvePanelBindings = (node) => {
132
+ const id = readAttribute(node, "data-id");
133
+ const bindingAttr = readAttribute(node, "data-binding");
134
+ if (!id || !bindingAttr) return null;
135
+ const parsed = node.bindings ?? parseBinding(bindingAttr);
136
+ if (!parsed.length) return null;
137
+ return {
138
+ id,
139
+ tagName: node.tagName || "element",
140
+ bindings: parsed.map((binding) => toPanelBindingData(node, id, binding))
141
+ };
142
+ };
143
+ const withPanelCommit = (bindings, commit) => bindings.map((binding) => ({
144
+ ...binding,
145
+ onChange: (value) => commit?.({
146
+ id: binding.id,
147
+ label: binding.label,
148
+ property: binding.property,
149
+ value
150
+ })
151
+ }));
152
+ //#endregion
81
153
  //#region src/components/dnd/panel/bulk-actions-bar.tsx
82
- const BulkActionsBar = ({ count, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
154
+ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
83
155
  if (count === 0) return null;
84
156
  return /* @__PURE__ */ jsxs("div", {
85
157
  className: "flex items-center justify-between rounded border\n border-blue-200 bg-blue-50 p-2",
@@ -93,18 +165,21 @@ const BulkActionsBar = ({ count, onDuplicate, onMoveUp, onMoveDown, onDelete, on
93
165
  size: "small",
94
166
  icon: /* @__PURE__ */ jsx(Copy, {}),
95
167
  title: "Duplicate selected",
168
+ disabled,
96
169
  onClick: onDuplicate
97
170
  }),
98
171
  /* @__PURE__ */ jsx(Button, {
99
172
  size: "small",
100
173
  icon: /* @__PURE__ */ jsx(ArrowUp, {}),
101
174
  title: "Move selected up",
175
+ disabled,
102
176
  onClick: onMoveUp
103
177
  }),
104
178
  /* @__PURE__ */ jsx(Button, {
105
179
  size: "small",
106
180
  icon: /* @__PURE__ */ jsx(ArrowDown, {}),
107
181
  title: "Move selected down",
182
+ disabled,
108
183
  onClick: onMoveDown
109
184
  }),
110
185
  /* @__PURE__ */ jsx(Button, {
@@ -112,6 +187,7 @@ const BulkActionsBar = ({ count, onDuplicate, onMoveUp, onMoveDown, onDelete, on
112
187
  size: "small",
113
188
  icon: /* @__PURE__ */ jsx(X, {}),
114
189
  title: "Delete selected",
190
+ disabled,
115
191
  onClick: onDelete
116
192
  }),
117
193
  /* @__PURE__ */ jsx(Button, {
@@ -126,77 +202,92 @@ const BulkActionsBar = ({ count, onDuplicate, onMoveUp, onMoveDown, onDelete, on
126
202
  //#endregion
127
203
  //#region src/components/dnd/panel/node.tsx
128
204
  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;
205
+ const source = resolvePanelBindings(data);
206
+ if (!source) return null;
135
207
  return /* @__PURE__ */ jsx("div", {
136
208
  className: "space-y-2 rounded",
137
209
  children: /* @__PURE__ */ jsx("div", {
138
210
  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
211
+ children: withPanelCommit(source.bindings, onChange).map((binding) => /* @__PURE__ */ jsxs("div", {
212
+ className: "space-y-1",
213
+ children: [/* @__PURE__ */ jsxs("label", {
214
+ className: "block text-xs font-semibold text-gray-700",
215
+ children: [binding.label, /* @__PURE__ */ jsxs("span", {
216
+ className: "ml-1 text-gray-400",
217
+ children: [
218
+ "(",
219
+ binding.property,
220
+ ")"
221
+ ]
178
222
  })]
179
- }, `${binding.property}-${binding.label}`);
180
- })
223
+ }), /* @__PURE__ */ jsx(Field, {
224
+ binding,
225
+ onNodeChange: onChange
226
+ })]
227
+ }, `${binding.id}-${binding.property}`))
181
228
  })
182
229
  });
183
230
  };
184
231
  //#endregion
232
+ //#region src/components/dnd/panel/use-structural-selection.ts
233
+ const selectionAfter = (command, selected, count) => {
234
+ switch (command.type) {
235
+ case "append":
236
+ case "duplicate": return new Set(selected);
237
+ case "move-selected": return moveSelectedIndices(Array.from({ length: count }, (_, index) => index), new Set(command.indices), command.direction).indices;
238
+ default: return /* @__PURE__ */ new Set();
239
+ }
240
+ };
241
+ const useStructuralSelection = (count, revision) => {
242
+ const selection = useMultiSelect(count);
243
+ const [seen, setSeen] = useState(revision);
244
+ const [pending, setPending] = useState(null);
245
+ if (revision !== seen) {
246
+ setSeen(revision);
247
+ setPending(null);
248
+ if (pending?.matches(revision) && pending.selection.size > 0) selection.replace(pending.selection);
249
+ else selection.clear();
250
+ }
251
+ const record = (next, matches, { now = false } = {}) => {
252
+ setPending({
253
+ matches,
254
+ selection: next
255
+ });
256
+ if (!now) return;
257
+ if (next.size > 0) selection.replace(next);
258
+ else selection.clear();
259
+ };
260
+ return {
261
+ selection,
262
+ record
263
+ };
264
+ };
265
+ //#endregion
185
266
  //#region src/components/dnd/panel/use-children-editor.ts
267
+ const producedBy = (expected, action) => {
268
+ if (action.type === "move-selected") {
269
+ const moved = JSON.stringify(moveSelectedIndices(expected, new Set(action.indices), action.direction).items);
270
+ return (revision) => revision === moved;
271
+ }
272
+ const added = action.type === "append" ? 1 : action.type === "duplicate" ? action.indices.length : 0;
273
+ return (revision) => {
274
+ const next = JSON.parse(revision);
275
+ return next.length === expected.length + added && expected.every((signature, index) => next[index] === signature);
276
+ };
277
+ };
186
278
  const useChildrenEditor = (items, { onChange } = {}) => {
187
- const selection = useMultiSelect(items.length);
188
279
  const expected = useMemo(() => getChildrenSignatures(items), [items]);
189
280
  const revision = JSON.stringify(expected);
190
- const clear = selection.clear;
191
- useEffect(() => {
192
- clear();
193
- }, [revision, clear]);
281
+ const { selection, record } = useStructuralSelection(items.length, revision);
194
282
  const commit = (action) => {
195
- onChange?.(JSON.stringify({
283
+ const edit = {
196
284
  kind: "children-edit",
197
285
  expected,
198
286
  action
199
- }));
287
+ };
288
+ const next = selectionAfter(action, selection.selected, items.length);
289
+ if (next.size > 0) record(next, producedBy(expected, action));
290
+ onChange?.(JSON.stringify(edit));
200
291
  };
201
292
  return {
202
293
  items,
@@ -356,63 +447,7 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
356
447
  });
357
448
  };
358
449
  //#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
407
450
  //#region src/components/dnd/panel/use-items-editor.ts
408
- const resolveLeaf = (render, key) => {
409
- const leaf = render?.[key];
410
- return leaf && "type" in leaf ? leaf : null;
411
- };
412
- const resolveMap = (render, key) => {
413
- const leaf = render?.[key];
414
- return leaf && !("type" in leaf) ? leaf : void 0;
415
- };
416
451
  const bindingsInJSX = (source) => {
417
452
  const nodes = extract(source);
418
453
  const container = nodes.find((n) => n.bindings?.some((b) => b.property === "children"));
@@ -512,8 +547,19 @@ const removeIds = (ids, indices) => {
512
547
  };
513
548
  const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
514
549
  const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value), [value]);
550
+ const canEditStructure = useMemo(() => canStructurallyEditArray(value), [value]);
551
+ const { reportError } = useDndEditOptions();
552
+ const reportErrorRef = useRef(reportError);
553
+ useEffect(() => {
554
+ reportErrorRef.current = reportError;
555
+ });
515
556
  useEffect(() => {
516
- if (parseError) Toast.error("Failed to parse items", { description: "Check the console for details." });
557
+ if (parseError) reportErrorRef.current({
558
+ type: "parse",
559
+ target: "items",
560
+ title: "Failed to parse items",
561
+ description: "Check the console for details."
562
+ });
517
563
  }, [parseError]);
518
564
  const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
519
565
  const isPrimitive = kind === "primitive";
@@ -525,16 +571,21 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
525
571
  rawItems,
526
572
  value
527
573
  ]);
528
- const selection = useMultiSelect(isPrimitive ? primitiveItems.length : objectItems.length);
529
- const commit = (next, nextIds) => {
574
+ const { selection, record } = useStructuralSelection(isPrimitive ? primitiveItems.length : objectItems.length, value);
575
+ const commit = (next, nextIds, nextSelection) => {
530
576
  if (next === null) {
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." });
577
+ reportError({
578
+ type: "items",
579
+ title: "Failed to update this item",
580
+ description: "The source was preserved. Structural edits require a dense array without spreads; use the code editor for unsupported syntax."
581
+ });
532
582
  return false;
533
583
  }
534
584
  const parsed = parseSource(next);
535
585
  const nextItems = kind === "primitive" ? parsed.primitiveItems : parsed.objectItems;
536
586
  if (nextIds) setIdentityState(createIdentityState(next, kind, nextItems, nextIds));
537
587
  else setIdentityState(createIdentityState(next, kind, nextItems, identity.ids));
588
+ record(nextSelection, (revision) => revision === next, { now: true });
538
589
  onChange?.(next);
539
590
  return true;
540
591
  };
@@ -542,7 +593,7 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
542
593
  return new Set((isPrimitive ? primitiveItems : objectItems).filter((item) => indices.has(item.index)).map((item) => item.elementIndex));
543
594
  };
544
595
  const updateProperty = (elementIndex, propertyKey, next) => {
545
- commit(updateArrayItemProperty(value, elementIndex, propertyKey, next, render));
596
+ commit(updateArrayItemProperty(value, elementIndex, propertyKey, next, render), void 0, new Set(selection.selected));
546
597
  };
547
598
  return {
548
599
  kind,
@@ -557,58 +608,37 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
557
608
  label: `item-${item.index}`,
558
609
  property: item.type,
559
610
  value: item.value ?? "",
560
- rawValue: String(item.value ?? ""),
561
- onChange: (next) => commit(updateArrayItemValue(value, item.elementIndex, next))
611
+ rawValue: item.type === "unknown" ? item.source : String(item.value ?? ""),
612
+ canEditValue: canLosslesslyEvaluateSource(item.source),
613
+ onChange: (next) => commit(updateArrayItemValue(value, item.elementIndex, next), void 0, new Set(selection.selected))
562
614
  }
563
615
  })) : objectItems.map((item) => {
564
616
  const id = identity.ids[item.index] ?? item.id;
565
617
  const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
566
- const leaf = resolveLeaf(render, key);
618
+ const binding = resolveRenderEntry(render, key);
619
+ const propertySource = value.slice(prop.astNode.start, prop.astNode.end);
567
620
  return {
621
+ ...toBindingFields(binding),
568
622
  id: `item-${id}-${key}`,
569
- label: key,
570
- property: leaf ? leaf.property ?? leaf.type : key,
571
- type: leaf?.type,
572
- render: leaf ? leaf.render : resolveMap(render, key),
623
+ meta: {
624
+ ...binding.meta,
625
+ valueType: prop.type
626
+ },
573
627
  value: parseValue(String(prop.value)),
574
- rawValue: String(prop.value),
575
- meta: { valueType: prop.type },
628
+ rawValue: prop.type === "unknown" ? propertySource : String(prop.value),
629
+ canEditValue: canLosslesslyEvaluateSource(propertySource),
576
630
  onChange: (next) => updateProperty(item.elementIndex, key, next)
577
631
  };
578
632
  });
579
633
  const nested = [...Object.entries(item.jsxBindings).map(([property, nodes]) => ({
580
634
  property,
581
635
  elements: nodes.flatMap((node) => {
582
- const id = node.dataAttributes.find((a) => a.name === "data-id")?.value;
583
- const attr = node.dataAttributes.find((a) => a.name === "data-binding")?.value;
584
- if (!id || !attr) return [];
585
- const parsed = node.bindings ?? parseBinding(attr);
586
- if (!parsed.length) return [];
636
+ const source = resolvePanelBindings(node);
637
+ if (!source) return [];
587
638
  return [{
588
- id,
589
- tagName: node.tagName || "element",
590
- bindings: parsed.map((binding) => ({
591
- id,
592
- label: binding.label,
593
- property: binding.property,
594
- type: binding.type,
595
- widget: binding.widget,
596
- options: binding.options,
597
- render: binding.render,
598
- min: binding.min,
599
- max: binding.max,
600
- pattern: binding.pattern,
601
- required: binding.required,
602
- meta: binding.meta,
603
- value: getStructuredValue(node, binding.property, binding.type),
604
- rawValue: getCurrentValue(node, binding.property),
605
- onChange: (next) => onNodeChange?.({
606
- id,
607
- label: binding.label,
608
- property: binding.property,
609
- value: next
610
- })
611
- }))
639
+ id: source.id,
640
+ tagName: source.tagName,
641
+ bindings: withPanelCommit(source.bindings, onNodeChange)
612
642
  }];
613
643
  })
614
644
  })), ...Object.entries(item.jsxFallbacks).map(([property, code]) => ({
@@ -621,6 +651,7 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
621
651
  type: "jsx",
622
652
  value: code,
623
653
  rawValue: code,
654
+ canEditValue: true,
624
655
  onChange: (next) => updateProperty(item.elementIndex, property, next)
625
656
  }
626
657
  }))];
@@ -636,40 +667,43 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
636
667
  actions: {
637
668
  add: () => {
638
669
  const next = appendArrayItem(value, kind);
639
- commit(next, next ? [...identity.ids, nanoid(6)] : void 0);
670
+ commit(next, next ? [...identity.ids, nanoid(6)] : void 0, selectionAfter({ type: "append" }, selection.selected, rawItems.length));
640
671
  },
641
672
  move: (elementIndex, toIndex) => {
642
673
  const items = isPrimitive ? primitiveItems : objectItems;
643
674
  const from = items.find((item) => item.elementIndex === elementIndex);
644
675
  const target = items.find((item) => item.index === toIndex);
645
676
  if (!from || !target) return;
646
- if (commit(moveArrayItem(value, elementIndex, target.elementIndex), moveId(identity.ids, from.index, target.index))) selection.clear();
677
+ commit(moveArrayItem(value, elementIndex, target.elementIndex), moveId(identity.ids, from.index, target.index), /* @__PURE__ */ new Set());
647
678
  },
648
679
  remove: (elementIndex) => {
649
680
  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();
681
+ commit(removeArrayItems(value, /* @__PURE__ */ new Set([elementIndex]), kind), removed ? removeIds(identity.ids, /* @__PURE__ */ new Set([removed.index])) : void 0, /* @__PURE__ */ new Set());
651
682
  },
652
683
  duplicateSelected: () => {
653
684
  const selected = elementIndicesOf(selection.selected);
654
685
  const next = duplicateArrayItems(value, selected);
655
686
  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);
687
+ commit(next, next ? [...identity.ids, ...copied] : void 0, selectionAfter({
688
+ type: "duplicate",
689
+ indices: [...selection.selected]
690
+ }, selection.selected, rawItems.length));
657
691
  },
658
692
  moveSelected: (direction) => {
659
693
  const nextIdentity = moveSelectedIndices(identity.ids, selection.selected, direction);
660
694
  const result = moveArrayItems(value, elementIndicesOf(selection.selected), direction);
661
695
  if (!result) {
662
- commit(null);
696
+ commit(null, void 0, selection.selected);
663
697
  return;
664
698
  }
665
699
  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);
700
+ commit(result.code, nextIdentity.items, new Set(visible.flatMap((position, index) => result.indices.has(position) ? [index] : [])));
668
701
  },
669
702
  removeSelected: () => {
670
- if (commit(removeArrayItems(value, elementIndicesOf(selection.selected), kind), removeIds(identity.ids, selection.selected))) selection.clear();
703
+ commit(removeArrayItems(value, elementIndicesOf(selection.selected), kind), removeIds(identity.ids, selection.selected), /* @__PURE__ */ new Set());
671
704
  }
672
705
  },
706
+ canEditStructure,
673
707
  parseError
674
708
  };
675
709
  };
@@ -732,7 +766,7 @@ const NestedGroup = ({ group, onNodeChange }) => {
732
766
  });
733
767
  };
734
768
  const Items = ({ value, render, onChange, onChildChange }) => {
735
- const { kind, items, selection, actions, parseError } = useItemsEditor(value, {
769
+ const { kind, items, selection, actions, canEditStructure, parseError } = useItemsEditor(value, {
736
770
  render,
737
771
  onChange,
738
772
  onNodeChange: onChildChange
@@ -746,7 +780,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
746
780
  items.length,
747
781
  ")"
748
782
  ]
749
- }), items.length > 0 && /* @__PURE__ */ jsx(Button, {
783
+ }), items.length > 0 && canEditStructure && /* @__PURE__ */ jsx(Button, {
750
784
  size: "small",
751
785
  icon: /* @__PURE__ */ jsx(Plus, {}),
752
786
  variant: "solid",
@@ -771,6 +805,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
771
805
  });
772
806
  const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
773
807
  count: selection.selected.size,
808
+ disabled: !canEditStructure,
774
809
  onDuplicate: actions.duplicateSelected,
775
810
  onMoveUp: () => actions.moveSelected("up"),
776
811
  onMoveDown: () => actions.moveSelected("down"),
@@ -783,20 +818,20 @@ const Items = ({ value, render, onChange, onChildChange }) => {
783
818
  /* @__PURE__ */ jsx(Button, {
784
819
  size: "small",
785
820
  icon: /* @__PURE__ */ jsx(ArrowUp, {}),
786
- disabled: item.index === 0,
821
+ disabled: !canEditStructure || item.index === 0,
787
822
  onClick: () => actions.move(item.elementIndex, item.index - 1)
788
823
  }),
789
824
  /* @__PURE__ */ jsx(Button, {
790
825
  size: "small",
791
826
  icon: /* @__PURE__ */ jsx(ArrowDown, {}),
792
- disabled: item.index === items.length - 1,
827
+ disabled: !canEditStructure || item.index === items.length - 1,
793
828
  onClick: () => actions.move(item.elementIndex, item.index + 1)
794
829
  }),
795
830
  /* @__PURE__ */ jsx(Button, {
796
831
  danger: true,
797
832
  size: "small",
798
833
  icon: /* @__PURE__ */ jsx(X, {}),
799
- disabled: items.length <= 1,
834
+ disabled: !canEditStructure || items.length <= 1,
800
835
  onClick: () => actions.remove(item.elementIndex)
801
836
  })
802
837
  ]
@@ -806,15 +841,20 @@ const Items = ({ value, render, onChange, onChildChange }) => {
806
841
  children: [
807
842
  header,
808
843
  bulkBar,
844
+ !canEditStructure && /* @__PURE__ */ jsx("div", {
845
+ className: "rounded border border-dashed border-amber-200 p-3 text-xs\n text-amber-700",
846
+ children: "Values remain editable, but moving, copying, adding, and deleting require a dense array without spreads or parenthesized top-level items. Use the code editor for those structural changes."
847
+ }),
809
848
  items.map((item) => /* @__PURE__ */ jsxs("div", {
810
849
  className: "space-y-2 rounded border border-gray-100 bg-gray-50 p-2",
811
850
  children: [/* @__PURE__ */ jsxs("div", {
812
851
  className: "flex items-center justify-between space-x-1",
813
852
  children: [/* @__PURE__ */ jsx("div", {
814
- onClick: (e) => selection.toggle(item.index, e.shiftKey),
853
+ onClick: (e) => canEditStructure && selection.toggle(item.index, e.shiftKey),
815
854
  className: "inline-flex",
816
855
  children: /* @__PURE__ */ jsx(Checkbox, {
817
856
  checked: selection.isSelected(item.index),
857
+ disabled: !canEditStructure,
818
858
  onChange: () => {}
819
859
  })
820
860
  }), itemControls(item)]
@@ -830,6 +870,10 @@ const Items = ({ value, render, onChange, onChildChange }) => {
830
870
  children: [
831
871
  header,
832
872
  bulkBar,
873
+ !canEditStructure && /* @__PURE__ */ jsx("div", {
874
+ className: "rounded border border-dashed border-amber-200 p-3 text-xs\n text-amber-700",
875
+ children: "Values remain editable, but moving, copying, adding, and deleting require a dense array without spreads or parenthesized top-level items. Use the code editor for those structural changes."
876
+ }),
833
877
  items.map((item) => /* @__PURE__ */ jsxs("div", {
834
878
  className: "space-y-3 rounded border bg-gray-50 p-3",
835
879
  children: [
@@ -838,10 +882,11 @@ const Items = ({ value, render, onChange, onChildChange }) => {
838
882
  children: [/* @__PURE__ */ jsxs("div", {
839
883
  className: "flex items-center space-x-2",
840
884
  children: [/* @__PURE__ */ jsx("div", {
841
- onClick: (e) => selection.toggle(item.index, e.shiftKey),
885
+ onClick: (e) => canEditStructure && selection.toggle(item.index, e.shiftKey),
842
886
  className: "inline-flex",
843
887
  children: /* @__PURE__ */ jsx(Checkbox, {
844
888
  checked: selection.isSelected(item.index),
889
+ disabled: !canEditStructure,
845
890
  onChange: () => {}
846
891
  })
847
892
  }), /* @__PURE__ */ jsxs("div", {
@@ -976,7 +1021,7 @@ const ColorPickerField = ({ value, onChange }) => {
976
1021
  }
977
1022
  });
978
1023
  };
979
- const Field = ({ binding, onNodeChange }) => {
1024
+ const BuiltinField = ({ binding, onNodeChange }) => {
980
1025
  const { id, value, rawValue, onChange } = binding;
981
1026
  const [validationError, setValidationError] = useState(null);
982
1027
  const [text, setText] = useState(rawValue);
@@ -985,6 +1030,17 @@ const Field = ({ binding, onNodeChange }) => {
985
1030
  setPrevRawValue(rawValue);
986
1031
  setText(rawValue);
987
1032
  }
1033
+ if (binding.canEditValue === false) return /* @__PURE__ */ jsxs("div", {
1034
+ className: "space-y-1 rounded border border-dashed border-amber-200 p-3\n text-xs text-amber-700",
1035
+ children: [
1036
+ /* @__PURE__ */ jsx("div", { children: "This expression is preserved but is not editable here." }),
1037
+ /* @__PURE__ */ jsx("code", {
1038
+ className: "block overflow-x-auto text-amber-800",
1039
+ children: rawValue
1040
+ }),
1041
+ /* @__PURE__ */ jsx("div", { children: "Use the code editor to change it." })
1042
+ ]
1043
+ });
988
1044
  const commitIfChanged = (next) => {
989
1045
  if (next !== (typeof next === "string" ? rawValue : value)) onChange(next);
990
1046
  };
@@ -1016,30 +1072,30 @@ const Field = ({ binding, onNodeChange }) => {
1016
1072
  const objectValue = value;
1017
1073
  return /* @__PURE__ */ jsx("div", {
1018
1074
  className: "space-y-2 rounded border border-gray-200 bg-gray-50 p-2",
1019
- children: Object.entries(objectValue).map(([key, val]) => /* @__PURE__ */ jsxs("div", {
1020
- className: "space-y-1",
1021
- children: [/* @__PURE__ */ jsx("label", {
1022
- className: "block text-xs font-medium text-gray-600",
1023
- children: key
1024
- }), /* @__PURE__ */ jsx(Field, {
1025
- binding: {
1026
- id,
1027
- label: key,
1028
- property: binding.render?.[key] && "type" in binding.render[key] ? binding.render[key].type : key,
1029
- type: binding.render?.[key] && "type" in binding.render[key] ? binding.render[key].type : void 0,
1030
- render: binding.render?.[key] && !("type" in binding.render[key]) ? binding.render[key] : void 0,
1031
- value: val,
1032
- rawValue: typeof val === "object" && val !== null ? JSON.stringify(val) : String(val),
1033
- onChange: (next) => {
1034
- onChange({
1035
- ...objectValue,
1036
- [key]: next
1037
- });
1038
- }
1039
- },
1040
- onNodeChange
1041
- })]
1042
- }, key))
1075
+ children: Object.entries(objectValue).map(([key, val]) => {
1076
+ const nested = resolveRenderEntry(binding.render, key);
1077
+ return /* @__PURE__ */ jsxs("div", {
1078
+ className: "space-y-1",
1079
+ children: [/* @__PURE__ */ jsx("label", {
1080
+ className: "block text-xs font-medium text-gray-600",
1081
+ children: nested.label
1082
+ }), /* @__PURE__ */ jsx(Field, {
1083
+ binding: {
1084
+ ...toBindingFields(nested),
1085
+ id,
1086
+ value: val,
1087
+ rawValue: typeof val === "object" && val !== null ? JSON.stringify(val) : String(val),
1088
+ onChange: (next) => {
1089
+ onChange({
1090
+ ...objectValue,
1091
+ [key]: next
1092
+ });
1093
+ }
1094
+ },
1095
+ onNodeChange
1096
+ })]
1097
+ }, key);
1098
+ })
1043
1099
  });
1044
1100
  }
1045
1101
  if (binding.type === "boolean" || typeof value === "boolean") return /* @__PURE__ */ jsx(Checkbox, {
@@ -1093,61 +1149,6 @@ const Field = ({ binding, onNodeChange }) => {
1093
1149
  className: "mt-1 text-xs text-red-500",
1094
1150
  children: validationError
1095
1151
  })] });
1096
- if (binding.widget === "icon-picker" || binding.type === "icon-picker") {
1097
- const SelectedIcon = ICON_MAP[stringValue];
1098
- return /* @__PURE__ */ jsxs("div", {
1099
- className: "flex items-center gap-2",
1100
- children: [/* @__PURE__ */ jsx(Select, {
1101
- value: stringValue,
1102
- options: ICON_OPTIONS,
1103
- onChange: commitIfChanged
1104
- }), SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, {
1105
- size: 18,
1106
- className: "shrink-0"
1107
- })]
1108
- });
1109
- }
1110
- if (binding.widget === "asset-picker" || binding.type === "asset-picker") {
1111
- const defaultUploadValue = stringValue ? [{
1112
- uid: "current",
1113
- name: stringValue.split("/").pop() || "asset",
1114
- url: stringValue
1115
- }] : [];
1116
- return /* @__PURE__ */ jsxs("div", {
1117
- className: "space-y-2",
1118
- children: [
1119
- /* @__PURE__ */ jsx(Input, {
1120
- value: text,
1121
- onChange: (e) => setText(e.target.value),
1122
- placeholder: "Enter an image URL",
1123
- onBlur: (e) => {
1124
- const next = e.target.value.trim();
1125
- const result = validateBindingValue(binding, next);
1126
- if (!result.valid) {
1127
- setValidationError(result.message ?? "Invalid value.");
1128
- return;
1129
- }
1130
- setValidationError(null);
1131
- commitIfChanged(next);
1132
- }
1133
- }),
1134
- /* @__PURE__ */ jsx(Upload, {
1135
- multiple: false,
1136
- maxCount: 1,
1137
- accept: "image/*",
1138
- defaultValue: defaultUploadValue,
1139
- onChange: (files) => {
1140
- const next = files[0]?.url ?? "";
1141
- commitIfChanged(next);
1142
- }
1143
- }),
1144
- validationError && /* @__PURE__ */ jsx("p", {
1145
- className: "mt-1 text-xs text-red-500",
1146
- children: validationError
1147
- })
1148
- ]
1149
- });
1150
- }
1151
1152
  if (binding.type === "number" || typeof value === "number") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
1152
1153
  type: "number",
1153
1154
  value: text,
@@ -1188,6 +1189,13 @@ const Field = ({ binding, onNodeChange }) => {
1188
1189
  children: validationError
1189
1190
  })] });
1190
1191
  };
1192
+ const Field = (props) => {
1193
+ const { renderField } = useDndEditOptions();
1194
+ const builtin = /* @__PURE__ */ jsx(BuiltinField, { ...props });
1195
+ if (!renderField) return builtin;
1196
+ const custom = renderField(props, builtin);
1197
+ return custom === void 0 ? builtin : /* @__PURE__ */ jsx(Fragment, { children: custom });
1198
+ };
1191
1199
  //#endregion
1192
1200
  //#region src/components/dnd/panel/field-group.tsx
1193
1201
  const FieldGroup = ({ bindings, onNodeChange }) => /* @__PURE__ */ jsx("div", {
@@ -1379,16 +1387,59 @@ const Layout = ({ palette, panel }) => {
1379
1387
  ] });
1380
1388
  };
1381
1389
  //#endregion
1390
+ //#region src/components/dnd/section-fallback-context.ts
1391
+ const SectionFallbackContext = createContext(void 0);
1392
+ //#endregion
1393
+ //#region src/components/dnd/section-fallback.tsx
1394
+ const ConsumerFallback = ({ render, args, builtin }) => {
1395
+ const node = render(args);
1396
+ return node === void 0 ? builtin : /* @__PURE__ */ jsx(Fragment, { children: node });
1397
+ };
1398
+ const SectionFallback = ({ args, builtin }) => {
1399
+ const render = useContext(SectionFallbackContext);
1400
+ if (!render) return builtin;
1401
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
1402
+ fallback: () => builtin,
1403
+ children: /* @__PURE__ */ jsx(ConsumerFallback, {
1404
+ render,
1405
+ args,
1406
+ builtin
1407
+ })
1408
+ });
1409
+ };
1410
+ //#endregion
1382
1411
  //#region src/components/dnd/renderer.tsx
1383
- const Renderer = ({ preview, headers, modules, frame, dynamicTailwind = false, provider }) => {
1384
- const module = useCompiledModule(preview, modules);
1385
- const { ref: wrapperRef, css: dynamicCSS } = useDynamicTailwind(preview, dynamicTailwind);
1412
+ const Renderer = ({ preview, sectionId = "", sectionName = "", sectionCode = preview, forceFallback = false, headers, modules, frame, dynamicTailwind = false, provider }) => {
1413
+ const module = useCompiledModule(forceFallback ? "" : preview, modules);
1414
+ const section = {
1415
+ id: sectionId,
1416
+ name: sectionName,
1417
+ code: sectionCode
1418
+ };
1419
+ const { ref: wrapperRef, css: dynamicCSS } = useDynamicTailwind(preview, dynamicTailwind && !forceFallback);
1386
1420
  const renderProvider = (component) => {
1387
1421
  return provider ? provider(component) : component;
1388
1422
  };
1389
- if (module?.error) return /* @__PURE__ */ jsx(Error$1, {
1390
- message: module.error,
1391
- title: "Compile Error"
1423
+ if (forceFallback) return /* @__PURE__ */ jsx(SectionFallback, {
1424
+ args: {
1425
+ section,
1426
+ reason: "forced"
1427
+ },
1428
+ builtin: /* @__PURE__ */ jsx(Error$1, {
1429
+ message: "This section is not rendered in this editor.",
1430
+ title: "Section Unavailable"
1431
+ })
1432
+ });
1433
+ if (module?.error) return /* @__PURE__ */ jsx(SectionFallback, {
1434
+ args: {
1435
+ section,
1436
+ reason: "compile",
1437
+ message: module.error
1438
+ },
1439
+ builtin: /* @__PURE__ */ jsx(Error$1, {
1440
+ message: module.error,
1441
+ title: "Compile Error"
1442
+ })
1392
1443
  });
1393
1444
  const Component = module?.exports?.default;
1394
1445
  if (!Component) return null;
@@ -1401,6 +1452,18 @@ const Renderer = ({ preview, headers, modules, frame, dynamicTailwind = false, p
1401
1452
  "data-editor-mode": true,
1402
1453
  children: /* @__PURE__ */ jsx(ErrorBoundary, {
1403
1454
  resetKeys: [preview],
1455
+ fallback: (message, reset) => /* @__PURE__ */ jsx(SectionFallback, {
1456
+ args: {
1457
+ section,
1458
+ reason: "runtime",
1459
+ message
1460
+ },
1461
+ builtin: /* @__PURE__ */ jsx(Error$1, {
1462
+ message,
1463
+ onReset: reset,
1464
+ title: "Rendering Error"
1465
+ })
1466
+ }),
1404
1467
  children: renderProvider(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Component, {
1405
1468
  headers,
1406
1469
  container
@@ -1455,7 +1518,7 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
1455
1518
  };
1456
1519
  //#endregion
1457
1520
  //#region src/components/dnd/overlay.tsx
1458
- const Overlay = ({ sections, renderProps }) => {
1521
+ const Overlay = ({ sections, isForced, renderProps }) => {
1459
1522
  const { active } = useDndContext();
1460
1523
  if (!active) return null;
1461
1524
  if (active.data.current?.type === "new-item") {
@@ -1470,6 +1533,10 @@ const Overlay = ({ sections, renderProps }) => {
1470
1533
  name: section.name,
1471
1534
  children: /* @__PURE__ */ jsx(renderer_default, {
1472
1535
  preview,
1536
+ sectionId: section.id,
1537
+ sectionName: section.name,
1538
+ sectionCode: section.code,
1539
+ forceFallback: isForced?.(section) ?? false,
1473
1540
  modules: renderProps.modules,
1474
1541
  frame: renderProps.frame,
1475
1542
  dynamicTailwind: renderProps.dynamicTailwind
@@ -1586,6 +1653,10 @@ const describeUpdateFailure = (failure, label) => {
1586
1653
  title: `Failed to update "${label}"`,
1587
1654
  description: "The target element could not be found in this section."
1588
1655
  };
1656
+ case "unsupported-syntax": return {
1657
+ title: `Cannot edit "${label}" in the panel`,
1658
+ description: `The "${failure.property}" expression was preserved. Change it in the code editor instead.`
1659
+ };
1589
1660
  case "parse-error": return {
1590
1661
  title: `Failed to update "${label}"`,
1591
1662
  description: failure.error instanceof Error ? failure.error.message : "Check the console for details."
@@ -1598,11 +1669,19 @@ const conditionalModifiers = (args) => {
1598
1669
  if (active?.data.current?.type === "new-item") return args.transform;
1599
1670
  return restrictToVerticalAxis(args);
1600
1671
  };
1601
- const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, className, items = [], frame, dynamicTailwind = false, provider, children, ...restProps }) => {
1672
+ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, className, items = [], frame, dynamicTailwind = false, provider, renderField, onEditError, renderSectionFallback, shouldForceSectionFallback, children, ...restProps }) => {
1602
1673
  const [mobilePaletteOpen, setMobilePaletteOpen] = useState(false);
1603
1674
  const { breakpoint } = useResponsiveSize();
1604
1675
  const isMobile = breakpoint.current === "xs" || breakpoint.current === "sm";
1605
1676
  const { code } = usePreview();
1677
+ const isForced = (section) => {
1678
+ try {
1679
+ return shouldForceSectionFallback?.(section) === true;
1680
+ } catch (error) {
1681
+ console.error("shouldForceSectionFallback threw", error);
1682
+ return false;
1683
+ }
1684
+ };
1606
1685
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
1607
1686
  const value = _value === void 0 ? code : _value;
1608
1687
  const { sections, previews, selectedId, selectedItem, selectedIndex, select, clearSelection, add: addItem, remove: onDelete, copy: onCopy, move: moveSection, reorder, patch } = useSectionDocument(value, _onChange);
@@ -1629,32 +1708,49 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1629
1708
  if (!selectedCode) return {
1630
1709
  fields: [],
1631
1710
  updatedCode: "",
1632
- parseError: false
1711
+ parseError: null
1633
1712
  };
1634
1713
  try {
1635
1714
  const updated = fillIds(selectedCode);
1636
1715
  return {
1637
1716
  fields: extract(updated).filter((node) => node.tagName !== "section"),
1638
1717
  updatedCode: updated !== selectedCode ? updated : selectedCode,
1639
- parseError: false
1718
+ parseError: null
1640
1719
  };
1641
1720
  } catch (e) {
1642
1721
  console.warn("⚠️ Parsing error", e);
1643
1722
  return {
1644
1723
  fields: [],
1645
1724
  updatedCode: "",
1646
- parseError: true
1725
+ parseError: { error: e }
1647
1726
  };
1648
1727
  }
1649
1728
  }, [selectedCode]);
1729
+ const reportError = onEditError ?? toastEditError;
1730
+ const reportErrorRef = useRef(reportError);
1650
1731
  useEffect(() => {
1651
- if (parseError) Toast.error("Failed to parse this section", { description: "Check the console for details." });
1732
+ reportErrorRef.current = reportError;
1733
+ });
1734
+ useEffect(() => {
1735
+ if (parseError) reportErrorRef.current({
1736
+ type: "parse",
1737
+ target: "section",
1738
+ error: parseError.error,
1739
+ title: "Failed to parse this section",
1740
+ description: "Check the console for details."
1741
+ });
1652
1742
  }, [parseError]);
1653
1743
  const onFieldChange = ({ id, label, property, value: fieldValue }) => {
1654
1744
  const result = update(updatedCode, id, label, fieldValue, property);
1655
1745
  if (!result.success) {
1656
- const { title, description } = describeUpdateFailure(result.failure, label);
1657
- Toast.error(title, description ? { description } : void 0);
1746
+ reportError({
1747
+ type: "update",
1748
+ id,
1749
+ label,
1750
+ property,
1751
+ failure: result.failure,
1752
+ ...describeUpdateFailure(result.failure, label)
1753
+ });
1658
1754
  return;
1659
1755
  }
1660
1756
  if (selectedItem) onChange({
@@ -1662,37 +1758,8 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1662
1758
  code: result.code
1663
1759
  });
1664
1760
  };
1665
- const bindings = useMemo(() => {
1666
- return fields.flatMap((node) => {
1667
- const dataId = node.dataAttributes.find((a) => a.name === "data-id")?.value;
1668
- const bindingAttr = node.dataAttributes.find((a) => a.name === "data-binding")?.value;
1669
- if (!dataId || !bindingAttr) return [];
1670
- return (node.bindings ?? parseBinding(bindingAttr)).map((binding) => ({
1671
- id: dataId,
1672
- label: binding.label,
1673
- property: binding.property,
1674
- type: binding.type,
1675
- widget: binding.widget,
1676
- options: binding.options,
1677
- render: binding.render,
1678
- min: binding.min,
1679
- max: binding.max,
1680
- pattern: binding.pattern,
1681
- required: binding.required,
1682
- meta: binding.meta,
1683
- value: getStructuredValue(node, binding.property, binding.type),
1684
- rawValue: getCurrentValue(node, binding.property)
1685
- }));
1686
- });
1687
- }, [fields]).map((field) => ({
1688
- ...field,
1689
- onChange: (value) => onFieldChange({
1690
- id: field.id,
1691
- label: field.label,
1692
- property: field.property,
1693
- value
1694
- })
1695
- }));
1761
+ const bindingFields = useMemo(() => fields.flatMap((node) => resolvePanelBindings(node)?.bindings ?? []), [fields]);
1762
+ const bindings = withPanelCommit(bindingFields, onFieldChange);
1696
1763
  useEffect(() => {
1697
1764
  if (frame?.scripts?.length) preloadScripts(frame.scripts);
1698
1765
  }, [frame?.scripts]);
@@ -1735,6 +1802,10 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1735
1802
  onCopy,
1736
1803
  children: /* @__PURE__ */ jsx(renderer_default, {
1737
1804
  preview: previews[index],
1805
+ sectionId: section.id,
1806
+ sectionName: section.name,
1807
+ sectionCode: section.code,
1808
+ forceFallback: isForced(section),
1738
1809
  modules,
1739
1810
  frame,
1740
1811
  dynamicTailwind,
@@ -1744,40 +1815,50 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1744
1815
  }, section.id))
1745
1816
  })
1746
1817
  }) });
1747
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(DndContext, {
1748
- sensors,
1749
- collisionDetection: closestCenter,
1750
- modifiers: [conditionalModifiers],
1751
- onDragStart,
1752
- onDragOver,
1753
- onDragEnd,
1754
- children: [/* @__PURE__ */ jsx("div", {
1755
- className: cn("relative flex h-full w-full", className),
1756
- ...restProps,
1757
- children: /* @__PURE__ */ jsx(DndRegionContext.Provider, {
1758
- value: {
1759
- palette,
1760
- panel,
1761
- canvas,
1762
- isMobile,
1763
- selectedId,
1764
- clearSelection,
1765
- paletteOpen: mobilePaletteOpen,
1766
- setPaletteOpen: setMobilePaletteOpen
1767
- },
1768
- children: Children.toArray(children).length ? children : /* @__PURE__ */ jsx(Layout, {})
1769
- })
1770
- }), /* @__PURE__ */ jsx(DragOverlay, { children: /* @__PURE__ */ jsx(Overlay, {
1771
- sections,
1772
- renderProps: {
1773
- fullCode: value,
1774
- modules,
1775
- frame,
1776
- dynamicTailwind,
1777
- ...props
1778
- }
1779
- }) })]
1780
- }) });
1818
+ return /* @__PURE__ */ jsx(SectionFallbackContext.Provider, {
1819
+ value: renderSectionFallback,
1820
+ children: /* @__PURE__ */ jsxs(DndContext, {
1821
+ sensors,
1822
+ collisionDetection: closestCenter,
1823
+ modifiers: [conditionalModifiers],
1824
+ onDragStart,
1825
+ onDragOver,
1826
+ onDragEnd,
1827
+ children: [/* @__PURE__ */ jsx("div", {
1828
+ className: cn("relative flex h-full w-full", className),
1829
+ ...restProps,
1830
+ children: /* @__PURE__ */ jsx(DndEditOptionsContext.Provider, {
1831
+ value: {
1832
+ renderField,
1833
+ reportError
1834
+ },
1835
+ children: /* @__PURE__ */ jsx(DndRegionContext.Provider, {
1836
+ value: {
1837
+ palette,
1838
+ panel,
1839
+ canvas,
1840
+ isMobile,
1841
+ selectedId,
1842
+ clearSelection,
1843
+ paletteOpen: mobilePaletteOpen,
1844
+ setPaletteOpen: setMobilePaletteOpen
1845
+ },
1846
+ children: Children.toArray(children).length ? children : /* @__PURE__ */ jsx(Layout, {})
1847
+ })
1848
+ })
1849
+ }), /* @__PURE__ */ jsx(DragOverlay, { children: /* @__PURE__ */ jsx(Overlay, {
1850
+ sections,
1851
+ isForced,
1852
+ renderProps: {
1853
+ fullCode: value,
1854
+ modules,
1855
+ frame,
1856
+ dynamicTailwind,
1857
+ ...props
1858
+ }
1859
+ }) })]
1860
+ })
1861
+ });
1781
1862
  };
1782
1863
  //#endregion
1783
1864
  //#region src/components/dnd/index.ts
@@ -1789,6 +1870,6 @@ Dnd.Canvas = Canvas;
1789
1870
  Dnd.Panel = Panel;
1790
1871
  Dnd.Layout = Layout;
1791
1872
  //#endregion
1792
- 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 };
1873
+ export { Panel as a, useChildrenEditor as c, useDndPanel as d, DraggableItem as f, Palette as i, useDndLayout as l, Canvas as n, Field as o, Layout as r, useItemsEditor as s, Dnd as t, useDndPalette as u };
1793
1874
 
1794
- //# sourceMappingURL=dnd-D90jLjLH.js.map
1875
+ //# sourceMappingURL=dnd-_0jfnnVB.js.map