@jbpark/live-editor 3.1.0 → 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.
@@ -1,8 +1,8 @@
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";
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
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 { E as extractObjectProperties, O as parseArrayExpression, T as extractNodeValue, _ as updateArrayItemValue, b as getStructuredValue, d as duplicateArrayItems, f as moveArrayItem, g as updateArrayItemProperty, h as removeArrayItems, i as replaceIds, k as parseValue, l as extract, o as update, p as moveArrayItems, r as fillIds, s as getChildrenSignatures, t as validateBindingValue, u as appendArrayItem, v as findEditableChildren, x as parseBinding, y as getCurrentValue } from "./ast-CIe-mhh_.js";
5
- import { n as useCompiledModule, r as Frame, t as useDynamicTailwind } from "./use-dynamic-tailwind-DqqQu2de.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
6
  import { t as Core } from "./core-D3q5rpuz.js";
7
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";
@@ -176,7 +176,7 @@ const Node = ({ data, onChange }) => {
176
176
  },
177
177
  onNodeChange: onChange
178
178
  })]
179
- }, binding.label);
179
+ }, `${binding.property}-${binding.label}`);
180
180
  })
181
181
  })
182
182
  });
@@ -441,6 +441,7 @@ const parseSource = (value) => {
441
441
  id: nanoid(6),
442
442
  index: primitiveItems.length,
443
443
  elementIndex,
444
+ source: value.slice(element.start, element.end),
444
445
  value: extracted.value,
445
446
  type: extracted.type
446
447
  });
@@ -465,6 +466,7 @@ const parseSource = (value) => {
465
466
  id: nanoid(6),
466
467
  index: objectItems.length,
467
468
  elementIndex,
469
+ source: value.slice(element.start, element.end),
468
470
  editableProperties,
469
471
  jsxBindings,
470
472
  jsxFallbacks
@@ -476,6 +478,38 @@ const parseSource = (value) => {
476
478
  parseError: false
477
479
  };
478
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
+ };
479
513
  const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
480
514
  const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value), [value]);
481
515
  useEffect(() => {
@@ -483,12 +517,24 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
483
517
  }, [parseError]);
484
518
  const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
485
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
+ ]);
486
528
  const selection = useMultiSelect(isPrimitive ? primitiveItems.length : objectItems.length);
487
- const commit = (next) => {
529
+ const commit = (next, nextIds) => {
488
530
  if (next === null) {
489
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." });
490
532
  return false;
491
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));
492
538
  onChange?.(next);
493
539
  return true;
494
540
  };
@@ -501,7 +547,7 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
501
547
  return {
502
548
  kind,
503
549
  items: isPrimitive ? primitiveItems.map((item) => ({
504
- id: item.id,
550
+ id: identity.ids[item.index] ?? item.id,
505
551
  index: item.index,
506
552
  elementIndex: item.elementIndex,
507
553
  properties: [],
@@ -515,10 +561,11 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
515
561
  onChange: (next) => commit(updateArrayItemValue(value, item.elementIndex, next))
516
562
  }
517
563
  })) : objectItems.map((item) => {
564
+ const id = identity.ids[item.index] ?? item.id;
518
565
  const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
519
566
  const leaf = resolveLeaf(render, key);
520
567
  return {
521
- id: `item-${item.id}-${key}`,
568
+ id: `item-${id}-${key}`,
522
569
  label: key,
523
570
  property: leaf ? leaf.property ?? leaf.type : key,
524
571
  type: leaf?.type,
@@ -578,7 +625,7 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
578
625
  }
579
626
  }))];
580
627
  return {
581
- id: item.id,
628
+ id,
582
629
  index: item.index,
583
630
  elementIndex: item.elementIndex,
584
631
  properties,
@@ -587,17 +634,29 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
587
634
  }),
588
635
  selection,
