@jbpark/live-editor 4.3.1 → 4.5.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 (69) hide show
  1. package/README.md +2 -2
  2. package/dist/{boundary-B17cepvm.js → boundary-DnjGgsK6.js} +8 -5
  3. package/dist/boundary-DnjGgsK6.js.map +1 -0
  4. package/dist/{compile-0Dvp8zp5.js → compile-DDfk5P25.js} +3 -3
  5. package/dist/{compile-0Dvp8zp5.js.map → compile-DDfk5P25.js.map} +1 -1
  6. package/dist/{constants-CKvdqZtO.js → constants-h1JAgOeW.js} +4 -2
  7. package/dist/constants-h1JAgOeW.js.map +1 -0
  8. package/dist/{context-93X3gyfJ.js → context-DsEXJl4h.js} +11 -5
  9. package/dist/context-DsEXJl4h.js.map +1 -0
  10. package/dist/{core-B3ruccKb.js → core-CRsAGpei.js} +2 -2
  11. package/dist/{core-B3ruccKb.js.map → core-CRsAGpei.js.map} +1 -1
  12. package/dist/{detect-typescript-5MV4mCM7.js → detect-typescript-CCj9ALXc.js} +2 -2
  13. package/dist/{detect-typescript-5MV4mCM7.js.map → detect-typescript-CCj9ALXc.js.map} +1 -1
  14. package/dist/dnd/index.d.ts +2 -2
  15. package/dist/dnd/index.js +1 -1
  16. package/dist/{dnd-x8Mvj--6.js → dnd-DGNp20Nk.js} +496 -307
  17. package/dist/dnd-DGNp20Nk.js.map +1 -0
  18. package/dist/{document-C-hGKMkk.js → document-DGHTfrda.js} +7739 -7736
  19. package/dist/document-DGHTfrda.js.map +1 -0
  20. package/dist/editor/index.js +2 -2
  21. package/dist/{editor-DvMruljw.js → editor-DQ8Q--qR.js} +2 -2
  22. package/dist/{editor-DvMruljw.js.map → editor-DQ8Q--qR.js.map} +1 -1
  23. package/dist/error/index.d.ts +1 -1
  24. package/dist/error/index.js +2 -2
  25. package/dist/{error-DWhxQEom.js → error-DwqwjJ2C.js} +13 -6
  26. package/dist/error-DwqwjJ2C.js.map +1 -0
  27. package/dist/index-CHknA6k1.d.ts +221 -0
  28. package/dist/{index-CtlUYnWq.d.ts → index-P9Aifaui.d.ts} +14 -4
  29. package/dist/index-WQgFy510.d.ts +11 -0
  30. package/dist/{index-C4SGQ3Ci.d.ts → index-vtyggWy0.d.ts} +4 -1
  31. package/dist/index.d.ts +18 -17
  32. package/dist/index.js +12 -8
  33. package/dist/index.js.map +1 -1
  34. package/dist/{ast-CehbCXwh.js → items-DdrwGxJw.js} +467 -426
  35. package/dist/items-DdrwGxJw.js.map +1 -0
  36. package/dist/messages-Cp3g1mn1.d.ts +129 -0
  37. package/dist/messages-CzUPVnzH.js +132 -0
  38. package/dist/messages-CzUPVnzH.js.map +1 -0
  39. package/dist/preview/index.js +2 -2
  40. package/dist/{preview-BYck9mVK.js → preview-BKFgq_5u.js} +6 -4
  41. package/dist/preview-BKFgq_5u.js.map +1 -0
  42. package/dist/provider/index.d.ts +3 -8
  43. package/dist/provider/index.js +3 -2
  44. package/dist/{sections-Ce63wbtG.js → sections-BRH3KHYc.js} +9 -4
  45. package/dist/sections-BRH3KHYc.js.map +1 -0
  46. package/dist/style.css +505 -21
  47. package/dist/{use-dynamic-tailwind-QKcGvUJs.js → use-dynamic-tailwind-T9r9ExbR.js} +21 -4
  48. package/dist/use-dynamic-tailwind-T9r9ExbR.js.map +1 -0
  49. package/dist/utils/ast/index.d.ts +3 -2
  50. package/dist/utils/ast/index.js +66 -3
  51. package/dist/utils/ast/index.js.map +1 -0
  52. package/dist/utils/index.d.ts +30 -13
  53. package/dist/utils/index.js +26 -6
  54. package/dist/utils/index.js.map +1 -0
  55. package/dist/validate-DB59Z_Nx.d.ts +100 -0
  56. package/dist/validate-iLo8IONY.js +64 -0
  57. package/dist/validate-iLo8IONY.js.map +1 -0
  58. package/package.json +3 -3
  59. package/dist/ast-CehbCXwh.js.map +0 -1
  60. package/dist/boundary-B17cepvm.js.map +0 -1
  61. package/dist/constants-CKvdqZtO.js.map +0 -1
  62. package/dist/context-93X3gyfJ.js.map +0 -1
  63. package/dist/dnd-x8Mvj--6.js.map +0 -1
  64. package/dist/document-C-hGKMkk.js.map +0 -1
  65. package/dist/error-DWhxQEom.js.map +0 -1
  66. package/dist/index-w3kLcCXl.d.ts +0 -255
  67. package/dist/preview-BYck9mVK.js.map +0 -1
  68. package/dist/sections-Ce63wbtG.js.map +0 -1
  69. package/dist/use-dynamic-tailwind-QKcGvUJs.js.map +0 -1
@@ -1,13 +1,15 @@
1
- import { S as require_lib, _ as nanoid, c as inspectDocument, h as replaceIds, m as fillIdsFrom, n as createSectionPreviewCache, r as fillSectionIds, w as __toESM } from "./document-C-hGKMkk.js";
2
- import { c as RESERVED_BINDING_PROPERTIES, o as DRAGGABLE_ITEMS, r as DATA_ATTR, t as BINDING_PROP } from "./constants-CKvdqZtO.js";
1
+ import { S as require_lib, c as inspectDocument, h as replaceIds, m as fillIdsFrom, n as createSectionPreviewCache, r as fillSectionIds, w as __toESM, x as nanoid } from "./document-DGHTfrda.js";
2
+ import { c as RESERVED_BINDING_PROPERTIES, o as DRAGGABLE_ITEMS, r as DATA_ATTR, t as BINDING_PROP } from "./constants-h1JAgOeW.js";
3
+ import { n as validateBindingValue } from "./validate-iLo8IONY.js";
4
+ import { i as useLiveMessages } from "./messages-CzUPVnzH.js";
3
5
  import { i as usePreview } from "./states-Ci1AvoQ9.js";
4
- import { A as parseValue, D as extractObjectProperties, E as extractNodeValue, _ as moveSelectedIndices, a as getChildrenSignatures, b as getStructuredValue, c as appendArrayItem, d as moveArrayItems, g as canStructurallyEditArray, h as updateArrayItemValue, i as updateAll, k as parseArrayExpression, l as duplicateArrayItems, m as updateArrayItemProperty, p as removeArrayItems, s as extract, t as validateBindingValue, u as moveArrayItem, v as findEditableChildren, w as canLosslesslyEvaluateSource, x as parseBinding, y as getCurrentValue } from "./ast-CehbCXwh.js";
6
+ import { A as extractNodeValue, N as parseArrayExpression, O as canLosslesslyEvaluateSource, P as parseValue, T as readNodeBindings, _ as findEditableChildren, c as updateArrayItemValue, f as updateAll, g as extract, i as moveArrayItems, j as extractObjectProperties, l as canStructurallyEditArray, m as moveSelectedIndices, n as duplicateArrayItems, o as removeArrayItems, p as getChildrenSignatures, r as moveArrayItem, s as updateArrayItemProperty, t as appendArrayItem, v as getCurrentValue, x as getStructuredValue } from "./items-DdrwGxJw.js";
5
7
  import { t as cn } from "./cn-CAnad2vL.js";
6
- import { o as preloadScripts } from "./compile-0Dvp8zp5.js";
7
- import { a as Frame, i as useStableModules, n as useCompiledModule, t as useDynamicTailwind } from "./use-dynamic-tailwind-QKcGvUJs.js";
8
- import { t as Core } from "./core-B3ruccKb.js";
9
- import { a as replaceSections, n as extractSections, r as generateSection } from "./sections-Ce63wbtG.js";
10
- import { n as Error$1, t as ErrorBoundary } from "./boundary-B17cepvm.js";
8
+ import { o as preloadScripts } from "./compile-DDfk5P25.js";
9
+ import { a as Frame, i as useStableModules, n as useCompiledModule, t as useDynamicTailwind } from "./use-dynamic-tailwind-T9r9ExbR.js";
10
+ import { t as Core } from "./core-CRsAGpei.js";
11
+ import { i as generateSection, o as replaceSections, r as extractSections } from "./sections-BRH3KHYc.js";
12
+ import { n as Error$1, t as ErrorBoundary } from "./boundary-DnjGgsK6.js";
11
13
  import { Children, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
12
14
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
15
  import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, PointerSensor, TouchSensor, closestCenter, useDndContext, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
@@ -26,6 +28,7 @@ const Droppable = ({ children, className }) => {
26
28
  const isNewItemDragging = active?.data.current?.type === "new-item";
27
29
  const shouldHighlight = isOver && isNewItemDragging;
28
30
  const shouldHighlightBottom = isBottomOver && isNewItemDragging;
31
+ const messages = useLiveMessages();
29
32
  return /* @__PURE__ */ jsxs("div", {
30
33
  ref: setNodeRef,
31
34
  className: cn("min-h-full", "border-2 border-dashed p-1", shouldHighlight ? "border-blue-300 bg-blue-50" : "border-gray-200", className),
@@ -34,7 +37,7 @@ const Droppable = ({ children, className }) => {
34
37
  className: cn("mt-2 min-h-24", "rounded-lg border-2 border-dashed", "transition-all duration-200", "flex items-center justify-center", shouldHighlightBottom ? "border-blue-400 bg-blue-100" : "border-gray-300 bg-gray-50"),
35
38
  children: /* @__PURE__ */ jsx(Typography.Text, {
36
39
  className: "text-sm text-gray-500",
37
- children: shouldHighlightBottom ? "Drop here" : "Drag here to add at bottom"
40
+ children: shouldHighlightBottom ? messages.canvas.dropHere : messages.canvas.dropAtBottom
38
41
  })
39
42
  })]
40
43
  });