589
636
  actions: {
590
- add: () => commit(appendArrayItem(value, kind)),
637
+ add: () => {
638
+ const next = appendArrayItem(value, kind);
639
+ commit(next, next ? [...identity.ids, nanoid(6)] : void 0);
640
+ },
591
641
  move: (elementIndex, toIndex) => {
592
- const target = (isPrimitive ? primitiveItems : objectItems).find((item) => item.index === toIndex);
593
- if (!target) return;
594
- if (commit(moveArrayItem(value, elementIndex, target.elementIndex))) 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();
595
647
  },
596
648
  remove: (elementIndex) => {
597
- if (commit(removeArrayItems(value, /* @__PURE__ */ new Set([elementIndex]), kind))) 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);
598
657
  },
599
- duplicateSelected: () => commit(duplicateArrayItems(value, elementIndicesOf(selection.selected))),
600
658
  moveSelected: (direction) => {
659
+ const nextIdentity = moveSelectedIndices(identity.ids, selection.selected, direction);
601
660
  const result = moveArrayItems(value, elementIndicesOf(selection.selected), direction);
602
661
  if (!result) {
603
662
  commit(null);
@@ -605,10 +664,10 @@ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
605
664
  }
606
665
  const visible = parseArrayExpression(result.code)?.elements.flatMap((node, index) => import_lib.isExpression(node) && import_lib.isObjectExpression(node) === !isPrimitive ? [index] : []) ?? [];
607
666
  selection.replace(new Set(visible.flatMap((position, index) => result.indices.has(position) ? [index] : [])));
608
- commit(result.code);
667
+ commit(result.code, nextIdentity.items);
609
668
  },
610
669
  removeSelected: () => {
611
- if (commit(removeArrayItems(value, elementIndicesOf(selection.selected), kind))) selection.clear();
670
+ if (commit(removeArrayItems(value, elementIndicesOf(selection.selected), kind), removeIds(identity.ids, selection.selected))) selection.clear();
612
671
  }
613
672
  },
614
673
  parseError
@@ -666,18 +725,50 @@ const NestedGroup = ({ group, onNodeChange }) => {
666
725
  binding,
667
726
  onNodeChange
668
727
  })]
669
- }, binding.label))
728
+ }, `${binding.property}-${binding.label}`))
670
729
  })
671
730
  })]
672
731
  }, `${group.property}-${element.id}`))]
673
732
  });
674
733
  };
675
734
  const Items = ({ value, render, onChange, onChildChange }) => {
676
- const { kind, items, selection, actions } = useItemsEditor(value, {
735
+ const { kind, items, selection, actions, parseError } = useItemsEditor(value, {
677
736
  render,
678
737
  onChange,
679
738
  onNodeChange: onChildChange
680
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
+ });
681
772
  const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
682
773
  count: selection.selected.size,
683
774
  onDuplicate: actions.duplicateSelected,
@@ -713,24 +804,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
713
804
  if (kind === "primitive") return /* @__PURE__ */ jsxs("div", {
714
805
  className: "space-y-4",
715
806
  children: [
716
- /* @__PURE__ */ jsxs("div", {
717
- className: "flex items-center justify-between",
718
- children: [/* @__PURE__ */ jsxs("div", {
719
- className: "text-sm font-semibold",
720
- children: [
721
- "Items (",
722
- items.length,
723
- ")"
724
- ]
725
- }), /* @__PURE__ */ jsx(Button, {
726
- size: "small",
727
- icon: /* @__PURE__ */ jsx(Plus, {}),
728
- variant: "solid",
729
- color: "green",
730
- onClick: actions.add,
731
- children: "Add Item"
732
- })]
733
- }),
807
+ header,
734
808
  bulkBar,
735
809
  items.map((item) => /* @__PURE__ */ jsxs("div", {
736
810
  className: "space-y-2 rounded border border-gray-100 bg-gray-50 p-2",
@@ -754,25 +828,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
754
828
  return /* @__PURE__ */ jsxs("div", {
755
829
  className: "space-y-4",
756
830
  children: [
757
- /* @__PURE__ */ jsxs("div", {
758
- className: "flex items-center justify-between",
759
- children: [/* @__PURE__ */ jsxs("div", {
760
- className: "text-sm font-semibold",
761
- children: [
762
- "Items (",
763
- items.length,
764
- ")"
765
- ]
766
- }), /* @__PURE__ */ jsx(Button, {
767
- size: "small",
768
- icon: /* @__PURE__ */ jsx(Plus, {}),
769
- variant: "solid",
770
- color: "green",
771
- disabled: items.length === 0,
772
- onClick: actions.add,
773
- children: "Add Item"
774
- })]
775
- }),
831
+ header,
776
832
  bulkBar,
777
833
  items.map((item) => /* @__PURE__ */ jsxs("div", {
778
834
  className: "space-y-3 rounded border bg-gray-50 p-3",
@@ -812,7 +868,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
812
868
  String(binding.meta?.valueType),
813
869
  ")"
814
870
  ]
815
- })] }, `${item.id}-${binding.label}`))
871
+ })] }, `${item.id}-${binding.property}-${binding.label}`))
816
872
  }),
817
873
  /* @__PURE__ */ jsx("div", {
818
874
  className: "space-y-3 border-t pt-2",
@@ -899,6 +955,9 @@ const Field = ({ binding, onNodeChange }) => {
899
955
  setPrevRawValue(rawValue);
900
956
  setText(rawValue);
901
957
  }
958
+ const commitIfChanged = (next) => {
959
+ if (next !== (typeof next === "string" ? rawValue : value)) onChange(next);
960
+ };
902
961
  if (binding.property === "items" || binding.property === "data" || binding.type === "array") return /* @__PURE__ */ jsx(Items, {
903
962
  value: rawValue,
904
963
  render: binding.render,
@@ -908,9 +967,7 @@ const Field = ({ binding, onNodeChange }) => {
908
967
  if (binding.type === "richtext") return /* @__PURE__ */ jsx(RichTextEditor, {
909
968
  value: rawValue,
910
969
  toolbar: RICHTEXT_TOOLBAR,
911
- onChange: (next) => {
912
- if (next !== rawValue) onChange(next);
913
- }
970
+ onChange: commitIfChanged
914
971
  });
915
972
  if (binding.property === BINDING_PROP.INNER_HTML || binding.type === "jsx") {
916
973
  const isHTML = binding.property === BINDING_PROP.INNER_HTML;
@@ -919,9 +976,7 @@ const Field = ({ binding, onNodeChange }) => {
919
976
  height: "150px",
920
977
  fragment: !isHTML,
921
978
  raw: isHTML,
922
- onSave: (next) => {
923
- if (next !== rawValue) onChange(next);
924
- }
979
+ onSave: commitIfChanged
925
980
  });
926
981
  }
927
982
  if (binding.property === "children" && Array.isArray(value)) return /* @__PURE__ */ jsx(Children$1, {
@@ -969,13 +1024,11 @@ const Field = ({ binding, onNodeChange }) => {
969
1024
  if (binding.options && Array.isArray(binding.options)) return /* @__PURE__ */ jsx(Select, {
970
1025
  value: stringValue,
971
1026
  options: binding.options,
972
- onChange: (next) => {
973
- if (next !== value) onChange(next);
974
- }
1027
+ onChange: commitIfChanged
975
1028
  });
976
1029
  if (binding.type === "color" || isColorProperty(binding.property)) return /* @__PURE__ */ jsx(ColorPickerField, {
977
1030
  value: normalizeToHex(stringValue),
978
- onChange
1031
+ onChange: commitIfChanged
979
1032
  });
980
1033
  if (binding.type === "date") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DatePicker, {
981
1034
  value: parseDateValue(stringValue),
@@ -987,7 +1040,7 @@ const Field = ({ binding, onNodeChange }) => {
987
1040
  return;
988
1041
  }
989
1042
  setValidationError(null);
990
- if (next !== value) onChange(next);
1043
+ commitIfChanged(next);
991
1044
  }
992
1045
  }), validationError && /* @__PURE__ */ jsx("p", {
993
1046
  className: "mt-1 text-xs text-red-500",
@@ -1006,7 +1059,7 @@ const Field = ({ binding, onNodeChange }) => {
1006
1059
  return;
1007
1060
  }
1008
1061
  setValidationError(null);
1009
- if (next !== value) onChange(next);
1062
+ commitIfChanged(next);
1010
1063
  }
1011
1064
  }), validationError && /* @__PURE__ */ jsx("p", {
1012
1065
  className: "mt-1 text-xs text-red-500",
@@ -1019,9 +1072,7 @@ const Field = ({ binding, onNodeChange }) => {
1019
1072
  children: [/* @__PURE__ */ jsx(Select, {
1020
1073
  value: stringValue,
1021
1074
  options: ICON_OPTIONS,
1022
- onChange: (next) => {
1023
- if (next !== value) onChange(next);
1024
- }
1075
+ onChange: commitIfChanged
1025
1076
  }), SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, {
1026
1077
  size: 18,
1027
1078
  className: "shrink-0"
@@ -1049,7 +1100,7 @@ const Field = ({ binding, onNodeChange }) => {
1049
1100
  return;
1050
1101
  }
1051
1102
  setValidationError(null);
1052
- if (next !== value) onChange(next);
1103
+ commitIfChanged(next);
1053
1104
  }
1054
1105
  }),
1055
1106
  /* @__PURE__ */ jsx(Upload, {
@@ -1059,7 +1110,7 @@ const Field = ({ binding, onNodeChange }) => {
1059
1110
  defaultValue: defaultUploadValue,
1060
1111
  onChange: (files) => {
1061
1112
  const next = files[0]?.url ?? "";
1062
- if (next !== value) onChange(next);
1113
+ commitIfChanged(next);
1063
1114
  }
1064
1115
  }),
1065
1116
  validationError && /* @__PURE__ */ jsx("p", {
@@ -1083,7 +1134,7 @@ const Field = ({ binding, onNodeChange }) => {
1083
1134
  return;
1084
1135
  }
1085
1136
  setValidationError(null);
1086
- if (next !== value) onChange(next);
1137
+ commitIfChanged(next);
1087
1138
  }
1088
1139
  }), validationError && /* @__PURE__ */ jsx("p", {
1089
1140
  className: "mt-1 text-xs text-red-500",
@@ -1102,7 +1153,7 @@ const Field = ({ binding, onNodeChange }) => {
1102
1153
  return;
1103
1154
  }
1104
1155
  setValidationError(null);
1105
- if (next !== rawValue) onChange(next);
1156
+ commitIfChanged(next);
1106
1157
  }
1107
1158
  }), validationError && /* @__PURE__ */ jsx("p", {
1108
1159
  className: "mt-1 text-xs text-red-500",
@@ -1131,7 +1182,7 @@ const FieldGroup = ({ bindings, onNodeChange }) => /* @__PURE__ */ jsx("div", {
1131
1182
  binding,
1132
1183
  onNodeChange
1133
1184
  })]
1134
- }, binding.label))
1185
+ }, `${binding.property}-${binding.label}`))
1135
1186
  })
1136
1187
  });
1137
1188
  //#endregion
@@ -1525,7 +1576,7 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1525
1576
  const isMobile = breakpoint.current === "xs" || breakpoint.current === "sm";
1526
1577
  const { code } = usePreview();
1527
1578
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
1528
- const value = _value || code || DEFAULT_TEMPLATE;
1579
+ const value = _value === void 0 ? code : _value;
1529
1580
  const { sections, previews, selectedId, selectedItem, selectedIndex, select, clearSelection, add: addItem, remove: onDelete, copy: onCopy, move: moveSection, reorder, patch } = useSectionDocument(value, _onChange);
1530
1581
  const onDragStart = (_) => {};
1531
1582
  const onDragOver = (_e) => {};
@@ -1712,4 +1763,4 @@ Dnd.Layout = Layout;
1712
1763
  //#endregion
1713
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 };
1714
1765
 
1715
- //# sourceMappingURL=dnd-BnXoytb5.js.map
1766
+ //# sourceMappingURL=dnd-DTT37EbA.js.map