@@ -73,6 +76,21 @@ const pickElement = (section, overlay, x, y) => {
73
76
  while (element && element !== section && !hasId(element)) element = element.parentElement ?? element.parentNode?.host ?? null;
74
77
  return element === section ? null : element;
75
78
  };
79
+ const findElementById = (section, id) => {
80
+ const selector = `[${DATA_ATTR.ID}="${CSS.escape(id)}"]`;
81
+ const roots = [section];
82
+ for (const root of roots) {
83
+ const found = root.querySelector(selector);
84
+ if (found) return found;
85
+ root.querySelectorAll("iframe").forEach((frame) => {
86
+ if (frame.contentDocument) roots.push(frame.contentDocument);
87
+ });
88
+ root.querySelectorAll("*").forEach((element) => {
89
+ if (element.shadowRoot) roots.push(element.shadowRoot);
90
+ });
91
+ }
92
+ return null;
93
+ };
76
94
  const viewportRect = (element) => {
77
95
  const rect = element.getBoundingClientRect();
78
96
  let x = rect.left;
@@ -204,13 +222,16 @@ const toPanelBindingData = (node, id, binding) => ({
204
222
  value: getStructuredValue(node, binding.property, binding.type),
205
223
  rawValue: getCurrentValue(node, binding.property),
206
224
  ...!canEditBindingValue(node, binding) && { canEditValue: false },
207
- ...isAttributeAbsent(node, binding) && { present: false }
225
+ ...isAttributeAbsent(node, binding) && { present: false },
226
+ element: {
227
+ tagName: node.tagName || "element",
228
+ text: node.textContent.replace(/\s+/g, " ").trim()
229
+ }
208
230
  });
209
231
  const resolvePanelBindings = (node) => {
210
232
  const id = readAttribute(node, "data-id");
211
- const bindingAttr = readAttribute(node, "data-binding");
212
- if (!id || !bindingAttr) return null;
213
- const parsed = node.bindings ?? parseBinding(bindingAttr);
233
+ if (!id) return null;
234
+ const parsed = readNodeBindings(node);
214
235
  if (!parsed.length) return null;
215
236
  return {
216
237
  id,
@@ -230,33 +251,34 @@ const withPanelCommit = (bindings, commit) => bindings.map((binding) => ({
230
251
  //#endregion
231
252
  //#region src/components/dnd/panel/bulk-actions-bar.tsx
232
253
  const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
254
+ const messages = useLiveMessages();
233
255
  if (count === 0) return null;
234
256
  return /* @__PURE__ */ jsxs("div", {
235
257
  className: "flex items-center justify-between rounded border\n border-blue-200 bg-blue-50 p-2",
236
- children: [/* @__PURE__ */ jsxs("div", {
258
+ children: [/* @__PURE__ */ jsx("div", {
237
259
  className: "text-xs font-medium text-blue-700",
238
- children: [count, " selected"]
260
+ children: messages.selection.count(count)
239
261
  }), /* @__PURE__ */ jsxs("div", {
240
262
  className: "flex items-center space-x-1",
241
263
  children: [
242
264
  /* @__PURE__ */ jsx(Button, {
243
265
  size: "small",
244
266
  icon: /* @__PURE__ */ jsx(Copy, {}),
245
- title: "Duplicate selected",
267
+ title: messages.selection.duplicate,
246
268
  disabled,
247
269
  onClick: onDuplicate
248
270
  }),
249
271
  /* @__PURE__ */ jsx(Button, {
250
272
  size: "small",
251
273
  icon: /* @__PURE__ */ jsx(ArrowUp, {}),
252
- title: "Move selected up",
274
+ title: messages.selection.moveUp,
253
275
  disabled,
254
276
  onClick: onMoveUp
255
277
  }),
256
278
  /* @__PURE__ */ jsx(Button, {
257
279
  size: "small",
258
280
  icon: /* @__PURE__ */ jsx(ArrowDown, {}),
259
- title: "Move selected down",
281
+ title: messages.selection.moveDown,
260
282
  disabled,
261
283
  onClick: onMoveDown
262
284
  }),
@@ -264,14 +286,14 @@ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMove
264
286
  danger: true,
265
287
  size: "small",
266
288
  icon: /* @__PURE__ */ jsx(X, {}),
267
- title: "Delete selected",
289
+ title: messages.selection.delete,
268
290
  disabled,
269
291
  onClick: onDelete
270
292
  }),
271
293
  /* @__PURE__ */ jsx(Button, {
272
294
  size: "small",
273
295
  onClick: onClear,
274
- children: "Clear"
296
+ children: messages.selection.clear
275
297
  })
276
298
  ]
277
299
  })]
@@ -402,6 +424,7 @@ const useDndChildren = (items, { onChange } = {}) => {
402
424
  const Children$1 = ({ value, onChange, onNodeChange }) => {
403
425
  const items = useMemo(() => Array.isArray(value) ? value : [], [value]);
404
426
  const { selection, actions } = useDndChildren(items, { onChange });
427
+ const messages = useLiveMessages();
405
428
  const editableChildrenMap = useMemo(() => {
406
429
  const map = /* @__PURE__ */ new Map();
407
430
  items.forEach((item, index) => {
@@ -415,19 +438,15 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
415
438
  children: [
416
439
  /* @__PURE__ */ jsxs("div", {
417
440
  className: "flex items-center justify-between",
418
- children: [/* @__PURE__ */ jsxs("div", {
441
+ children: [/* @__PURE__ */ jsx("div", {
419
442
  className: "text-sm font-semibold text-green-700",
420
- children: [
421
- "Children Items (",
422
- items.length,
423
- ")"
424
- ]
443
+ children: messages.children.heading(items.length)
425
444
  }), /* @__PURE__ */ jsx(Button, {
426
445
  size: "small",
427
446
  color: "green",
428
447
  icon: /* @__PURE__ */ jsx(Plus, {}),
429
448
  onClick: actions.add,
430
- children: "Add Child"
449
+ children: messages.children.add
431
450
  })]
432
451
  }),
433
452
  /* @__PURE__ */ jsx(BulkActionsBar, {
@@ -452,15 +471,9 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
452
471
  checked: selection.isSelected(itemIndex),
453
472
  onChange: () => {}
454
473
  })
455
- }), /* @__PURE__ */ jsxs("div", {
474
+ }), /* @__PURE__ */ jsx("div", {
456
475
  className: "text-xs font-medium text-green-800",
457
- children: [
458
- "Child ",
459
- itemIndex + 1,
460
- " (",
461
- item.tagName || "fragment",
462
- ")"
463
- ]
476
+ children: messages.children.child(itemIndex + 1, item.tagName || "fragment")
464
477
  })]
465
478
  }), /* @__PURE__ */ jsxs("div", {
466
479
  className: "flex space-x-1",
@@ -468,17 +481,20 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
468
481
  /* @__PURE__ */ jsx(Button, {
469
482
  size: "small",
470
483
  icon: /* @__PURE__ */ jsx(ArrowUp, {}),
484
+ "aria-label": messages.children.moveUp,
471
485
  disabled: itemIndex === 0,
472
486
  onClick: () => actions.move(itemIndex, itemIndex - 1)
473
487
  }),
474
488
  /* @__PURE__ */ jsx(Button, {
475
489
  size: "small",
476
490
  icon: /* @__PURE__ */ jsx(ArrowDown, {}),
491
+ "aria-label": messages.children.moveDown,
477
492
  disabled: itemIndex === items.length - 1,
478
493
  onClick: () => actions.move(itemIndex, itemIndex + 1)
479
494
  }),
480
495
  /* @__PURE__ */ jsx(Button, {
481
- title: "Delete item",
496
+ "aria-label": messages.children.delete,
497
+ title: messages.children.delete,
482
498
  danger: true,
483
499
  size: "small",
484
500
  icon: /* @__PURE__ */ jsx(X, {}),
@@ -491,7 +507,7 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
491
507
  className: "space-y-2",
492
508
  children: [/* @__PURE__ */ jsx("div", {
493
509
  className: "text-xs font-medium text-green-700",
494
- children: "Editable Bindings:"
510
+ children: messages.children.editableBindings
495
511
  }), editableChildrenMap.get(itemIndex).map((editableNode, idx) => {
496
512
  const nodeId = editableNode.dataAttributes.find((a) => a.name === "data-id")?.value;
497
513
  return /* @__PURE__ */ jsxs("div", {
@@ -511,7 +527,7 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
511
527
  className: "space-y-1",
512
528
  children: [/* @__PURE__ */ jsx("div", {
513
529
  className: "text-xs font-medium text-green-700",
514
- children: "Child Nodes:"
530
+ children: messages.children.childNodes
515
531
  }), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx("div", {
516
532
  className: "ml-2 rounded border border-green-100 bg-white p-2",
517
533
  children: /* @__PURE__ */ jsx(Node, {
@@ -548,9 +564,9 @@ const getFieldKind = (binding) => {
548
564
  const isStructuralFieldKind = (kind) => kind === "items" || kind === "children";
549
565
  //#endregion
550
566
  //#region src/components/dnd/panel/sortable-items.tsx
551
- const label = (items, id) => {
567
+ const label = (messages, items, id) => {
552
568
  const index = items.findIndex((item) => item.id === id);
553
- return index >= 0 ? `Item ${index + 1}` : "Item";
569
+ return index >= 0 ? messages.items.item(index + 1) : String(id);
554
570
  };
555
571
  const SortableItems = ({ items, disabled, onMove, children }) => {
556
572
  const sensors = useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 4 } }), useSensor(TouchSensor, { activationConstraint: {
@@ -562,15 +578,18 @@ const SortableItems = ({ items, disabled, onMove, children }) => {
562
578
  const to = items.find((item) => item.id === over?.id);
563
579
  if (from && to && from !== to) onMove(from, to.index);
564
580
  };
581
+ const messages = useLiveMessages();
582
+ const { announcements: say } = messages;
583
+ const name = (id) => label(messages, items, id);
565
584
  return /* @__PURE__ */ jsx(DndContext, {
566
585
  sensors,
567
586
  collisionDetection: closestCenter,
568
587
  modifiers: [restrictToVerticalAxis, restrictToParentElement],
569
588
  accessibility: { announcements: {
570
- onDragStart: ({ active }) => `Picked up ${label(items, active.id)}.`,
571
- onDragOver: ({ active, over }) => over ? `${label(items, active.id)} is over ${label(items, over.id)}.` : void 0,
572
- onDragEnd: ({ active, over }) => over ? `${label(items, active.id)} was moved to the place of ${label(items, over.id)}.` : `${label(items, active.id)} was dropped.`,
573
- onDragCancel: ({ active }) => `Moving ${label(items, active.id)} was cancelled.`
589
+ onDragStart: ({ active }) => say.pickedUp(name(active.id)),
590
+ onDragOver: ({ active, over }) => over ? say.itemOver(name(active.id), name(over.id)) : void 0,
591
+ onDragEnd: ({ active, over }) => over ? say.itemMoved(name(active.id), name(over.id)) : say.dropped(name(active.id)),
592
+ onDragCancel: ({ active }) => say.cancelled(name(active.id))
574
593
  } },
575
594
  onDragEnd,
576
595
  children: /* @__PURE__ */ jsx(SortableContext, {
@@ -585,6 +604,7 @@ const SortableItems = ({ items, disabled, onMove, children }) => {
585
604
  });
586
605
  };
587
606
  const ItemCard = ({ id, title, disabled, expanded, onToggle, checkbox, controls, className, children }) => {
607
+ const messages = useLiveMessages();
588
608
  const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
589
609
  id,
590
610
  disabled
@@ -606,7 +626,7 @@ const ItemCard = ({ id, title, disabled, expanded, onToggle, checkbox, controls,
606
626
  ref: setActivatorNodeRef,
607
627
  ...attributes,
608
628
  ...listeners,
609
- "aria-label": `Reorder ${title}`,
629
+ "aria-label": messages.items.reorder(title),
610
630
  disabled,
611
631
  className: "cursor-grab rounded p-0.5 text-gray-400\n hover:text-gray-700 disabled:cursor-not-allowed\n disabled:opacity-40",
612
632
  children: /* @__PURE__ */ jsx(GripVertical, { size: 14 })
@@ -638,8 +658,8 @@ const createPendingCommit = () => {
638
658
  }
639
659
  };
640
660
  };
641
- const bindingsInJSX = (source) => {
642
- const nodes = extract(source);
661
+ const bindingsInJSX = (source, bindingOptions) => {
662
+ const nodes = extract(source, bindingOptions);
643
663
  const container = nodes.find((n) => n.bindings?.some((b) => b.property === "children"));
644
664
  if (container) return [container];
645
665
  const bindings = [];
@@ -649,7 +669,7 @@ const bindingsInJSX = (source) => {
649
669
  });
650
670
  return bindings;
651
671
  };
652
- const parseSource = (value) => {
672
+ const parseSource = (value, bindingOptions) => {
653
673
  const ast = parseArrayExpression(value);
654
674
  if (!ast) return {
655
675
  objectItems: [],
@@ -678,7 +698,7 @@ const parseSource = (value) => {
678
698
  if (!import_lib.isObjectProperty(prop) || !import_lib.isIdentifier(prop.key) || !(import_lib.isJSXElement(prop.value) || import_lib.isJSXFragment(prop.value))) return;
679
699
  const propertyName = prop.key.name;
680
700
  try {
681
- const found = bindingsInJSX(value.slice(prop.value.start, prop.value.end));
701
+ const found = bindingsInJSX(value.slice(prop.value.start, prop.value.end), bindingOptions);
682
702
  if (found.length > 0) jsxBindings[propertyName] = found;
683
703
  else jsxFallbacks[propertyName] = value.slice(prop.value.start, prop.value.end);
684
704
  } catch (error) {
@@ -736,19 +756,22 @@ const removeIds = (ids, indices) => {
736
756
  return ids.filter((_, index) => !indices.has(index));
737
757
  };
738
758
  const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
739
- const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value), [value]);
759
+ const { reportError, bindingOptions } = useDndEditOptions();
760
+ const messages = useLiveMessages();
761
+ const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value, bindingOptions), [value, bindingOptions]);
740
762
  const canEditStructure = useMemo(() => canStructurallyEditArray(value), [value]);
741
- const { reportError } = useDndEditOptions();
742
763
  const reportErrorRef = useRef(reportError);
764
+ const messagesRef = useRef(messages);
743
765
  useEffect(() => {
744
766
  reportErrorRef.current = reportError;
767
+ messagesRef.current = messages;
745
768
  });
746
769
  useEffect(() => {
747
770
  if (parseError) reportErrorRef.current({
748
771
  type: "parse",
749
772
  target: "items",
750
- title: "Failed to parse items",
751
- description: "Check the console for details."
773
+ title: messagesRef.current.editErrors.itemsParseFailed,
774
+ description: messagesRef.current.editErrors.checkConsole
752
775
  });
753
776
  }, [parseError]);
754
777
  const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
@@ -793,8 +816,8 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
793
816
  if (next === null) {
794
817
  reportError({
795
818
  type: "items",
796
- title: "Failed to update this item",
797
- description: "The source was preserved. Structural edits require a dense array without spreads; use the code editor for unsupported syntax."
819
+ title: messages.editErrors.itemUpdateFailed,
820
+ description: messages.editErrors.itemUpdateFailedDetail
798
821
  });
799
822
  return false;
800
823
  }
@@ -1018,6 +1041,7 @@ const NestedGroup = ({ group, onNodeChange }) => {
1018
1041
  });
1019
1042
  };
1020
1043
  const Items = ({ value, render, onChange, onChildChange }) => {
1044
+ const messages = useLiveMessages();
1021
1045
  const { kind, items, selection, expansion, actions, canEditStructure, parseError } = useDndItems(value, {
1022
1046
  render,
1023
1047
  onChange,
@@ -1025,34 +1049,30 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1025
1049
  });
1026
1050
  const header = /* @__PURE__ */ jsxs("div", {
1027
1051
  className: "flex items-center justify-between",
1028
- children: [/* @__PURE__ */ jsxs("div", {
1052
+ children: [/* @__PURE__ */ jsx("div", {
1029
1053
  className: "text-sm font-semibold",
1030
- children: [
1031
- "Items (",
1032
- items.length,
1033
- ")"
1034
- ]
1054
+ children: messages.items.heading(items.length)
1035
1055
  }), items.length > 0 && canEditStructure && /* @__PURE__ */ jsx(Button, {
1036
1056
  size: "small",
1037
1057
  icon: /* @__PURE__ */ jsx(Plus, {}),
1038
1058
  variant: "solid",
1039
1059
  color: "green",
1040
1060
  onClick: actions.add,
1041
- children: "Add Item"
1061
+ children: messages.items.add
1042
1062
  })]
1043
1063
  });
1044
1064
  if (parseError) return /* @__PURE__ */ jsxs("div", {
1045
1065
  className: "space-y-4",
1046
1066
  children: [header, /* @__PURE__ */ jsx("div", {
1047
1067
  className: "rounded border border-dashed border-red-200 p-3 text-xs\n text-red-600",
1048
- children: "This value could not be read as a list. Edit it in the code editor."
1068
+ children: messages.items.unreadable
1049
1069
  })]
1050
1070
  });
1051
1071
  if (items.length === 0) return /* @__PURE__ */ jsxs("div", {
1052
1072
  className: "space-y-4",
1053
1073
  children: [header, /* @__PURE__ */ jsx("div", {
1054
1074
  className: "rounded border border-dashed border-gray-200 p-3 text-xs\n text-gray-500",
1055
- 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."
1075
+ children: messages.items.empty
1056
1076
  })]
1057
1077
  });
1058
1078
  const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
@@ -1070,12 +1090,14 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1070
1090
  /* @__PURE__ */ jsx(Button, {
1071
1091
  size: "small",
1072
1092
  icon: /* @__PURE__ */ jsx(ArrowUp, {}),
1093
+ "aria-label": messages.items.moveUp,
1073
1094
  disabled: !canEditStructure || item.index === 0,
1074
1095
  onClick: () => actions.move(item.elementIndex, item.index - 1)
1075
1096
  }),
1076
1097
  /* @__PURE__ */ jsx(Button, {
1077
1098
  size: "small",
1078
1099
  icon: /* @__PURE__ */ jsx(ArrowDown, {}),
1100
+ "aria-label": messages.items.moveDown,
1079
1101
  disabled: !canEditStructure || item.index === items.length - 1,
1080
1102
  onClick: () => actions.move(item.elementIndex, item.index + 1)
1081
1103
  }),
@@ -1083,6 +1105,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1083
1105
  danger: true,
1084
1106
  size: "small",
1085
1107
  icon: /* @__PURE__ */ jsx(X, {}),
1108
+ "aria-label": messages.items.delete,
1086
1109
  disabled: !canEditStructure || items.length <= 1,
1087
1110
  onClick: () => actions.remove(item.elementIndex)
1088
1111
  })
@@ -1121,9 +1144,16 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1121
1144
  children: item.nested.length ? item.nested.map((group) => /* @__PURE__ */ jsx(NestedGroup, {
1122
1145
  group,
1123
1146
  onNodeChange: onChildChange
1124
- }, group.property)) : /* @__PURE__ */ jsx("div", {
1147
+ }, group.property)) : /* @__PURE__ */ jsxs("div", {
1125
1148
  className: "text-xs text-gray-500",
1126
- children: "✓ No JSX bindings found"
1149
+ children: [
1150
+ /* @__PURE__ */ jsx("span", {
1151
+ "aria-hidden": "true",
1152
+ children: "✓"
1153
+ }),
1154
+ " ",
1155
+ messages.items.noNestedBindings
1156
+ ]
1127
1157
  })
1128
1158
  })] });
1129
1159
  const allExpanded = expansion.expandedIds.length === items.length;
@@ -1137,13 +1167,13 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1137
1167
  size: "small",
1138
1168
  variant: "text",
1139
1169
  onClick: () => expansion.setExpanded(allExpanded ? [] : items.map((item) => item.id)),
1140
- children: allExpanded ? "Collapse all" : "Expand all"
1170
+ children: allExpanded ? messages.items.collapseAll : messages.items.expandAll
1141
1171
  })
1142
1172
  }),
1143
1173
  bulkBar,
1144
1174
  !canEditStructure && /* @__PURE__ */ jsx("div", {
1145
1175
  className: "rounded border border-dashed border-amber-200 p-3 text-xs\n text-amber-700",
1146
- 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."
1176
+ children: messages.items.structureLocked
1147
1177
  }),
1148
1178
  /* @__PURE__ */ jsx(SortableItems, {
1149
1179
  items,
@@ -1151,7 +1181,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
1151
1181
  onMove: (item, toIndex) => actions.move(item.elementIndex, toIndex),
1152
1182
  children: (item) => /* @__PURE__ */ jsx(ItemCard, {
1153
1183
  id: item.id,
1154
- title: `Item ${item.index + 1}`,
1184
+ title: messages.items.item(item.index + 1),
1155
1185
  disabled: !canEditStructure,
1156
1186
  expanded: expansion.isExpanded(item.id),
1157
1187
  onToggle: () => expansion.toggle(item.id),
@@ -1260,6 +1290,8 @@ const FieldError = ({ message }) => message ? /* @__PURE__ */ jsx("p", {
1260
1290
  children: message
1261
1291
  }) : null;
1262
1292
  const BuiltinField = ({ binding, onNodeChange }) => {
1293
+ const messages = useLiveMessages();
1294
+ const validationOptions = { messages: messages.validation };
1263
1295
  const { id, value, rawValue, onChange } = binding;
1264
1296
  const [validationError, setValidationError] = useState(null);
1265
1297
  const [text, setText] = useState(rawValue);
@@ -1272,12 +1304,12 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1272
1304
  if (kind === "readonly") return /* @__PURE__ */ jsxs("div", {
1273
1305
  className: "space-y-1 rounded border border-dashed border-amber-200 p-3\n text-xs text-amber-700",
1274
1306
  children: [
1275
- /* @__PURE__ */ jsx("div", { children: "This expression is preserved but is not editable here." }),
1307
+ /* @__PURE__ */ jsx("div", { children: messages.panel.expressionPreserved }),
1276
1308
  /* @__PURE__ */ jsx("code", {
1277
1309
  className: "block overflow-x-auto text-amber-800",
1278
1310
  children: rawValue
1279
1311
  }),
1280
- /* @__PURE__ */ jsx("div", { children: "Use the code editor to change it." })
1312
+ /* @__PURE__ */ jsx("div", { children: messages.panel.useCodeEditor })
1281
1313
  ]
1282
1314
  });
1283
1315
  const commitIfChanged = (next) => {
@@ -1354,9 +1386,9 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1354
1386
  value: parseDateValue(stringValue),
1355
1387
  onChange: (date) => {
1356
1388
  const next = date ? formatDateValue(date) : "";
1357
- const result = validateBindingValue(binding, next);
1389
+ const result = validateBindingValue(binding, next, validationOptions);
1358
1390
  if (!result.valid) {
1359
- setValidationError(result.message ?? "Invalid value.");
1391
+ setValidationError(result.message ?? messages.panel.invalidValue);
1360
1392
  return;
1361
1393
  }
1362
1394
  setValidationError(null);
@@ -1367,12 +1399,12 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1367
1399
  type: "url",
1368
1400
  value: text,
1369
1401
  onChange: (e) => setText(e.target.value),
1370
- placeholder: "https://example.com",
1402
+ placeholder: messages.panel.urlPlaceholder,
1371
1403
  onBlur: (e) => {
1372
1404
  const next = e.target.value.trim();
1373
- const result = validateBindingValue(binding, next);
1405
+ const result = validateBindingValue(binding, next, validationOptions);
1374
1406
  if (!result.valid) {
1375
- setValidationError(result.message ?? "Invalid value.");
1407
+ setValidationError(result.message ?? messages.panel.invalidValue);
1376
1408
  return;
1377
1409
  }
1378
1410
  setValidationError(null);
@@ -1383,13 +1415,13 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1383
1415
  type: "number",
1384
1416
  value: text,
1385
1417
  onChange: (e) => setText(e.target.value),
1386
- placeholder: "Enter a numeric value",
1418
+ placeholder: messages.panel.numberPlaceholder,
1387
1419
  onBlur: (e) => {
1388
1420
  const raw = e.target.value.trim();
1389
1421
  const next = raw !== "" && !Number.isNaN(Number(raw)) ? Number(raw) : raw;
1390
- const result = validateBindingValue(binding, next);
1422
+ const result = validateBindingValue(binding, next, validationOptions);
1391
1423
  if (!result.valid) {
1392
- setValidationError(result.message ?? "Invalid value.");
1424
+ setValidationError(result.message ?? messages.panel.invalidValue);
1393
1425
  return;
1394
1426
  }
1395
1427
  setValidationError(null);
@@ -1399,13 +1431,13 @@ const BuiltinField = ({ binding, onNodeChange }) => {
1399
1431
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input.TextArea, {
1400
1432
  value: text,
1401
1433
  onChange: (e) => setText(e.target.value),
1402
- placeholder: "Enter a value",
1434
+ placeholder: messages.panel.textPlaceholder,
1403
1435
  onBlur: (e) => {
1404
1436
  const raw = e.target.value.trim();
1405
1437
  const next = binding.type ? raw : parseValue(raw);
1406
- const result = validateBindingValue(binding, next);
1438
+ const result = validateBindingValue(binding, next, validationOptions);
1407
1439
  if (!result.valid) {
1408
- setValidationError(result.message ?? "Invalid value.");
1440
+ setValidationError(result.message ?? messages.panel.invalidValue);
1409
1441
  return;
1410
1442
  }
1411
1443
  setValidationError(null);
@@ -1456,10 +1488,30 @@ const groupBindingsById = (bindings) => {
1456
1488
  }
1457
1489
  return [...groups.values()];
1458
1490
  };
1491
+ const describeElement = (binding) => {
1492
+ const { tagName = "element", text = "" } = binding.element ?? {};
1493
+ return {
1494
+ tagName,
1495
+ text,
1496
+ label: text ? `${tagName} "${text}"` : tagName
1497
+ };
1498
+ };
1499
+ const nodeIdOf = (target) => target instanceof Element && target.closest("[data-node-id]")?.getAttribute("data-node-id") || null;
1459
1500
  const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canMoveDown = false, bindings, onNodeChange, readOnly = false }) => {
1501
+ const messages = useLiveMessages();
1460
1502
  const groups = groupBindingsById(bindings);
1461
1503
  const rootRef = useRef(null);
1462
- const pickedId = (useContext(DndInspectorContext)?.picked ?? null)?.id;
1504
+ const inspector = useContext(DndInspectorContext);
1505
+ const pickedId = (inspector?.picked ?? null)?.id;
1506
+ const highlight = inspector?.highlight;
1507
+ const [hoveredId, setHoveredId] = useState(null);
1508
+ const [focusedId, setFocusedId] = useState(null);
1509
+ const targetId = hoveredId ?? focusedId;
1510
+ useEffect(() => {
1511
+ if (!highlight) return;
1512
+ highlight(targetId);
1513
+ return () => highlight(null);
1514
+ }, [highlight, targetId]);
1463
1515
  useEffect(() => {
1464
1516
  const root = rootRef.current;
1465
1517
  if (!root || !pickedId) return;
@@ -1482,10 +1534,16 @@ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canM
1482
1534
  }, [pickedId, groups.length]);
1483
1535
  if (!item) return /* @__PURE__ */ jsx(Typography.Paragraph, {
1484
1536
  className: cn("p-4 text-sm text-gray-500"),
1485
- children: "Please select a section."
1537
+ children: messages.panel.selectSection
1486
1538
  });
1487
1539
  return /* @__PURE__ */ jsxs("div", {
1488
1540
  ref: rootRef,
1541
+ onPointerOver: (e) => setHoveredId(nodeIdOf(e.target)),
1542
+ onPointerLeave: () => setHoveredId(null),
1543
+ onFocus: (e) => setFocusedId(nodeIdOf(e.target)),
1544
+ onBlur: (e) => {
1545
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocusedId(null);
1546
+ },
1489
1547
  className: cn("h-full space-y-4 p-4", "overflow-x-hidden overflow-y-auto"),
1490
1548
  children: [
1491
1549
  /* @__PURE__ */ jsxs("div", {
@@ -1500,19 +1558,19 @@ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canM
1500
1558
  icon: /* @__PURE__ */ jsx(ChevronUp, {}),
1501
1559
  disabled: !canMoveUp,
1502
1560
  onClick: onMoveUp,
1503
- "aria-label": "Move section up"
1561
+ "aria-label": messages.section.moveUp
1504
1562
  }),
1505
1563
  onMoveDown && /* @__PURE__ */ jsx(Button, {
1506
1564
  icon: /* @__PURE__ */ jsx(ChevronDown, {}),
1507
1565
  disabled: !canMoveDown,
1508
1566
  onClick: onMoveDown,
1509
- "aria-label": "Move section down"
1567
+ "aria-label": messages.section.moveDown
1510
1568
  }),
1511
1569
  onDelete && /* @__PURE__ */ jsx(Button, {
1512
1570
  danger: true,
1513
1571
  icon: /* @__PURE__ */ jsx(Trash, {}),
1514
1572
  onClick: () => onDelete(item.id),
1515
- "aria-label": "Delete section"
1573
+ "aria-label": messages.section.delete
1516
1574
  })
1517
1575
  ]
1518
1576
  })]
@@ -1520,20 +1578,34 @@ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canM
1520
1578
  readOnly && /* @__PURE__ */ jsx(Typography.Text, {
1521
1579
  role: "status",
1522
1580
  className: "block text-xs text-amber-700",
1523
- children: "The document has a syntax error. These fields are from the last version that parsed, and can't be edited until it parses again."
1581
+ children: messages.panel.readOnly
1524
1582
  }),
1525
1583
  !groups.length && /* @__PURE__ */ jsx(Typography.Text, {
1526
1584
  className: "text-xs text-gray-400",
1527
- children: "No editable elements."
1585
+ children: messages.panel.noEditableElements
1528
1586
  }),
1529
- groups.map((group) => /* @__PURE__ */ jsx("div", {
1530
- "data-node-id": group[0].id,
1531
- className: "rounded data-[picked]:ring-2 data-[picked]:ring-blue-400\n data-[picked]:ring-offset-2",
1532
- children: /* @__PURE__ */ jsx(FieldGroup, {
1533
- bindings: group,
1534
- onNodeChange
1535
- })
1536
- }, group[0].id))
1587
+ groups.map((group) => {
1588
+ const element = describeElement(group[0]);
1589
+ return /* @__PURE__ */ jsxs("div", {
1590
+ "data-node-id": group[0].id,
1591
+ role: "group",
1592
+ "aria-label": element.label,
1593
+ className: "rounded data-[picked]:ring-2 data-[picked]:ring-blue-400\n data-[picked]:ring-offset-2",
1594
+ children: [groups.length > 1 && /* @__PURE__ */ jsxs("div", {
1595
+ className: "mb-2 flex min-w-0 items-baseline gap-1.5 border-b\n border-gray-100 pb-1 text-xs",
1596
+ children: [/* @__PURE__ */ jsx("code", {
1597
+ className: "shrink-0 text-gray-500",
1598
+ children: element.tagName
1599
+ }), element.text && /* @__PURE__ */ jsx("span", {
1600
+ className: "truncate text-gray-400",
1601
+ children: element.text
1602
+ })]
1603
+ }), /* @__PURE__ */ jsx(FieldGroup, {
1604
+ bindings: group,
1605
+ onNodeChange
1606
+ })]
1607
+ }, group[0].id);
1608
+ })
1537
1609
  ]
1538
1610
  });
1539
1611
  };
@@ -1573,6 +1645,7 @@ const Palette = ({ className, ...restProps }) => /* @__PURE__ */ jsx("div", {
1573
1645
  const Canvas = ({ className, style, ...restProps }) => {
1574
1646
  const { canvas, documentError } = useDndRegions("<Live.Dnd.Canvas>");
1575
1647
  const inspector = useDndInspector();
1648
+ const messages = useLiveMessages();
1576
1649
  return /* @__PURE__ */ jsxs("div", {
1577
1650
  className: cn("relative h-full w-full overflow-y-auto", className),
1578
1651
  "data-frame-container": true,
@@ -1592,8 +1665,8 @@ const Canvas = ({ className, style, ...restProps }) => {
1592
1665
  color: "primary",
1593
1666
  variant: inspector.active ? "solid" : "outlined",
1594
1667
  "aria-pressed": inspector.active,
1595
- "aria-label": "Pick an element",
1596
- title: inspector.active ? "Click an element to edit it. Esc to cancel." : "Pick an element",
1668
+ "aria-label": messages.canvas.pickElement,
1669
+ title: inspector.active ? messages.canvas.pickElementActive : messages.canvas.pickElement,
1597
1670
  onClick: inspector.toggle
1598
1671
  })
1599
1672
  })
@@ -1607,6 +1680,7 @@ const Panel = ({ className, ...restProps }) => /* @__PURE__ */ jsx("div", {
1607
1680
  });
1608
1681
  const Layout = ({ palette, panel }) => {
1609
1682
  const { isMobile, selectedId, clearSelection, paletteOpen, setPaletteOpen } = useDndRegions("<Live.Dnd.Layout>");
1683
+ const messages = useLiveMessages();
1610
1684
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1611
1685
  isMobile ? /* @__PURE__ */ jsx(Canvas, {}) : /* @__PURE__ */ jsxs(Splitter, {
1612
1686
  withHandle: true,
@@ -1636,7 +1710,7 @@ const Layout = ({ palette, panel }) => {
1636
1710
  type: "primary",
1637
1711
  shape: "circle",
1638
1712
  icon: /* @__PURE__ */ jsx(LayoutGrid, {}),
1639
- "aria-label": "Components",
1713
+ "aria-label": messages.canvas.palette,
1640
1714
  className: "fixed right-4 bottom-4 z-20 md:hidden",
1641
1715
  onClick: () => setPaletteOpen(true)
1642
1716
  }),
@@ -1645,7 +1719,7 @@ const Layout = ({ palette, panel }) => {
1645
1719
  onClose: () => setPaletteOpen(false),
1646
1720
  direction: "bottom",
1647
1721
  size: "large",
1648
- title: "Components",
1722
+ title: messages.canvas.palette,
1649
1723
  children: palette ?? /* @__PURE__ */ jsx(PaletteItems, { subject: "<Live.Dnd.Layout>" })
1650
1724
  }),
1651
1725
  /* @__PURE__ */ jsx(Drawer, {
@@ -1653,7 +1727,7 @@ const Layout = ({ palette, panel }) => {
1653
1727
  onClose: clearSelection,
1654
1728
  direction: "bottom",
1655
1729
  size: "large",
1656
- title: "Properties",
1730
+ title: messages.canvas.properties,
1657
1731
  children: panel ?? /* @__PURE__ */ jsx(PanelFields, { subject: "<Live.Dnd.Layout>" })
1658
1732
  })
1659
1733
  ] });
@@ -1688,6 +1762,7 @@ const Renderer = ({ preview, sectionId = "", sectionName = "", sectionCode = pre
1688
1762
  name: sectionName,
1689
1763
  code: sectionCode
1690
1764
  };
1765
+ const messages = useLiveMessages();
1691
1766
  const { ref: wrapperRef, css: dynamicCSS } = useDynamicTailwind(preview, dynamicTailwind && !forceFallback);
1692
1767
  const renderProvider = (component) => {
1693
1768
  return provider ? provider(component) : component;
@@ -1698,8 +1773,8 @@ const Renderer = ({ preview, sectionId = "", sectionName = "", sectionCode = pre
1698
1773
  reason: "forced"
1699
1774
  },
1700
1775
  builtin: /* @__PURE__ */ jsx(Error$1, {
1701
- message: "This section is not rendered in this editor.",
1702
- title: "Section Unavailable"
1776
+ message: messages.errors.sectionNotRendered,
1777
+ title: messages.errors.sectionUnavailable
1703
1778
  })
1704
1779
  });
1705
1780
  if (module?.error) return /* @__PURE__ */ jsx(SectionFallback, {
@@ -1710,7 +1785,7 @@ const Renderer = ({ preview, sectionId = "", sectionName = "", sectionCode = pre
1710
1785
  },
1711
1786
  builtin: /* @__PURE__ */ jsx(Error$1, {
1712
1787
  message: module.error,
1713
- title: "Compile Error"
1788
+ title: messages.errors.compile
1714
1789
  })
1715
1790
  });
1716
1791
  const Component = module?.exports?.default;
@@ -1733,7 +1808,7 @@ const Renderer = ({ preview, sectionId = "", sectionName = "", sectionCode = pre
1733
1808
  builtin: /* @__PURE__ */ jsx(Error$1, {
1734
1809
  message,
1735
1810
  onReset: reset,
1736
- title: "Rendering Error"
1811
+ title: messages.errors.rendering
1737
1812
  })
1738
1813
  }),
1739
1814
  children: renderProvider(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Component, {
@@ -1770,11 +1845,12 @@ const NAVIGATION_KEYS = {
1770
1845
  Home: "first",
1771
1846
  End: "last"
1772
1847
  };
1773
- const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy: _onCopy, nodeRef, onNavigate, onDeleteKey, inspecting = false, onInspectMove, onInspectPick, onInspectLeave }) => {
1848
+ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy: _onCopy, onMoveUp, onMoveDown, canMoveUp = false, canMoveDown = false, nodeRef, onNavigate, onDeleteKey, inspecting = false, onInspectMove, onInspectPick, onInspectLeave }) => {
1774
1849
  const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging, isOver, active } = useSortable({
1775
1850
  id,
1776
1851
  disabled: inspecting
1777
1852
  });
1853
+ const messages = useLiveMessages();
1778
1854
  const style = {
1779
1855
  transform: CSS$1.Transform.toString(transform),
1780
1856
  transition,
@@ -1814,6 +1890,10 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
1814
1890
  e.stopPropagation();
1815
1891
  _onCopy?.(id);
1816
1892
  };
1893
+ const press = (action) => (e) => {
1894
+ e.stopPropagation();
1895
+ action?.();
1896
+ };
1817
1897
  return /* @__PURE__ */ jsxs("div", {
1818
1898
  ref: setRefs,
1819
1899
  style,
@@ -1835,14 +1915,31 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
1835
1915
  children,
1836
1916
  selected && /* @__PURE__ */ jsxs(Space, {
1837
1917
  className: cn("absolute top-1 right-1 z-60"),
1838
- children: [/* @__PURE__ */ jsx(Button, {
1839
- icon: /* @__PURE__ */ jsx(Copy, {}),
1840
- onClick: onCopy
1841
- }), /* @__PURE__ */ jsx(Button, {
1842
- danger: true,
1843
- icon: /* @__PURE__ */ jsx(Trash, {}),
1844
- onClick: onDelete
1845
- })]
1918
+ children: [
1919
+ onMoveUp && /* @__PURE__ */ jsx(Button, {
1920
+ icon: /* @__PURE__ */ jsx(ChevronUp, {}),
1921
+ disabled: !canMoveUp,
1922
+ onClick: press(onMoveUp),
1923
+ "aria-label": messages.section.moveUp
1924
+ }),
1925
+ onMoveDown && /* @__PURE__ */ jsx(Button, {
1926
+ icon: /* @__PURE__ */ jsx(ChevronDown, {}),
1927
+ disabled: !canMoveDown,
1928
+ onClick: press(onMoveDown),
1929
+ "aria-label": messages.section.moveDown
1930
+ }),
1931
+ /* @__PURE__ */ jsx(Button, {
1932
+ icon: /* @__PURE__ */ jsx(Copy, {}),
1933
+ onClick: onCopy,
1934
+ "aria-label": messages.section.duplicate
1935
+ }),
1936
+ /* @__PURE__ */ jsx(Button, {
1937
+ danger: true,
1938
+ icon: /* @__PURE__ */ jsx(Trash, {}),
1939
+ onClick: onDelete,
1940
+ "aria-label": messages.section.delete
1941
+ })
1942
+ ]
1846
1943
  })
1847
1944
  ]
1848
1945
  });
@@ -1877,6 +1974,205 @@ const Overlay = ({ sections, isForced, renderProps }) => {
1877
1974
  return null;
1878
1975
  };
1879
1976
  //#endregion
1977
+ //#region src/components/dnd/use-delete-flow.ts
1978
+ const useDeleteFlow = ({ sections, remove, onBeforeDelete }) => {
1979
+ const removeRef = useRef(remove);
1980
+ useEffect(() => {
1981
+ removeRef.current = remove;
1982
+ });
1983
+ return (id, onDeleted) => {
1984
+ const section = sections.find((candidate) => candidate.id === id);
1985
+ if (!onBeforeDelete || !section) {
1986
+ remove(id);
1987
+ onDeleted?.();
1988
+ return;
1989
+ }
1990
+ const decide = (allowed) => {
1991
+ if (allowed) {
1992
+ removeRef.current(id);
1993
+ onDeleted?.();
1994
+ }
1995
+ };
1996
+ try {
1997
+ const answer = onBeforeDelete(section);
1998
+ if (typeof answer === "boolean") {
1999
+ decide(answer);
2000
+ return;
2001
+ }
2002
+ answer.then(decide, (error) => {
2003
+ console.error("onBeforeDelete rejected; the section was kept", error);
2004
+ });
2005
+ } catch (error) {
2006
+ console.error("onBeforeDelete threw; the section was kept", error);
2007
+ }
2008
+ };
2009
+ };
2010
+ //#endregion
2011
+ //#region src/components/dnd/use-dnd-keyboard.ts
2012
+ const KEYBOARD_CODES = {
2013
+ start: ["Space"],
2014
+ cancel: ["Escape"],
2015
+ end: ["Space", "Enter"]
2016
+ };
2017
+ const createNodeRegistry = () => {
2018
+ const nodes = /* @__PURE__ */ new Map();
2019
+ const callbacks = /* @__PURE__ */ new Map();
2020
+ return {
2021
+ get: (id) => nodes.get(id),
2022
+ register: (id) => {
2023
+ let callback = callbacks.get(id);
2024
+ if (!callback) {
2025
+ callback = (node) => {
2026
+ if (node) nodes.set(id, node);
2027
+ else {
2028
+ nodes.delete(id);
2029
+ callbacks.delete(id);
2030
+ }
2031
+ };
2032
+ callbacks.set(id, callback);
2033
+ }
2034
+ return callback;
2035
+ }
2036
+ };
2037
+ };
2038
+ const useDndKeyboard = ({ sections, selectedId, selectedIndex, selectOnly, move, requestDelete }) => {
2039
+ const { announcements: say } = useLiveMessages();
2040
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }), useSensor(KeyboardSensor, {
2041
+ coordinateGetter: sortableKeyboardCoordinates,
2042
+ keyboardCodes: KEYBOARD_CODES
2043
+ }));
2044
+ const nameOf = (id, data) => {
2045
+ return (data?.current)?.item?.name ?? sections.find((section) => section.id === id)?.name ?? say.section;
2046
+ };
2047
+ const positionOf = (id) => sections.findIndex((section) => section.id === id) + 1;
2048
+ const announcements = {
2049
+ onDragStart: ({ active }) => say.pickedUp(nameOf(active.id, active.data)),
2050
+ onDragOver: ({ active, over }) => over && positionOf(over.id) > 0 ? say.movedTo(nameOf(active.id, active.data), positionOf(over.id), sections.length) : say.notOver(nameOf(active.id, active.data)),
2051
+ onDragEnd: ({ active, over }) => over && positionOf(over.id) > 0 ? say.droppedAt(nameOf(active.id, active.data), positionOf(over.id), sections.length) : say.dropped(nameOf(active.id, active.data)),
2052
+ onDragCancel: ({ active }) => say.cancelled(nameOf(active.id, active.data))
2053
+ };
2054
+ const screenReaderInstructions = { draggable: say.instructions };
2055
+ const [sectionNodes] = useState(createNodeRegistry);
2056
+ const focusSection = (id) => sectionNodes.get(id)?.focus();
2057
+ const onNavigate = (fromId, to) => {
2058
+ const from = sections.findIndex((section) => section.id === fromId);
2059
+ const target = sections[to === "first" ? 0 : to === "last" ? sections.length - 1 : from + (to === "next" ? 1 : -1)];
2060
+ if (from < 0 || !target || target.id === fromId) return;
2061
+ selectOnly(target.id);
2062
+ focusSection(target.id);
2063
+ };
2064
+ const onDeleteKey = (id) => {
2065
+ const index = sections.findIndex((section) => section.id === id);
2066
+ const neighbor = sections[index + 1] ?? sections[index - 1];
2067
+ requestDelete(id, () => {
2068
+ if (neighbor) focusSection(neighbor.id);
2069
+ });
2070
+ };
2071
+ const onMoveButton = (id, direction) => {
2072
+ const to = sections.findIndex((section) => section.id === id) + (direction === "up" ? -1 : 1);
2073
+ move(id, direction);
2074
+ if (to === 0 || to === sections.length - 1) focusSection(id);
2075
+ };
2076
+ useEffect(() => {
2077
+ if (selectedId === null) return;
2078
+ const frame = requestAnimationFrame(() => {
2079
+ sectionNodes.get(selectedId)?.scrollIntoView?.({ block: "nearest" });
2080
+ });
2081
+ return () => cancelAnimationFrame(frame);
2082
+ }, [
2083
+ sectionNodes,
2084
+ selectedId,
2085
+ selectedIndex
2086
+ ]);
2087
+ return {
2088
+ sensors,
2089
+ announcements,
2090
+ screenReaderInstructions,
2091
+ sectionNodes,
2092
+ onNavigate,
2093
+ onDeleteKey,
2094
+ onMoveButton
2095
+ };
2096
+ };
2097
+ //#endregion
2098
+ //#region src/components/dnd/use-inspector-state.ts
2099
+ const useInspectorState = ({ selectedId, selectOnly, sectionNodes, onNodePick }) => {
2100
+ const [inspecting, setInspecting] = useState(false);
2101
+ const [picked, setPicked] = useState(null);
2102
+ const [hoverRect, setHoverRect] = useState(null);
2103
+ const [fieldTarget, setFieldTarget] = useState(null);
2104
+ const [fieldRect, setFieldRect] = useState(null);
2105
+ if (picked && picked.sectionId !== selectedId) setPicked(null);
2106
+ const stopInspecting = useCallback(() => {
2107
+ setInspecting(false);
2108
+ setHoverRect(null);
2109
+ }, []);
2110
+ useEffect(() => {
2111
+ if (!inspecting) return;
2112
+ const onKeyDown = (e) => {
2113
+ if (e.key === "Escape") stopInspecting();
2114
+ };
2115
+ window.addEventListener("keydown", onKeyDown);
2116
+ return () => window.removeEventListener("keydown", onKeyDown);
2117
+ }, [inspecting, stopInspecting]);
2118
+ useEffect(() => {
2119
+ const section = selectedId === null ? null : sectionNodes.get(selectedId);
2120
+ if (!fieldTarget || !section) return;
2121
+ let frame = 0;
2122
+ const measure = () => {
2123
+ cancelAnimationFrame(frame);
2124
+ frame = requestAnimationFrame(() => {
2125
+ const element = findElementById(section, fieldTarget);
2126
+ setFieldRect(element ? viewportRect(element) : null);
2127
+ });
2128
+ };
2129
+ measure();
2130
+ window.addEventListener("scroll", measure, true);
2131
+ window.addEventListener("resize", measure);
2132
+ return () => {
2133
+ cancelAnimationFrame(frame);
2134
+ window.removeEventListener("scroll", measure, true);
2135
+ window.removeEventListener("resize", measure);
2136
+ };
2137
+ }, [
2138
+ fieldTarget,
2139
+ selectedId,
2140
+ sectionNodes
2141
+ ]);
2142
+ const inspector = {
2143
+ active: inspecting,
2144
+ activate: () => setInspecting(true),
2145
+ deactivate: stopInspecting,
2146
+ toggle: () => inspecting ? stopInspecting() : setInspecting(true),
2147
+ picked,
2148
+ highlight: setFieldTarget
2149
+ };
2150
+ const onInspectMove = (section, overlay, x, y) => {
2151
+ const element = pickElement(section, overlay, x, y);
2152
+ setHoverRect(element ? viewportRect(element) : null);
2153
+ };
2154
+ const onInspectPick = (sectionId, section, overlay, x, y) => {
2155
+ const id = pickElement(section, overlay, x, y)?.getAttribute(DATA_ATTR.ID);
2156
+ if (!id) return;
2157
+ const pick = {
2158
+ id,
2159
+ sectionId
2160
+ };
2161
+ selectOnly(sectionId);
2162
+ setPicked(pick);
2163
+ stopInspecting();
2164
+ onNodePick?.(pick);
2165
+ };
2166
+ return {
2167
+ inspector,
2168
+ inspecting,
2169
+ onInspectMove,
2170
+ onInspectPick,
2171
+ onInspectLeave: () => setHoverRect(null),
2172
+ highlightRect: inspecting ? hoverRect : fieldTarget ? fieldRect : null
2173
+ };
2174
+ };
2175
+ //#endregion
1880
2176
  //#region src/components/dnd/use-section-document.ts
1881
2177
  const createLastParsed = () => {
1882
2178
  let last = null;
@@ -2070,81 +2366,61 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
2070
2366
  };
2071
2367
  //#endregion
2072
2368
  //#region src/components/dnd/dnd.tsx
2073
- const describeUpdateFailure = (failure, label) => {
2369
+ const describeUpdateFailure = (failure, label, { editErrors: m }) => {
2074
2370
  switch (failure?.reason) {
2075
2371
  case "attribute-not-found": return {
2076
- title: `Failed to update "${label}"`,
2077
- description: `This element has no "${failure.property}" attribute — check the property in its data-binding.`
2372
+ title: m.updateFailed(label),
2373
+ description: m.attributeNotFound(failure.property)
2078
2374
  };
2079
2375
  case "binding-not-declared": return {
2080
- title: `Failed to update "${label}"`,
2081
- description: `No binding for ${failure.property ? `property "${failure.property}"` : `label "${label}"`} is declared on this element's data-binding.`
2376
+ title: m.updateFailed(label),
2377
+ description: m.bindingNotDeclared({
2378
+ label,
2379
+ property: failure.property
2380
+ })
2082
2381
  };
2083
2382
  case "duplicate-binding": return {
2084
- title: `Failed to update "${label}"`,
2085
- description: `${failure.count} bindings share ${failure.property ? `property "${failure.property}"` : `label "${label}"`} on this element — remove the duplicate in its data-binding.`
2383
+ title: m.updateFailed(label),
2384
+ description: m.duplicateBinding({
2385
+ count: failure.count,
2386
+ label,
2387
+ property: failure.property
2388
+ })
2086
2389
  };
2087
2390
  case "reserved-property": return {
2088
- title: `Cannot edit "${label}" in the panel`,
2089
- description: `"${failure.property}" is managed by the editor, so a binding can't change it.`
2391
+ title: m.cannotEdit(label),
2392
+ description: m.reservedProperty(failure.property)
2090
2393
  };
2091
2394
  case "required-property": return {
2092
- title: `Cannot remove "${label}"`,
2093
- description: `"${failure.property}" is marked required in its data-binding, so it can't be removed.`
2395
+ title: m.cannotRemove(label),
2396
+ description: m.requiredProperty(failure.property)
2094
2397
  };
2095
2398
  case "no-binding": return {
2096
- title: `Failed to update "${label}"`,
2097
- description: "This element has no data-binding declaration."
2399
+ title: m.updateFailed(label),
2400
+ description: m.noBinding
2098
2401
  };
2099
2402
  case "element-not-found": return {
2100
- title: `Failed to update "${label}"`,
2101
- description: "The target element could not be found in this section."
2403
+ title: m.updateFailed(label),
2404
+ description: m.elementNotFound
2102
2405
  };
2103
2406
  case "unsupported-syntax": return {
2104
- title: `Cannot edit "${label}" in the panel`,
2105
- description: `The "${failure.property}" expression was preserved. Change it in the code editor instead.`
2407
+ title: m.cannotEdit(label),
2408
+ description: m.unsupportedSyntax(failure.property)
2106
2409
  };
2107
2410
  case "parse-error": return {
2108
- title: `Failed to update "${label}"`,
2109
- description: failure.error instanceof Error ? failure.error.message : "Check the console for details."
2411
+ title: m.updateFailed(label),
2412
+ description: failure.error instanceof Error ? failure.error.message : m.checkConsole
2110
2413
  };
2111
- default: return { title: `Failed to update "${label}"` };
2414
+ default: return { title: m.updateFailed(label) };
2112
2415
  }
2113
2416
  };
2114
- const KEYBOARD_CODES = {
2115
- start: ["Space"],
2116
- cancel: ["Escape"],
2117
- end: ["Space", "Enter"]
2118
- };
2119
- const SCREEN_READER_INSTRUCTIONS = { draggable: "On a palette item, press Enter to add it to the canvas. On a canvas section, press Enter to select it, the up and down arrow keys to go to the previous or next section, and Delete to remove it. Press Space to pick a section up, the arrow keys to move it, and Space or Enter to drop it. Press Escape to cancel." };
2120
- const createNodeRegistry = () => {
2121
- const nodes = /* @__PURE__ */ new Map();
2122
- const callbacks = /* @__PURE__ */ new Map();
2123
- return {
2124
- get: (id) => nodes.get(id),
2125
- register: (id) => {
2126
- let callback = callbacks.get(id);
2127
- if (!callback) {
2128
- callback = (node) => {
2129
- if (node) nodes.set(id, node);
2130
- else {
2131
- nodes.delete(id);
2132
- callbacks.delete(id);
2133
- }
2134
- };
2135
- callbacks.set(id, callback);
2136
- }
2137
- return callback;
2138
- }
2139
- };
2140
- };
2141
- const blockedEditError = (error) => ({
2417
+ const blockedEditError = (error, { editErrors: m }) => ({
2142
2418
  type: "parse",
2143
2419
  target: "document",
2144
2420
  reason: "parse-error",
2145
2421
  error,
2146
- title: "The document has a syntax error",
2147
- description: "The canvas shows the last version that parsed. Fix the error in the code, then edit here again."
2422
+ title: m.syntaxError,
2423
+ description: m.syntaxErrorDetail
2148
2424
  });
2149
2425
  const conditionalModifiers = (args) => {
2150
2426
  const { active } = args;
@@ -2152,7 +2428,7 @@ const conditionalModifiers = (args) => {
2152
2428
  return restrictToVerticalAxis(args);
2153
2429
  };
2154
2430
  const NO_MODULES = {};
2155
- const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange: _onChange, className, items = [], frame, dynamicTailwind = false, provider, renderField, onEditError, renderSectionFallback, shouldForceSectionFallback, onBeforeDelete, sectionNameFallback, containerId, onNodePick, children, ...restProps }) => {
2431
+ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange: _onChange, className, items = [], frame, dynamicTailwind = false, provider, renderField, onEditError, renderSectionFallback, shouldForceSectionFallback, onBeforeDelete, sectionNameFallback, containerId, bindings: bindingRegistry, bindingKeys, onNodePick, children, ...restProps }) => {
2156
2432
  const modules = useStableModules(_modules);
2157
2433
  const [mobilePaletteOpen, setMobilePaletteOpen] = useState(false);
2158
2434
  const { breakpoint } = useResponsiveSize();
@@ -2166,28 +2442,17 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2166
2442
  return false;
2167
2443
  }
2168
2444
  };
2169
- const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }), useSensor(KeyboardSensor, {
2170
- coordinateGetter: sortableKeyboardCoordinates,
2171
- keyboardCodes: KEYBOARD_CODES
2172
- }));
2173
- const nameOf = (id, data) => {
2174
- return (data?.current)?.item?.name ?? sections.find((section) => section.id === id)?.name ?? "section";
2175
- };
2176
- const positionOf = (id) => sections.findIndex((section) => section.id === id) + 1;
2177
- const announcements = {
2178
- onDragStart: ({ active }) => `Picked up ${nameOf(active.id, active.data)}.`,
2179
- onDragOver: ({ active, over }) => over && positionOf(over.id) > 0 ? `${nameOf(active.id, active.data)} moved to position ${positionOf(over.id)} of ${sections.length}.` : `${nameOf(active.id, active.data)} is no longer over a position.`,
2180
- onDragEnd: ({ active, over }) => over && positionOf(over.id) > 0 ? `${nameOf(active.id, active.data)} dropped at position ${positionOf(over.id)} of ${sections.length}.` : `${nameOf(active.id, active.data)} dropped.`,
2181
- onDragCancel: ({ active }) => `Moving ${nameOf(active.id, active.data)} was cancelled.`
2182
- };
2183
2445
  const value = _value === void 0 ? code : _value;
2446
+ const messages = useLiveMessages();
2184
2447
  const reportError = onEditError ?? toastEditError;
2185
2448
  const reportErrorRef = useRef(reportError);
2449
+ const messagesRef = useRef(messages);
2186
2450
  useEffect(() => {
2187
2451
  reportErrorRef.current = reportError;
2452
+ messagesRef.current = messages;
2188
2453
  });
2189
2454
  const onBlockedEdit = useCallback((problem) => {
2190
- if (problem.reason === "parse-error") reportErrorRef.current(blockedEditError(problem.error));
2455
+ if (problem.reason === "parse-error") reportErrorRef.current(blockedEditError(problem.error, messagesRef.current));
2191
2456
  }, []);
2192
2457
  const { sections, previews, selectedId, selectedItem, selectedIndex, select, selectOnly, clearSelection, add: addItem, remove, copy: onCopy, move: moveSection, reorder, patch, getCommittedSection, problem, stale } = useSectionDocument(value, _onChange, {
2193
2458
  containerId,
@@ -2195,8 +2460,6 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2195
2460
  onBlockedEdit
2196
2461
  });
2197
2462
  const missingContainer = problem?.reason === "container-not-found" ? problem.containerId : null;
2198
- const onDragStart = (_) => {};
2199
- const onDragOver = (_e) => {};
2200
2463
  const onDragEnd = (event) => {
2201
2464
  const { active, over } = event;
2202
2465
  if (!over) return;
@@ -2209,108 +2472,34 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2209
2472
  if (active.id !== over.id && sections.some((s) => s.id === active.id)) reorder(String(active.id), String(over.id));
2210
2473
  };
2211
2474
  const onSelect = (id) => select(id);
2212
- const [inspecting, setInspecting] = useState(false);
2213
- const [picked, setPicked] = useState(null);
2214
- const [highlight, setHighlight] = useState(null);
2215
- if (picked && picked.sectionId !== selectedId) setPicked(null);
2216
- const stopInspecting = useCallback(() => {
2217
- setInspecting(false);
2218
- setHighlight(null);
2219
- }, []);
2220
- useEffect(() => {
2221
- if (!inspecting) return;
2222
- const onKeyDown = (e) => {
2223
- if (e.key === "Escape") stopInspecting();
2224
- };
2225
- window.addEventListener("keydown", onKeyDown);
2226
- return () => window.removeEventListener("keydown", onKeyDown);
2227
- }, [inspecting, stopInspecting]);
2228
- const inspector = {
2229
- active: inspecting,
2230
- activate: () => setInspecting(true),
2231
- deactivate: stopInspecting,
2232
- toggle: () => inspecting ? stopInspecting() : setInspecting(true),
2233
- picked
2234
- };
2235
- const onInspectMove = (section, overlay, x, y) => {
2236
- const element = pickElement(section, overlay, x, y);
2237
- setHighlight(element ? viewportRect(element) : null);
2238
- };
2239
- const onInspectPick = (sectionId, section, overlay, x, y) => {
2240
- const id = pickElement(section, overlay, x, y)?.getAttribute(DATA_ATTR.ID);
2241
- if (!id) return;
2242
- const pick = {
2243
- id,
2244
- sectionId
2245
- };
2246
- selectOnly(sectionId);
2247
- setPicked(pick);
2248
- stopInspecting();
2249
- onNodePick?.(pick);
2250
- };
2251
- const removeRef = useRef(remove);
2252
- useEffect(() => {
2253
- removeRef.current = remove;
2475
+ const requestDelete = useDeleteFlow({
2476
+ sections,
2477
+ remove,
2478
+ onBeforeDelete
2254
2479
  });
2255
- const requestDelete = (id, onDeleted) => {
2256
- const section = sections.find((candidate) => candidate.id === id);
2257
- if (!onBeforeDelete || !section) {
2258
- remove(id);
2259
- onDeleted?.();
2260
- return;
2261
- }
2262
- const decide = (allowed) => {
2263
- if (allowed) {
2264
- removeRef.current(id);
2265
- onDeleted?.();
2266
- }
2267
- };
2268
- try {
2269
- const answer = onBeforeDelete(section);
2270
- if (typeof answer === "boolean") {
2271
- decide(answer);
2272
- return;
2273
- }
2274
- answer.then(decide, (error) => {
2275
- console.error("onBeforeDelete rejected; the section was kept", error);
2276
- });
2277
- } catch (error) {
2278
- console.error("onBeforeDelete threw; the section was kept", error);
2279
- }
2280
- };
2281
2480
  const onDelete = (id) => requestDelete(id);
2282
- const [sectionNodes] = useState(createNodeRegistry);
2283
- const focusSection = (id) => sectionNodes.get(id)?.focus();
2284
- const onNavigate = (fromId, to) => {
2285
- const from = sections.findIndex((section) => section.id === fromId);
2286
- const index = to === "first" ? 0 : to === "last" ? sections.length - 1 : from + (to === "next" ? 1 : -1);
2287
- const target = sections[index];
2288
- if (from < 0 || !target || target.id === fromId) return;
2289
- selectOnly(target.id);
2290
- focusSection(target.id);
2291
- };
2292
- const onDeleteKey = (id) => {
2293
- const index = sections.findIndex((section) => section.id === id);
2294
- const neighbor = sections[index + 1] ?? sections[index - 1];
2295
- requestDelete(id, () => {
2296
- if (neighbor) focusSection(neighbor.id);
2297
- });
2298
- };
2299
- useEffect(() => {
2300
- if (selectedId === null) return;
2301
- const frame = requestAnimationFrame(() => {
2302
- sectionNodes.get(selectedId)?.scrollIntoView?.({ block: "nearest" });
2303
- });
2304
- return () => cancelAnimationFrame(frame);
2305
- }, [
2306
- sectionNodes,
2481
+ const { sensors, announcements, screenReaderInstructions, sectionNodes, onNavigate, onDeleteKey, onMoveButton } = useDndKeyboard({
2482
+ sections,
2307
2483
  selectedId,
2308
- selectedIndex
2309
- ]);
2484
+ selectedIndex,
2485
+ selectOnly,
2486
+ move: moveSection,
2487
+ requestDelete
2488
+ });
2489
+ const { inspector, inspecting, onInspectMove, onInspectPick, onInspectLeave, highlightRect } = useInspectorState({
2490
+ selectedId,
2491
+ selectOnly,
2492
+ sectionNodes,
2493
+ onNodePick
2494
+ });
2310
2495
  const onChange = (next) => {
2311
2496
  if (!next.id) return;
2312
2497
  patch(next);
2313
2498
  };
2499
+ const bindingOptions = useMemo(() => ({
2500
+ bindings: bindingRegistry,
2501
+ bindingKeys
2502
+ }), [bindingRegistry, bindingKeys]);
2314
2503
  const selectedCode = selectedItem?.code;
2315
2504
  const selectedSectionId = selectedItem?.id;
2316
2505
  const { fields, updatedCode, parseError } = useMemo(() => {
@@ -2322,7 +2511,7 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2322
2511
  try {
2323
2512
  const updated = fillIdsFrom(selectedCode, selectedSectionId ?? "");
2324
2513
  return {
2325
- fields: extract(updated),
2514
+ fields: extract(updated, bindingOptions),
2326
2515
  updatedCode: updated !== selectedCode ? updated : selectedCode,
2327
2516
  parseError: null
2328
2517
  };
@@ -2334,15 +2523,19 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2334
2523
  parseError: { error: e }
2335
2524
  };
2336
2525
  }
2337
- }, [selectedCode, selectedSectionId]);
2526
+ }, [
2527
+ selectedCode,
2528
+ selectedSectionId,
2529
+ bindingOptions
2530
+ ]);
2338
2531
  useEffect(() => {
2339
2532
  if (missingContainer !== null) reportErrorRef.current({
2340
2533
  type: "parse",
2341
2534
  target: "document",
2342
2535
  reason: "container-not-found",
2343
2536
  containerId: missingContainer,
2344
- title: `No #${missingContainer} element in the document`,
2345
- description: `Sections are the <section> elements inside the element with id="${missingContainer}". Start from createDocument(), or set containerId to match your document.`
2537
+ title: messagesRef.current.editErrors.missingContainer(missingContainer),
2538
+ description: messagesRef.current.editErrors.missingContainerDetail(missingContainer)
2346
2539
  });
2347
2540
  }, [missingContainer]);
2348
2541
  useEffect(() => {
@@ -2350,14 +2543,14 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2350
2543
  type: "parse",
2351
2544
  target: "section",
2352
2545
  error: parseError.error,
2353
- title: "Failed to parse this section",
2354
- description: "Check the console for details."
2546
+ title: messagesRef.current.editErrors.sectionParseFailed,
2547
+ description: messagesRef.current.editErrors.checkConsole
2355
2548
  });
2356
2549
  }, [parseError]);
2357
2550
  const commitChanges = (changes) => {
2358
2551
  if (changes.length === 0) return;
2359
2552
  if (stale && problem?.reason === "parse-error") {
2360
- reportError(blockedEditError(problem.error));
2553
+ reportError(blockedEditError(problem.error, messages));
2361
2554
  return;
2362
2555
  }
2363
2556
  const committed = selectedItem && getCommittedSection(selectedItem.id)?.code;
@@ -2366,7 +2559,7 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2366
2559
  label,
2367
2560
  property,
2368
2561
  value: changeValue
2369
- })));
2562
+ })), bindingOptions);
2370
2563
  if (!result.success) {
2371
2564
  const { id, label, property } = changes[result.index];
2372
2565
  reportError({
@@ -2375,7 +2568,7 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2375
2568
  label,
2376
2569
  property,
2377
2570
  failure: result.failure,
2378
- ...describeUpdateFailure(result.failure, label)
2571
+ ...describeUpdateFailure(result.failure, label, messages)
2379
2572
  });
2380
2573
  return;
2381
2574
  }
@@ -2408,12 +2601,13 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2408
2601
  bindings,
2409
2602
  onNodeChange: onFieldChange,
2410
2603
  onNodesChange: commitChanges,
2411
- readOnly: stale
2604
+ readOnly: stale,
2605
+ bindingOptions
2412
2606
  };
2413
2607
  const canvas = /* @__PURE__ */ jsxs(Fragment, { children: [stale && /* @__PURE__ */ jsx("div", {
2414
2608
  role: "status",
2415
2609
  className: cn("sticky top-0 z-70", "border-b border-amber-200 bg-amber-50 px-3 py-2", "text-sm text-amber-900"),
2416
- children: "Showing the last version that parsed. Fix the syntax error in the code to edit here again."
2610
+ children: messages.canvas.stale
2417
2611
  }), /* @__PURE__ */ jsx(Droppable, {
2418
2612
  className: cn(!sections.length && "h-full"),
2419
2613
  children: !sections.length ? /* @__PURE__ */ jsx("div", {
@@ -2421,23 +2615,15 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2421
2615
  children: problem?.reason === "parse-error" ? /* @__PURE__ */ jsxs(Space, {
2422
2616
  orientation: "vertical",
2423
2617
  align: "center",
2424
- children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: "The document has a syntax error" }), /* @__PURE__ */ jsx(Typography.Text, { children: "Fix it in the code to see its sections" })]
2618
+ children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: messages.canvas.syntaxError }), /* @__PURE__ */ jsx(Typography.Text, { children: messages.canvas.syntaxErrorDetail })]
2425
2619
  }) : missingContainer !== null ? /* @__PURE__ */ jsxs(Space, {
2426
2620
  orientation: "vertical",
2427
2621
  align: "center",
2428
- children: [/* @__PURE__ */ jsxs(Typography.Paragraph, { children: [
2429
- "No #",
2430
- missingContainer,
2431
- " element in the document"
2432
- ] }), /* @__PURE__ */ jsxs(Typography.Text, { children: [
2433
- "Sections go inside the element with id=\"",
2434
- missingContainer,
2435
- "\""
2436
- ] })]
2622
+ children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: messages.canvas.missingContainer(missingContainer) }), /* @__PURE__ */ jsx(Typography.Text, { children: messages.canvas.missingContainerDetail(missingContainer) })]
2437
2623
  }) : /* @__PURE__ */ jsxs(Space, {
2438
2624
  orientation: "vertical",
2439
2625
  align: "center",
2440
- children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: "No sections available" }), /* @__PURE__ */ jsx(Typography.Text, { children: isMobile ? "Tap a component to add it" : "Drag a component from the left to add it" })]
2626
+ children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: messages.canvas.empty }), /* @__PURE__ */ jsx(Typography.Text, { children: isMobile ? messages.canvas.emptyHintTouch : messages.canvas.emptyHintDrag })]
2441
2627
  })
2442
2628
  }) : /* @__PURE__ */ jsx(SortableContext, {
2443
2629
  items: sections.map((s) => s.id),
@@ -2449,13 +2635,17 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2449
2635
  onClick: () => onSelect(section.id),
2450
2636
  onDelete,
2451
2637
  onCopy,
2638
+ onMoveUp: () => onMoveButton(section.id, "up"),
2639
+ onMoveDown: () => onMoveButton(section.id, "down"),
2640
+ canMoveUp: index > 0,
2641
+ canMoveDown: index < sections.length - 1,
2452
2642
  nodeRef: sectionNodes.register(section.id),
2453
2643
  onNavigate: (to) => onNavigate(section.id, to),
2454
2644
  onDeleteKey: () => onDeleteKey(section.id),
2455
2645
  inspecting,
2456
2646
  onInspectMove,
2457
2647
  onInspectPick: (...args) => onInspectPick(section.id, ...args),
2458
- onInspectLeave: () => setHighlight(null),
2648
+ onInspectLeave,
2459
2649
  children: /* @__PURE__ */ jsx(renderer_default, {
2460
2650
  preview: previews[index],
2461
2651
  sectionId: section.id,
@@ -2478,11 +2668,9 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2478
2668
  collisionDetection: closestCenter,
2479
2669
  accessibility: {
2480
2670
  announcements,
2481
- screenReaderInstructions: SCREEN_READER_INSTRUCTIONS
2671
+ screenReaderInstructions
2482
2672
  },
2483
2673
  modifiers: [conditionalModifiers],
2484
- onDragStart,
2485
- onDragOver,
2486
2674
  onDragEnd,
2487
2675
  children: [/* @__PURE__ */ jsx("div", {
2488
2676
  className: cn("relative flex h-full w-full", className),
@@ -2490,7 +2678,8 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2490
2678
  children: /* @__PURE__ */ jsxs(DndEditOptionsContext.Provider, {
2491
2679
  value: {
2492
2680
  renderField,
2493
- reportError
2681
+ reportError,
2682
+ bindingOptions
2494
2683
  },
2495
2684
  children: [/* @__PURE__ */ jsx(DndRegionContext.Provider, {
2496
2685
  value: {
@@ -2508,7 +2697,7 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
2508
2697
  value: inspector,
2509
2698
  children: Children.toArray(children).length ? children : /* @__PURE__ */ jsx(Layout, {})
2510
2699
  })
2511
- }), /* @__PURE__ */ jsx(InspectorHighlight, { rect: inspecting ? highlight : null })]
2700
+ }), /* @__PURE__ */ jsx(InspectorHighlight, { rect: highlightRect })]
2512
2701
  })
2513
2702
  }), /* @__PURE__ */ jsx(DragOverlay, { children: /* @__PURE__ */ jsx(Overlay, {
2514
2703
  sections,
@@ -2541,4 +2730,4 @@ const useChildrenEditor = useDndChildren;
2541
2730
  //#endregion
2542
2731
  export { useDndPanel as _, Layout as a, Field as c, isStructuralFieldKind as d, useDndChildren as f, useDndPalette as g, useDndLayout as h, Canvas as i, useDndItems as l, withPanelCommit as m, useChildrenEditor as n, Palette as o, resolvePanelBindings as p, useItemsEditor as r, Panel as s, Dnd as t, getFieldKind as u, DraggableItem as v, useDndInspector as y };
2543
2732
 
2544
- //# sourceMappingURL=dnd-x8Mvj--6.js.map
2733
+ //# sourceMappingURL=dnd-DGNp20Nk.js.map