@elytracms/studio 0.0.64 → 0.0.66

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 (182) hide show
  1. package/convex/assets.ts +11 -0
  2. package/convex/delivery.ts +5 -0
  3. package/convex/schema.ts +23 -0
  4. package/dist/components/account/self-account-view-source.js +9 -2
  5. package/dist/components/account/self-account-view-source.js.map +1 -1
  6. package/dist/components/app-views/default-modules.d.ts +28 -1
  7. package/dist/components/app-views/default-modules.js +47 -2
  8. package/dist/components/app-views/default-modules.js.map +1 -1
  9. package/dist/components/assets/asset-thumb.d.ts +4 -2
  10. package/dist/components/assets/asset-thumb.js +26 -4
  11. package/dist/components/assets/asset-thumb.js.map +1 -1
  12. package/dist/components/assets/assets-module.js +48 -2
  13. package/dist/components/assets/assets-module.js.map +1 -1
  14. package/dist/components/assets/assets-store.d.ts +11 -1
  15. package/dist/components/assets/assets-store.js +18 -1
  16. package/dist/components/assets/assets-store.js.map +1 -1
  17. package/dist/components/auth-context.js +32 -1
  18. package/dist/components/auth-context.js.map +1 -1
  19. package/dist/components/builder/blocks-model.d.ts +11 -0
  20. package/dist/components/builder/blocks-model.js +30 -10
  21. package/dist/components/builder/blocks-model.js.map +1 -1
  22. package/dist/components/builder/canvas-chrome-css.js +90 -8
  23. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  24. package/dist/components/builder/canvas-dnd.js +4 -2
  25. package/dist/components/builder/canvas-dnd.js.map +1 -1
  26. package/dist/components/builder/canvas-inline-editing.d.ts +7 -0
  27. package/dist/components/builder/canvas-inline-editing.js +3 -1
  28. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  29. package/dist/components/builder/canvas-overlay-geometry.d.ts +72 -0
  30. package/dist/components/builder/canvas-overlay-geometry.js +80 -0
  31. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -0
  32. package/dist/components/builder/canvas-rect-tracking.d.ts +124 -0
  33. package/dist/components/builder/canvas-rect-tracking.js +198 -0
  34. package/dist/components/builder/canvas-rect-tracking.js.map +1 -0
  35. package/dist/components/builder/canvas.js +61 -12
  36. package/dist/components/builder/canvas.js.map +1 -1
  37. package/dist/components/builder/composition-field-editor.js +22 -3
  38. package/dist/components/builder/composition-field-editor.js.map +1 -1
  39. package/dist/components/builder/delete-key.d.ts +38 -0
  40. package/dist/components/builder/delete-key.js +46 -0
  41. package/dist/components/builder/delete-key.js.map +1 -0
  42. package/dist/components/builder/drag-session.d.ts +8 -0
  43. package/dist/components/builder/drag-session.js +15 -0
  44. package/dist/components/builder/drag-session.js.map +1 -1
  45. package/dist/components/builder/insert-affordance-context.d.ts +19 -0
  46. package/dist/components/builder/insert-affordance-context.js +7 -0
  47. package/dist/components/builder/insert-affordance-context.js.map +1 -0
  48. package/dist/components/builder/insert-context.d.ts +40 -0
  49. package/dist/components/builder/insert-context.js +97 -5
  50. package/dist/components/builder/insert-context.js.map +1 -1
  51. package/dist/components/builder/node-title.d.ts +60 -0
  52. package/dist/components/builder/node-title.js +135 -0
  53. package/dist/components/builder/node-title.js.map +1 -0
  54. package/dist/components/builder/page-tree.js +25 -5
  55. package/dist/components/builder/page-tree.js.map +1 -1
  56. package/dist/components/builder/prop-selection.d.ts +52 -0
  57. package/dist/components/builder/prop-selection.js +38 -0
  58. package/dist/components/builder/prop-selection.js.map +1 -0
  59. package/dist/components/builder/prop-sync-context.d.ts +49 -0
  60. package/dist/components/builder/prop-sync-context.js +23 -0
  61. package/dist/components/builder/prop-sync-context.js.map +1 -0
  62. package/dist/components/builder/property-inspector.js +24 -1
  63. package/dist/components/builder/property-inspector.js.map +1 -1
  64. package/dist/components/builder/reuse-context.js +1 -1
  65. package/dist/components/builder/reuse-context.js.map +1 -1
  66. package/dist/components/builder/selection-overlay.d.ts +79 -0
  67. package/dist/components/builder/selection-overlay.js +244 -0
  68. package/dist/components/builder/selection-overlay.js.map +1 -0
  69. package/dist/components/builder/spacing-handles.d.ts +57 -1
  70. package/dist/components/builder/spacing-handles.js +142 -26
  71. package/dist/components/builder/spacing-handles.js.map +1 -1
  72. package/dist/components/cms/asset-field.js +21 -5
  73. package/dist/components/cms/asset-field.js.map +1 -1
  74. package/dist/components/cms/asset-picker.js +11 -5
  75. package/dist/components/cms/asset-picker.js.map +1 -1
  76. package/dist/components/cms/bulk-actions.d.ts +22 -3
  77. package/dist/components/cms/bulk-actions.js +27 -3
  78. package/dist/components/cms/bulk-actions.js.map +1 -1
  79. package/dist/components/cms/cms-store.js +19 -0
  80. package/dist/components/cms/cms-store.js.map +1 -1
  81. package/dist/components/cms/detail-editor.js +18 -19
  82. package/dist/components/cms/detail-editor.js.map +1 -1
  83. package/dist/components/cms/prop-fields.js +23 -16
  84. package/dist/components/cms/prop-fields.js.map +1 -1
  85. package/dist/components/cms/publish-clean.d.ts +21 -0
  86. package/dist/components/cms/publish-clean.js +30 -0
  87. package/dist/components/cms/publish-clean.js.map +1 -0
  88. package/dist/components/cms/rich-text/block-style.d.ts +48 -0
  89. package/dist/components/cms/rich-text/block-style.js +30 -0
  90. package/dist/components/cms/rich-text/block-style.js.map +1 -0
  91. package/dist/components/cms/rich-text/embed-picker.js +4 -3
  92. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  93. package/dist/components/cms/rich-text/extensions.d.ts +2 -0
  94. package/dist/components/cms/rich-text/extensions.js +4 -0
  95. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  96. package/dist/components/cms/rich-text/font-size-control.d.ts +31 -0
  97. package/dist/components/cms/rich-text/font-size-control.js +40 -0
  98. package/dist/components/cms/rich-text/font-size-control.js.map +1 -0
  99. package/dist/components/cms/rich-text/font-size.d.ts +16 -0
  100. package/dist/components/cms/rich-text/font-size.js +40 -0
  101. package/dist/components/cms/rich-text/font-size.js.map +1 -0
  102. package/dist/components/cms/rich-text/toolbar-state.d.ts +3 -0
  103. package/dist/components/cms/rich-text/toolbar-state.js +3 -0
  104. package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
  105. package/dist/components/cms/rich-text/toolbar.d.ts +13 -0
  106. package/dist/components/cms/rich-text/toolbar.js +17 -5
  107. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  108. package/dist/components/cms/use-live-url.d.ts +24 -1
  109. package/dist/components/cms/use-live-url.js +26 -4
  110. package/dist/components/cms/use-live-url.js.map +1 -1
  111. package/dist/components/cms/use-publish-controller.js +8 -6
  112. package/dist/components/cms/use-publish-controller.js.map +1 -1
  113. package/dist/components/collection-view/cms-collection-view-source.js +15 -7
  114. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  115. package/dist/components/components/component-detail.js +3 -3
  116. package/dist/components/components/component-detail.js.map +1 -1
  117. package/dist/components/components/component-preview.js +1 -1
  118. package/dist/components/components/component-preview.js.map +1 -1
  119. package/dist/components/components/registry-browser.js +2 -2
  120. package/dist/components/components/registry-browser.js.map +1 -1
  121. package/dist/components/components/taxonomy.js +3 -2
  122. package/dist/components/components/taxonomy.js.map +1 -1
  123. package/dist/components/components/transfer-dialog.js +1 -1
  124. package/dist/components/components/transfer-dialog.js.map +1 -1
  125. package/dist/lib/assets/backfill-thumbs.d.ts +67 -0
  126. package/dist/lib/assets/backfill-thumbs.js +120 -0
  127. package/dist/lib/assets/backfill-thumbs.js.map +1 -0
  128. package/dist/lib/assets/delete-asset.d.ts +2 -1
  129. package/dist/lib/assets/delete-asset.js +12 -0
  130. package/dist/lib/assets/delete-asset.js.map +1 -1
  131. package/dist/lib/assets/index.d.ts +2 -0
  132. package/dist/lib/assets/index.js +2 -0
  133. package/dist/lib/assets/index.js.map +1 -1
  134. package/dist/lib/assets/thumbnail.d.ts +145 -0
  135. package/dist/lib/assets/thumbnail.js +220 -0
  136. package/dist/lib/assets/thumbnail.js.map +1 -0
  137. package/dist/lib/assets/upload.d.ts +9 -0
  138. package/dist/lib/assets/upload.js +31 -0
  139. package/dist/lib/assets/upload.js.map +1 -1
  140. package/dist/lib/auth/auth-adapter.d.ts +22 -0
  141. package/dist/lib/auth/auth-adapter.js.map +1 -1
  142. package/dist/lib/auth/convex-auth-adapter.d.ts +42 -1
  143. package/dist/lib/auth/convex-auth-adapter.js +69 -0
  144. package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
  145. package/dist/lib/auth/dev-auto-login-adapter.d.ts +2 -1
  146. package/dist/lib/auth/dev-auto-login-adapter.js +6 -0
  147. package/dist/lib/auth/dev-auto-login-adapter.js.map +1 -1
  148. package/dist/lib/auth/index.d.ts +1 -1
  149. package/dist/lib/auth/index.js.map +1 -1
  150. package/dist/lib/auth/local-auth-adapter.d.ts +2 -1
  151. package/dist/lib/auth/local-auth-adapter.js +7 -0
  152. package/dist/lib/auth/local-auth-adapter.js.map +1 -1
  153. package/dist/lib/auth-errors.d.ts +45 -3
  154. package/dist/lib/auth-errors.js +46 -5
  155. package/dist/lib/auth-errors.js.map +1 -1
  156. package/dist/lib/clipboard/paste-target.js +5 -2
  157. package/dist/lib/clipboard/paste-target.js.map +1 -1
  158. package/dist/lib/dnd/drop-rules.d.ts +13 -0
  159. package/dist/lib/dnd/drop-rules.js +15 -0
  160. package/dist/lib/dnd/drop-rules.js.map +1 -1
  161. package/dist/lib/i18n/messages.de.js +19 -0
  162. package/dist/lib/i18n/messages.de.js.map +1 -1
  163. package/dist/lib/i18n/messages.en.d.ts +16 -0
  164. package/dist/lib/i18n/messages.en.js +24 -0
  165. package/dist/lib/i18n/messages.en.js.map +1 -1
  166. package/dist/lib/persistence/convex-adapter.js +4 -0
  167. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  168. package/dist/lib/persistence/index.js +6 -3
  169. package/dist/lib/persistence/index.js.map +1 -1
  170. package/dist/lib/persona/labels.d.ts +9 -5
  171. package/dist/lib/persona/labels.js +13 -8
  172. package/dist/lib/persona/labels.js.map +1 -1
  173. package/dist/lib/references/references-store.js +7 -1
  174. package/dist/lib/references/references-store.js.map +1 -1
  175. package/dist/lib/session-expiry.d.ts +19 -6
  176. package/dist/lib/session-expiry.js +13 -1
  177. package/dist/lib/session-expiry.js.map +1 -1
  178. package/dist/lib/session-revalidation.d.ts +81 -0
  179. package/dist/lib/session-revalidation.js +94 -0
  180. package/dist/lib/session-revalidation.js.map +1 -0
  181. package/package.json +2 -2
  182. package/src/styles/studio.css +13 -0
@@ -1 +1 @@
1
- {"version":3,"file":"page-tree.js","sourceRoot":"","sources":["../../../src/components/builder/page-tree.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Boxes,\n ChevronUp,\n ChevronDown,\n ClipboardCopy,\n ClipboardPaste,\n ClipboardX,\n Copy,\n EyeOff,\n Filter,\n MoreHorizontal,\n Plus,\n Trash2,\n Unlink,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { Condition, ComponentNode } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPointer,\n resolveDrop,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { DropIndicator, DropPlan, DropPosition } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useData } from '@elytracms/studio/lib/data'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useCollab } from '../collab/collab-context'\nimport { useBuilder } from './builder-store'\nimport { attachClipboardListeners, useStudioClipboard } from './clipboard-actions'\nimport { useReuse } from './reuse-context'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\n\ninterface TreeRow {\n id: string\n componentId: string\n depth: number\n isRoot: boolean\n isFirst: boolean\n isLast: boolean\n condition?: Condition\n}\n\nfunction flatten(\n node: ComponentNode,\n depth: number,\n rows: TreeRow[],\n meta: { isFirst: boolean; isLast: boolean; isRoot: boolean },\n): void {\n rows.push({ id: node.id, componentId: node.componentId, depth, ...meta, condition: node.condition })\n for (const children of Object.values(node.slots ?? {})) {\n children.forEach((child, i) =>\n flatten(child, depth + 1, rows, {\n isFirst: i === 0,\n isLast: i === children.length - 1,\n isRoot: false,\n }),\n )\n }\n}\n\n/** Tailwind classes per drop indicator state (EC-160/EC-162) — rejection is visible. */\nconst ROW_DROP_CLASSES: Record<Exclude<DropIndicator, null>, string> = {\n into: 'ring-1 ring-inset ring-emerald-400 bg-emerald-400/10',\n before: 'shadow-[inset_0_2px_0_0_#38bdf8]',\n after: 'shadow-[inset_0_-2px_0_0_#38bdf8]',\n // Wrap-into-columns gesture (EC-162): the tree can execute wrap plans, the\n // bands themselves are a canvas gesture (rows have no spatial \"beside\").\n left: 'shadow-[inset_2px_0_0_0_#a78bfa] bg-violet-400/10',\n right: 'shadow-[inset_-2px_0_0_0_#a78bfa] bg-violet-400/10',\n blocked: 'ring-1 ring-inset ring-red-500/80 bg-red-500/10 cursor-not-allowed',\n}\n\nexport function PageTree({ onAddChild }: { onAddChild: (parentId: string) => void }) {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const data = useData()\n const collab = useCollab()\n const rows: TreeRow[] = []\n flatten(builder.present, 0, rows, { isFirst: true, isLast: true, isRoot: true })\n\n // Copy/cut/paste (EC-175): keyboard shortcuts for the whole studio window\n // (the canvas iframe wires its own document) plus per-row affordances. The\n // shared listener keeps shortcuts inert in form fields/inline editors, and\n // the published preview is read-only.\n const clipboard = useStudioClipboard()\n const readOnly = collab.previewMode === 'published'\n const selectedIdRef = useRef(builder.selectedId)\n selectedIdRef.current = builder.selectedId\n useEffect(() => {\n if (readOnly) return\n return attachClipboardListeners(window, {\n clipboard,\n getSelectedId: () => selectedIdRef.current,\n getSelection: () => window.getSelection(),\n })\n }, [clipboard, readOnly])\n\n // Drag & drop (EC-160): eligibility comes from the same pure rule set the\n // canvas uses (`lib/dnd/drop-rules`); the tree only renders the resolution.\n // One eligible drop dispatches exactly one move/insert action.\n // The current plan lives in a ref: `drop` fires synchronously after the\n // last `dragover`, before React flushes indicator state.\n const dropPlanRef = useRef<DropPlan | null>(null)\n const [drop, setDrop] = useState<{ rowId: string; indicator: DropIndicator } | null>(null)\n const clearDrop = () => {\n dropPlanRef.current = null\n setDrop(null)\n }\n useEffect(() => onDragSessionEnd(() => clearDrop()), [])\n const getManifest = (id: string) => studio.registry.getManifest(id)\n\n function handleRowDragStart(row: TreeRow, e: React.DragEvent) {\n if (row.isRoot) {\n e.preventDefault()\n return\n }\n beginDrag(\n {\n subject: { kind: 'node', nodeId: row.id, componentId: row.componentId },\n source: 'tree',\n label: shortId(row.componentId),\n },\n e.dataTransfer,\n )\n }\n\n function handleRowDragOver(row: TreeRow, e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n let position: DropPosition = 'into'\n if (!row.isRoot) {\n const rect = e.currentTarget.getBoundingClientRect()\n // The hit-band axis follows the parent container's flow (EC-162):\n // children of horizontal Stacks/Grids reorder along the row's x axis.\n const flow = containerFlowAxis(getParentInfo(builder.present, row.id)?.parent)\n const ratio =\n flow === 'horizontal'\n ? rect.width > 0\n ? (e.clientX - rect.left) / rect.width\n : 0.5\n : rect.height > 0\n ? (e.clientY - rect.top) / rect.height\n : 0.5\n position = dropPositionForPointer({\n ratio,\n nestable: canNestInto(getManifest, row.componentId),\n edges: true,\n })\n }\n const resolution = resolveDrop({\n root: builder.present,\n getManifest,\n subject: drag.subject,\n location: { nodeId: row.id, position },\n })\n const indicator = dropIndicatorFor(resolution, position)\n dropPlanRef.current = resolution.eligible ? resolution : null\n setDrop({ rowId: row.id, indicator })\n if (resolution.eligible) {\n e.preventDefault()\n e.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n }\n\n function handleRowDrop(e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlanRef.current\n clearDrop()\n if (!plan) return\n e.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n builder.dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162: shared plan vocabulary — the tree executes wrap plans too.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n builder.dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n builder.dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n function handleRowDragEnd() {\n clearDrop()\n endDrag('cancelled')\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"2\" className=\"h-10 shrink-0 border-b border-border px-3\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.pageTree.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {rows.length}\n </Text>\n </Row>\n <div\n className=\"min-h-0 flex-1 overflow-auto p-1.5\"\n onMouseLeave={() => builder.dispatch({ type: 'hover', id: null })}\n onDragLeave={(e) => {\n // Clear the indicator when the drag exits the tree panel.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) clearDrop()\n }}\n >\n {rows.map((row) => {\n const manifest = studio.registry.getManifest(row.componentId)\n const selected = row.id === builder.selectedId\n const hovered = row.id === builder.hoveredId\n // EC-088: indicate condition-gated nodes; flag the currently-hidden ones.\n let condState: 'none' | 'visible' | 'hidden' = 'none'\n if (row.condition) {\n const r = data.resolveCondition(row.condition)\n condState = r.kind === 'condition' && r.pass ? 'visible' : 'hidden'\n }\n return (\n <div\n key={row.id}\n onMouseEnter={() => builder.dispatch({ type: 'hover', id: row.id })}\n draggable={!row.isRoot}\n onDragStart={(e) => handleRowDragStart(row, e)}\n onDragOver={(e) => handleRowDragOver(row, e)}\n onDrop={handleRowDrop}\n onDragEnd={handleRowDragEnd}\n data-tree-row={row.id}\n data-tree-drop={drop?.rowId === row.id ? (drop.indicator ?? undefined) : undefined}\n className={cn(\n 'group flex items-center rounded-md pr-1 text-left text-xs transition-colors',\n selected\n ? 'bg-accent text-accent-foreground'\n : hovered\n ? 'bg-muted text-foreground'\n : 'text-muted-foreground',\n drop?.rowId === row.id && drop.indicator\n ? ROW_DROP_CLASSES[drop.indicator]\n : undefined,\n )}\n >\n <button\n type=\"button\"\n onClick={() => builder.dispatch({ type: 'select', id: row.id })}\n className=\"flex min-w-0 flex-1 items-center gap-1.5 py-1\"\n style={{ paddingLeft: `${6 + row.depth * 14}px` }}\n >\n <span className={cn('truncate font-mono', condState === 'hidden' && 'line-through opacity-70')}>\n {shortId(row.componentId)}\n </span>\n {manifest?.title ? (\n <span className=\"truncate text-muted-foreground/70\">{manifest.title}</span>\n ) : null}\n {condState === 'hidden' ? (\n <EyeOff\n className=\"size-3 shrink-0 text-warning\"\n aria-label={translate('builder.pageTree.hiddenByCondition')}\n />\n ) : condState === 'visible' ? (\n <Filter\n className=\"size-3 shrink-0 text-muted-foreground/60\"\n aria-label={translate('builder.pageTree.conditional')}\n />\n ) : null}\n </button>\n\n <div\n className={cn(\n 'flex shrink-0 items-center gap-0.5',\n selected\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',\n )}\n >\n {/* Add child stays inline — the one constructive, high-frequency\n action. Everything else collapses into the ••• menu so labels\n stop cropping on the 200–240px panel (EC-211). */}\n <IconAction label={translate('builder.pageTree.addChild')} onClick={() => onAddChild(row.id)}>\n <Plus />\n </IconAction>\n <RowActionsMenu row={row} builder={builder} clipboard={clipboard} />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\n/** Right-aligned keyboard-shortcut hint in a menu item — informational only; the\n * shortcut fires globally via the clipboard listeners regardless of the menu. */\nfunction MenuShortcut({ children }: { children: React.ReactNode }) {\n return <span className=\"ml-auto pl-6 text-xs tracking-widest text-muted-foreground\">{children}</span>\n}\n\n/**\n * The per-row \"•••\" action menu (EC-211). Copy/Paste on every row; Cut, Move,\n * Duplicate, Delete for non-root rows. Collapsing the old inline icon cluster\n * (up to 8 buttons, cropping on the 200–240px panel) into a menu changes no\n * behavior — same clipboard fragment + dispatch paths, same Cmd/Ctrl shortcuts.\n * Delete stays a one-click (undoable) destructive item, consistent with the rest\n * of the studio.\n */\nfunction RowActionsMenu({\n row,\n builder,\n clipboard,\n}: {\n row: { id: string; isRoot: boolean; isFirst: boolean; isLast: boolean }\n builder: ReturnType<typeof useBuilder>\n clipboard: ReturnType<typeof useStudioClipboard>\n}) {\n // EC-282 in-canvas reuse gestures, present only where a reuse capability is\n // configured (and the row is eligible). Null outside the composition editor.\n const reuse = useReuse()\n const canMakeReusable = !row.isRoot && (reuse?.canMakeReusable(row.id) ?? false)\n const canDetach = reuse?.canDetach(row.id) ?? false\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.pageTree.moreActions')}\n title={translate('builder.pageTree.moreActions')}\n className=\"size-6\"\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => clipboard.copyNode(row.id)}>\n <ClipboardCopy /> {translate('common.copy')} <MenuShortcut>⌘C</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void clipboard.pasteFromClipboard(row.id)}>\n <ClipboardPaste /> {translate('builder.clipboard.paste')} <MenuShortcut>⌘V</MenuShortcut>\n </DropdownMenuItem>\n {!row.isRoot ? (\n <>\n <DropdownMenuItem onClick={() => clipboard.cutNode(row.id)}>\n <ClipboardX /> {translate('builder.clipboard.cut')} <MenuShortcut>⌘X</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n disabled={row.isFirst}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: -1 })}\n >\n <ChevronUp /> {translate('builder.pageTree.moveUp')}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={row.isLast}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 })}\n >\n <ChevronDown /> {translate('builder.pageTree.moveDown')}\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => builder.dispatch({ type: 'duplicate', id: row.id })}>\n <Copy /> {translate('common.duplicate')}\n </DropdownMenuItem>\n {canMakeReusable || canDetach ? <DropdownMenuSeparator /> : null}\n {canMakeReusable ? (\n <DropdownMenuItem onClick={() => reuse?.startMakeReusable(row.id)}>\n <Boxes /> {translate('builder.reuse.makeReusable')}\n </DropdownMenuItem>\n ) : null}\n {canDetach ? (\n <DropdownMenuItem onClick={() => reuse?.detach(row.id)}>\n <Unlink /> {translate('builder.reuse.detachToCopy')}\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuSeparator />\n <DropdownMenuItem\n variant=\"destructive\"\n onClick={() => builder.dispatch({ type: 'remove', id: row.id })}\n >\n <Trash2 /> {translate('common.delete')}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction IconAction({\n label,\n onClick,\n disabled,\n destructive,\n children,\n}: {\n label: string\n onClick: () => void\n disabled?: boolean\n destructive?: boolean\n children: React.ReactNode\n}) {\n return (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className={cn('size-6', destructive && 'hover:text-destructive')}\n >\n {children}\n </Button>\n )\n}\n"]}
1
+ {"version":3,"file":"page-tree.js","sourceRoot":"","sources":["../../../src/components/builder/page-tree.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Boxes,\n ChevronUp,\n ChevronDown,\n ClipboardCopy,\n ClipboardPaste,\n ClipboardX,\n Copy,\n EyeOff,\n Filter,\n MoreHorizontal,\n Plus,\n Trash2,\n Unlink,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { Condition, ComponentNode } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPointer,\n manifestRules,\n resolveDrop,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { DropIndicator, DropPlan, DropPosition } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\nimport { useData } from '@elytracms/studio/lib/data'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useCollab } from '../collab/collab-context'\nimport { useBuilder } from './builder-store'\nimport { attachClipboardListeners, useStudioClipboard } from './clipboard-actions'\nimport { attachDeleteKeyListener } from './delete-key'\nimport { nodeDisplayTitle } from './node-title'\nimport { useReuse } from './reuse-context'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\n\ninterface TreeRow {\n id: string\n componentId: string\n /** The node itself — EC-407 derives the instance title from its prop values. */\n node: ComponentNode\n depth: number\n isRoot: boolean\n isFirst: boolean\n isLast: boolean\n condition?: Condition\n}\n\nfunction flatten(\n node: ComponentNode,\n depth: number,\n rows: TreeRow[],\n meta: { isFirst: boolean; isLast: boolean; isRoot: boolean },\n): void {\n rows.push({ id: node.id, componentId: node.componentId, node, depth, ...meta, condition: node.condition })\n for (const children of Object.values(node.slots ?? {})) {\n children.forEach((child, i) =>\n flatten(child, depth + 1, rows, {\n isFirst: i === 0,\n isLast: i === children.length - 1,\n isRoot: false,\n }),\n )\n }\n}\n\n/** Tailwind classes per drop indicator state (EC-160/EC-162) — rejection is visible. */\nconst ROW_DROP_CLASSES: Record<Exclude<DropIndicator, null>, string> = {\n into: 'ring-1 ring-inset ring-emerald-400 bg-emerald-400/10',\n before: 'shadow-[inset_0_2px_0_0_#38bdf8]',\n after: 'shadow-[inset_0_-2px_0_0_#38bdf8]',\n // Wrap-into-columns gesture (EC-162): the tree can execute wrap plans, the\n // bands themselves are a canvas gesture (rows have no spatial \"beside\").\n left: 'shadow-[inset_2px_0_0_0_#a78bfa] bg-violet-400/10',\n right: 'shadow-[inset_-2px_0_0_0_#a78bfa] bg-violet-400/10',\n blocked: 'ring-1 ring-inset ring-red-500/80 bg-red-500/10 cursor-not-allowed',\n}\n\nexport function PageTree({ onAddChild }: { onAddChild: (parentId: string) => void }) {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const data = useData()\n const collab = useCollab()\n const uiLocale = useUiLocale()\n const rows: TreeRow[] = []\n flatten(builder.present, 0, rows, { isFirst: true, isLast: true, isRoot: true })\n\n // Copy/cut/paste (EC-175): keyboard shortcuts for the whole studio window\n // (the canvas iframe wires its own document) plus per-row affordances. The\n // shared listener keeps shortcuts inert in form fields/inline editors, and\n // the published preview is read-only.\n const clipboard = useStudioClipboard()\n const readOnly = collab.previewMode === 'published'\n const selectedIdRef = useRef(builder.selectedId)\n selectedIdRef.current = builder.selectedId\n useEffect(() => {\n if (readOnly) return\n return attachClipboardListeners(window, {\n clipboard,\n getSelectedId: () => selectedIdRef.current,\n getSelection: () => window.getSelection(),\n })\n }, [clipboard, readOnly])\n\n // Delete/Backspace removes the selected node from the tree side too (EC-404),\n // beside the clipboard shortcuts. Inert in the read-only published preview and\n // while focus is in a form field / inline editor (isEditingContext).\n const { dispatch } = builder\n const rootId = builder.present.id\n useEffect(() => {\n if (readOnly) return\n return attachDeleteKeyListener(window, {\n getSelectedId: () => selectedIdRef.current,\n remove: (id) => dispatch({ type: 'remove', id }),\n getRootId: () => rootId,\n })\n }, [readOnly, rootId, dispatch])\n\n // Drag & drop (EC-160): eligibility comes from the same pure rule set the\n // canvas uses (`lib/dnd/drop-rules`); the tree only renders the resolution.\n // One eligible drop dispatches exactly one move/insert action.\n // The current plan lives in a ref: `drop` fires synchronously after the\n // last `dragover`, before React flushes indicator state.\n const dropPlanRef = useRef<DropPlan | null>(null)\n const [drop, setDrop] = useState<{ rowId: string; indicator: DropIndicator } | null>(null)\n const clearDrop = () => {\n dropPlanRef.current = null\n setDrop(null)\n }\n useEffect(() => onDragSessionEnd(() => clearDrop()), [])\n // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a\n // Section's `body`) are drop targets with their `allow` leash, like raw slots.\n const getManifest = (id: string) => manifestRules(studio.registry.getManifest(id))\n\n function handleRowDragStart(row: TreeRow, e: React.DragEvent) {\n if (row.isRoot) {\n e.preventDefault()\n return\n }\n beginDrag(\n {\n subject: { kind: 'node', nodeId: row.id, componentId: row.componentId },\n source: 'tree',\n label: shortId(row.componentId),\n },\n e.dataTransfer,\n )\n }\n\n function handleRowDragOver(row: TreeRow, e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n let position: DropPosition = 'into'\n if (!row.isRoot) {\n const rect = e.currentTarget.getBoundingClientRect()\n // The hit-band axis follows the parent container's flow (EC-162):\n // children of horizontal Stacks/Grids reorder along the row's x axis.\n const flow = containerFlowAxis(getParentInfo(builder.present, row.id)?.parent)\n const ratio =\n flow === 'horizontal'\n ? rect.width > 0\n ? (e.clientX - rect.left) / rect.width\n : 0.5\n : rect.height > 0\n ? (e.clientY - rect.top) / rect.height\n : 0.5\n position = dropPositionForPointer({\n ratio,\n nestable: canNestInto(getManifest, row.componentId),\n edges: true,\n })\n }\n const resolution = resolveDrop({\n root: builder.present,\n getManifest,\n subject: drag.subject,\n location: { nodeId: row.id, position },\n })\n const indicator = dropIndicatorFor(resolution, position)\n dropPlanRef.current = resolution.eligible ? resolution : null\n setDrop({ rowId: row.id, indicator })\n if (resolution.eligible) {\n e.preventDefault()\n e.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n }\n\n function handleRowDrop(e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlanRef.current\n clearDrop()\n if (!plan) return\n e.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n builder.dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162: shared plan vocabulary — the tree executes wrap plans too.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n builder.dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n builder.dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n function handleRowDragEnd() {\n clearDrop()\n endDrag('cancelled')\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"2\" className=\"h-10 shrink-0 border-b border-border px-3\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.pageTree.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {rows.length}\n </Text>\n </Row>\n <div\n className=\"min-h-0 flex-1 overflow-auto p-1.5\"\n onMouseLeave={() => builder.dispatch({ type: 'hover', id: null })}\n onDragLeave={(e) => {\n // Clear the indicator when the drag exits the tree panel.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) clearDrop()\n }}\n >\n {rows.map((row) => {\n const manifest = studio.registry.getManifest(row.componentId)\n const title = nodeDisplayTitle(manifest, row.node, uiLocale)\n const selected = row.id === builder.selectedId\n const hovered = row.id === builder.hoveredId\n // EC-088: indicate condition-gated nodes; flag the currently-hidden ones.\n let condState: 'none' | 'visible' | 'hidden' = 'none'\n if (row.condition) {\n const r = data.resolveCondition(row.condition)\n condState = r.kind === 'condition' && r.pass ? 'visible' : 'hidden'\n }\n return (\n <div\n key={row.id}\n onMouseEnter={() => builder.dispatch({ type: 'hover', id: row.id })}\n draggable={!row.isRoot}\n onDragStart={(e) => handleRowDragStart(row, e)}\n onDragOver={(e) => handleRowDragOver(row, e)}\n onDrop={handleRowDrop}\n onDragEnd={handleRowDragEnd}\n data-tree-row={row.id}\n data-tree-drop={drop?.rowId === row.id ? (drop.indicator ?? undefined) : undefined}\n className={cn(\n 'group flex items-center rounded-md pr-1 text-left text-xs transition-colors',\n selected\n ? 'bg-accent text-accent-foreground'\n : hovered\n ? 'bg-muted text-foreground'\n : 'text-muted-foreground',\n drop?.rowId === row.id && drop.indicator\n ? ROW_DROP_CLASSES[drop.indicator]\n : undefined,\n )}\n >\n <button\n type=\"button\"\n onClick={() => builder.dispatch({ type: 'select', id: row.id })}\n title={row.componentId}\n className=\"flex min-w-0 flex-1 items-center gap-1.5 py-1\"\n style={{ paddingLeft: `${6 + row.depth * 14}px` }}\n >\n {/* EC-407: instance title (its own text content) is primary; the\n localized component name is secondary context. The raw mono id\n left the row — it stays in the `title=` above + inspector header. */}\n <span className={cn('truncate', condState === 'hidden' && 'line-through opacity-70')}>\n {title.primary}\n </span>\n {title.secondary ? (\n <span className=\"truncate text-muted-foreground/70\">{title.secondary}</span>\n ) : null}\n {condState === 'hidden' ? (\n <EyeOff\n className=\"size-3 shrink-0 text-warning\"\n aria-label={translate('builder.pageTree.hiddenByCondition')}\n />\n ) : condState === 'visible' ? (\n <Filter\n className=\"size-3 shrink-0 text-muted-foreground/60\"\n aria-label={translate('builder.pageTree.conditional')}\n />\n ) : null}\n </button>\n\n <div\n className={cn(\n 'flex shrink-0 items-center gap-0.5',\n selected\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',\n )}\n >\n {/* Add child stays inline — the one constructive, high-frequency\n action. Everything else collapses into the ••• menu so labels\n stop cropping on the 200–240px panel (EC-211). */}\n <IconAction label={translate('builder.pageTree.addChild')} onClick={() => onAddChild(row.id)}>\n <Plus />\n </IconAction>\n <RowActionsMenu row={row} builder={builder} clipboard={clipboard} />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\n/** Right-aligned keyboard-shortcut hint in a menu item — informational only; the\n * shortcut fires globally via the clipboard listeners regardless of the menu. */\nfunction MenuShortcut({ children }: { children: React.ReactNode }) {\n return <span className=\"ml-auto pl-6 text-xs tracking-widest text-muted-foreground\">{children}</span>\n}\n\n/**\n * The per-row \"•••\" action menu (EC-211). Copy/Paste on every row; Cut, Move,\n * Duplicate, Delete for non-root rows. Collapsing the old inline icon cluster\n * (up to 8 buttons, cropping on the 200–240px panel) into a menu changes no\n * behavior — same clipboard fragment + dispatch paths, same Cmd/Ctrl shortcuts.\n * Delete stays a one-click (undoable) destructive item, consistent with the rest\n * of the studio.\n */\nfunction RowActionsMenu({\n row,\n builder,\n clipboard,\n}: {\n row: { id: string; isRoot: boolean; isFirst: boolean; isLast: boolean }\n builder: ReturnType<typeof useBuilder>\n clipboard: ReturnType<typeof useStudioClipboard>\n}) {\n // EC-282 in-canvas reuse gestures, present only where a reuse capability is\n // configured (and the row is eligible). Null outside the composition editor.\n const reuse = useReuse()\n const canMakeReusable = !row.isRoot && (reuse?.canMakeReusable(row.id) ?? false)\n const canDetach = reuse?.canDetach(row.id) ?? false\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.pageTree.moreActions')}\n title={translate('builder.pageTree.moreActions')}\n className=\"size-6\"\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => clipboard.copyNode(row.id)}>\n <ClipboardCopy /> {translate('common.copy')} <MenuShortcut>⌘C</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void clipboard.pasteFromClipboard(row.id)}>\n <ClipboardPaste /> {translate('builder.clipboard.paste')} <MenuShortcut>⌘V</MenuShortcut>\n </DropdownMenuItem>\n {!row.isRoot ? (\n <>\n <DropdownMenuItem onClick={() => clipboard.cutNode(row.id)}>\n <ClipboardX /> {translate('builder.clipboard.cut')} <MenuShortcut>⌘X</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n disabled={row.isFirst}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: -1 })}\n >\n <ChevronUp /> {translate('builder.pageTree.moveUp')}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={row.isLast}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 })}\n >\n <ChevronDown /> {translate('builder.pageTree.moveDown')}\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => builder.dispatch({ type: 'duplicate', id: row.id })}>\n <Copy /> {translate('common.duplicate')}\n </DropdownMenuItem>\n {canMakeReusable || canDetach ? <DropdownMenuSeparator /> : null}\n {canMakeReusable ? (\n <DropdownMenuItem onClick={() => reuse?.startMakeReusable(row.id)}>\n <Boxes /> {translate('builder.reuse.makeReusable')}\n </DropdownMenuItem>\n ) : null}\n {canDetach ? (\n <DropdownMenuItem onClick={() => reuse?.detach(row.id)}>\n <Unlink /> {translate('builder.reuse.detachToCopy')}\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuSeparator />\n <DropdownMenuItem\n variant=\"destructive\"\n onClick={() => builder.dispatch({ type: 'remove', id: row.id })}\n >\n <Trash2 /> {translate('common.delete')}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction IconAction({\n label,\n onClick,\n disabled,\n destructive,\n children,\n}: {\n label: string\n onClick: () => void\n disabled?: boolean\n destructive?: boolean\n children: React.ReactNode\n}) {\n return (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className={cn('size-6', destructive && 'hover:text-destructive')}\n >\n {children}\n </Button>\n )\n}\n"]}
@@ -0,0 +1,52 @@
1
+ import type { ComponentManifest } from '@elytracms/core/component-registry';
2
+ /**
3
+ * EC-408 — the click-resolution core: given a click inside a selected node's
4
+ * wrapper, which prop (if any) does the clicked element map to, and should the
5
+ * inspector focus that prop's control?
6
+ *
7
+ * Pure and framework-free (the `findInlineTextHost` house pattern): a minimal
8
+ * ancestor-walk over an injected element face, so the three rules are unit-pinned
9
+ * without a DOM:
10
+ * 1. A `data-ec-prop` marker INSIDE the selected node's wrapper wins.
11
+ * 2. A marker OUTSIDE the wrapper (belonging to a parent node's render) is
12
+ * ignored — the walk stops at the wrapper and never climbs past it.
13
+ * 3. A marker inside a NESTED node's wrapper is ignored — the nested
14
+ * `data-ec-node-id` scopes out its own markers (that node is what a click
15
+ * there selects, resolved by its own wrapper).
16
+ * The inline-editable deferral (rule from scope 3) is a separate manifest check:
17
+ * an `inlineEditable` prop keeps inline editing (double-click) as its primary
18
+ * interaction, so a single click never steals focus to the inspector field.
19
+ */
20
+ /** The minimal element face the ancestor walk needs (a real `Element` satisfies it). */
21
+ export interface PropClickElement {
22
+ getAttribute(name: string): string | null;
23
+ readonly parentElement: PropClickElement | null;
24
+ }
25
+ /**
26
+ * The `data-ec-prop` name at `target` that belongs to `wrapper` (the selected
27
+ * node's editable wrapper), or `null`. Walks ancestors from `target` up to
28
+ * `wrapper`: returns the first `data-ec-prop` found, but stops (returns `null`)
29
+ * the moment it crosses into a NESTED node wrapper before finding one, and never
30
+ * climbs above `wrapper`.
31
+ */
32
+ export declare function propMarkerName(target: PropClickElement | null, wrapper: PropClickElement): string | null;
33
+ /** The outcome of resolving a sub-part click. */
34
+ export interface PropClickResolution {
35
+ /** The prop the clicked element maps to, or `null` (unannotated / marker outside). */
36
+ propName: string | null;
37
+ /**
38
+ * Whether to focus that prop's inspector control. `false` when there is no
39
+ * marker, or when the prop is inline-editable (inline editing stays primary).
40
+ */
41
+ focusField: boolean;
42
+ }
43
+ /**
44
+ * Resolve what a sub-part click should do: find the node-scoped `data-ec-prop`
45
+ * marker, then decide whether to focus its inspector control. Inline-editable
46
+ * props defer to inline editing; everything else focuses the field (scope 3).
47
+ */
48
+ export declare function resolvePropClick(args: {
49
+ target: PropClickElement | null;
50
+ wrapper: PropClickElement;
51
+ manifest: ComponentManifest | undefined;
52
+ }): PropClickResolution;
@@ -0,0 +1,38 @@
1
+ import { inlineEditablePropNames } from '@elytracms/core/component-registry';
2
+ /**
3
+ * The `data-ec-prop` name at `target` that belongs to `wrapper` (the selected
4
+ * node's editable wrapper), or `null`. Walks ancestors from `target` up to
5
+ * `wrapper`: returns the first `data-ec-prop` found, but stops (returns `null`)
6
+ * the moment it crosses into a NESTED node wrapper before finding one, and never
7
+ * climbs above `wrapper`.
8
+ */
9
+ export function propMarkerName(target, wrapper) {
10
+ let el = target;
11
+ while (el) {
12
+ // A nested node wrapper below the selected one scopes out its own markers.
13
+ if (el !== wrapper && el.getAttribute('data-ec-node-id') !== null)
14
+ return null;
15
+ const prop = el.getAttribute('data-ec-prop');
16
+ if (prop !== null)
17
+ return prop;
18
+ if (el === wrapper)
19
+ return null;
20
+ el = el.parentElement;
21
+ }
22
+ return null;
23
+ }
24
+ /**
25
+ * Resolve what a sub-part click should do: find the node-scoped `data-ec-prop`
26
+ * marker, then decide whether to focus its inspector control. Inline-editable
27
+ * props defer to inline editing; everything else focuses the field (scope 3).
28
+ */
29
+ export function resolvePropClick(args) {
30
+ const propName = propMarkerName(args.target, args.wrapper);
31
+ if (propName === null)
32
+ return { propName: null, focusField: false };
33
+ const inlineEditable = args.manifest
34
+ ? inlineEditablePropNames(args.manifest).includes(propName)
35
+ : false;
36
+ return { propName, focusField: !inlineEditable };
37
+ }
38
+ //# sourceMappingURL=prop-selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prop-selection.js","sourceRoot":"","sources":["../../../src/components/builder/prop-selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAA;AA4B5E;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA+B,EAC/B,OAAyB;IAEzB,IAAI,EAAE,GAA4B,MAAM,CAAA;IACxC,OAAO,EAAE,EAAE,CAAC;QACV,2EAA2E;QAC3E,IAAI,EAAE,KAAK,OAAO,IAAI,EAAE,CAAC,YAAY,CAAC,iBAAiB,CAAC,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC9E,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,CAAC,CAAA;QAC5C,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC9B,IAAI,EAAE,KAAK,OAAO;YAAE,OAAO,IAAI,CAAA;QAC/B,EAAE,GAAG,EAAE,CAAC,aAAa,CAAA;IACvB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAaD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAIhC;IACC,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;IAC1D,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAA;IACnE,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ;QAClC,CAAC,CAAC,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC3D,CAAC,CAAC,KAAK,CAAA;IACT,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,cAAc,EAAE,CAAA;AAClD,CAAC","sourcesContent":["import { inlineEditablePropNames } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n/**\n * EC-408 — the click-resolution core: given a click inside a selected node's\n * wrapper, which prop (if any) does the clicked element map to, and should the\n * inspector focus that prop's control?\n *\n * Pure and framework-free (the `findInlineTextHost` house pattern): a minimal\n * ancestor-walk over an injected element face, so the three rules are unit-pinned\n * without a DOM:\n * 1. A `data-ec-prop` marker INSIDE the selected node's wrapper wins.\n * 2. A marker OUTSIDE the wrapper (belonging to a parent node's render) is\n * ignored — the walk stops at the wrapper and never climbs past it.\n * 3. A marker inside a NESTED node's wrapper is ignored — the nested\n * `data-ec-node-id` scopes out its own markers (that node is what a click\n * there selects, resolved by its own wrapper).\n * The inline-editable deferral (rule from scope 3) is a separate manifest check:\n * an `inlineEditable` prop keeps inline editing (double-click) as its primary\n * interaction, so a single click never steals focus to the inspector field.\n */\n\n/** The minimal element face the ancestor walk needs (a real `Element` satisfies it). */\nexport interface PropClickElement {\n getAttribute(name: string): string | null\n readonly parentElement: PropClickElement | null\n}\n\n/**\n * The `data-ec-prop` name at `target` that belongs to `wrapper` (the selected\n * node's editable wrapper), or `null`. Walks ancestors from `target` up to\n * `wrapper`: returns the first `data-ec-prop` found, but stops (returns `null`)\n * the moment it crosses into a NESTED node wrapper before finding one, and never\n * climbs above `wrapper`.\n */\nexport function propMarkerName(\n target: PropClickElement | null,\n wrapper: PropClickElement,\n): string | null {\n let el: PropClickElement | null = target\n while (el) {\n // A nested node wrapper below the selected one scopes out its own markers.\n if (el !== wrapper && el.getAttribute('data-ec-node-id') !== null) return null\n const prop = el.getAttribute('data-ec-prop')\n if (prop !== null) return prop\n if (el === wrapper) return null\n el = el.parentElement\n }\n return null\n}\n\n/** The outcome of resolving a sub-part click. */\nexport interface PropClickResolution {\n /** The prop the clicked element maps to, or `null` (unannotated / marker outside). */\n propName: string | null\n /**\n * Whether to focus that prop's inspector control. `false` when there is no\n * marker, or when the prop is inline-editable (inline editing stays primary).\n */\n focusField: boolean\n}\n\n/**\n * Resolve what a sub-part click should do: find the node-scoped `data-ec-prop`\n * marker, then decide whether to focus its inspector control. Inline-editable\n * props defer to inline editing; everything else focuses the field (scope 3).\n */\nexport function resolvePropClick(args: {\n target: PropClickElement | null\n wrapper: PropClickElement\n manifest: ComponentManifest | undefined\n}): PropClickResolution {\n const propName = propMarkerName(args.target, args.wrapper)\n if (propName === null) return { propName: null, focusField: false }\n const inlineEditable = args.manifest\n ? inlineEditablePropNames(args.manifest).includes(propName)\n : false\n return { propName, focusField: !inlineEditable }\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * EC-408 — the field↔component selection-sync channel. A small shared context
4
+ * (the `InsertAffordanceProvider` / `ReuseProvider` pattern) bridging the three
5
+ * surfaces that live in one editor tree but reach each other through nothing
6
+ * else: the in-iframe canvas click listener, the property inspector's prop
7
+ * controls, and the portaled selection overlay.
8
+ *
9
+ * Two directions ride it:
10
+ * - FORWARD (canvas → inspector): a sub-part click issues a one-shot
11
+ * {@link PropSyncApi.requestFocus}; the matching inspector control scrolls
12
+ * into view and takes focus. A monotonic `nonce` distinguishes repeat
13
+ * requests for the same prop so re-clicking re-focuses.
14
+ * - REVERSE (inspector → overlay): a prop control's focus sets
15
+ * {@link PropSyncApi.highlighted}; the overlay draws a quiet highlight on
16
+ * the matching `[data-ec-prop]` element. Blur clears it. No marker → the
17
+ * overlay simply measures nothing and draws nothing.
18
+ *
19
+ * A canvas mounted WITHOUT this provider (component preview) reads `null` and
20
+ * both directions are inert — today's node-level behavior, unchanged.
21
+ */
22
+ /** A prop identified by its owning node. */
23
+ export interface PropPath {
24
+ nodeId: string;
25
+ propName: string;
26
+ }
27
+ /** A one-shot focus request (nonce distinguishes repeats for the same prop). */
28
+ export interface PropFocusRequest extends PropPath {
29
+ nonce: number;
30
+ }
31
+ export interface PropSyncApi {
32
+ /** REVERSE: the prop control currently focused, or `null`. */
33
+ highlighted: PropPath | null;
34
+ /** Set/clear the focused prop (drives the overlay highlight). */
35
+ setHighlighted(path: PropPath | null): void;
36
+ /** FORWARD: the pending focus request, or `null`. */
37
+ focusRequest: PropFocusRequest | null;
38
+ /** Ask the inspector to scroll to + focus the given prop's control. */
39
+ requestFocus(path: PropPath): void;
40
+ }
41
+ /**
42
+ * Owns the shared sync state and exposes the api to both directions. Mounted
43
+ * around the canvas + inspector (the composition-field editor shell).
44
+ */
45
+ export declare function PropSyncProvider({ children }: {
46
+ children: ReactNode;
47
+ }): import("react").JSX.Element;
48
+ /** The sync api, or `null` outside a provider (component preview). */
49
+ export declare function usePropSync(): PropSyncApi | null;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
3
+ const PropSyncContext = createContext(null);
4
+ /**
5
+ * Owns the shared sync state and exposes the api to both directions. Mounted
6
+ * around the canvas + inspector (the composition-field editor shell).
7
+ */
8
+ export function PropSyncProvider({ children }) {
9
+ const [highlighted, setHighlighted] = useState(null);
10
+ const [focusRequest, setFocusRequest] = useState(null);
11
+ const nonceRef = useRef(0);
12
+ const requestFocus = useCallback((path) => {
13
+ nonceRef.current += 1;
14
+ setFocusRequest({ ...path, nonce: nonceRef.current });
15
+ }, []);
16
+ const api = useMemo(() => ({ highlighted, setHighlighted, focusRequest, requestFocus }), [highlighted, focusRequest, requestFocus]);
17
+ return _jsx(PropSyncContext.Provider, { value: api, children: children });
18
+ }
19
+ /** The sync api, or `null` outside a provider (component preview). */
20
+ export function usePropSync() {
21
+ return useContext(PropSyncContext);
22
+ }
23
+ //# sourceMappingURL=prop-sync-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prop-sync-context.js","sourceRoot":"","sources":["../../../src/components/builder/prop-sync-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AA8CzF,MAAM,eAAe,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAA;AAE/D;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,QAAQ,EAA2B;IACpE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAkB,IAAI,CAAC,CAAA;IACrE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAA0B,IAAI,CAAC,CAAA;IAC/E,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAE1B,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,IAAc,EAAE,EAAE;QAClD,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAA;QACrB,eAAe,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACvD,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC,EACnE,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,CAC1C,CAAA;IAED,OAAO,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,GAAG,YAAG,QAAQ,GAA4B,CAAA;AACpF,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC,eAAe,CAAC,CAAA;AACpC,CAAC","sourcesContent":["import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\n\n/**\n * EC-408 — the field↔component selection-sync channel. A small shared context\n * (the `InsertAffordanceProvider` / `ReuseProvider` pattern) bridging the three\n * surfaces that live in one editor tree but reach each other through nothing\n * else: the in-iframe canvas click listener, the property inspector's prop\n * controls, and the portaled selection overlay.\n *\n * Two directions ride it:\n * - FORWARD (canvas → inspector): a sub-part click issues a one-shot\n * {@link PropSyncApi.requestFocus}; the matching inspector control scrolls\n * into view and takes focus. A monotonic `nonce` distinguishes repeat\n * requests for the same prop so re-clicking re-focuses.\n * - REVERSE (inspector → overlay): a prop control's focus sets\n * {@link PropSyncApi.highlighted}; the overlay draws a quiet highlight on\n * the matching `[data-ec-prop]` element. Blur clears it. No marker → the\n * overlay simply measures nothing and draws nothing.\n *\n * A canvas mounted WITHOUT this provider (component preview) reads `null` and\n * both directions are inert — today's node-level behavior, unchanged.\n */\n\n/** A prop identified by its owning node. */\nexport interface PropPath {\n nodeId: string\n propName: string\n}\n\n/** A one-shot focus request (nonce distinguishes repeats for the same prop). */\nexport interface PropFocusRequest extends PropPath {\n nonce: number\n}\n\nexport interface PropSyncApi {\n /** REVERSE: the prop control currently focused, or `null`. */\n highlighted: PropPath | null\n /** Set/clear the focused prop (drives the overlay highlight). */\n setHighlighted(path: PropPath | null): void\n /** FORWARD: the pending focus request, or `null`. */\n focusRequest: PropFocusRequest | null\n /** Ask the inspector to scroll to + focus the given prop's control. */\n requestFocus(path: PropPath): void\n}\n\nconst PropSyncContext = createContext<PropSyncApi | null>(null)\n\n/**\n * Owns the shared sync state and exposes the api to both directions. Mounted\n * around the canvas + inspector (the composition-field editor shell).\n */\nexport function PropSyncProvider({ children }: { children: ReactNode }) {\n const [highlighted, setHighlighted] = useState<PropPath | null>(null)\n const [focusRequest, setFocusRequest] = useState<PropFocusRequest | null>(null)\n const nonceRef = useRef(0)\n\n const requestFocus = useCallback((path: PropPath) => {\n nonceRef.current += 1\n setFocusRequest({ ...path, nonce: nonceRef.current })\n }, [])\n\n const api = useMemo<PropSyncApi>(\n () => ({ highlighted, setHighlighted, focusRequest, requestFocus }),\n [highlighted, focusRequest, requestFocus],\n )\n\n return <PropSyncContext.Provider value={api}>{children}</PropSyncContext.Provider>\n}\n\n/** The sync api, or `null` outside a provider (component preview). */\nexport function usePropSync(): PropSyncApi | null {\n return useContext(PropSyncContext)\n}\n"]}
@@ -1,4 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { Lock } from 'lucide-react';
2
4
  import { staticProp } from '@elytracms/core/project-graph';
3
5
  import { effectivePropValues, manifestSlots } from '@elytracms/core/component-registry';
4
6
  import { isFieldHidden } from '@elytracms/core/cms-core';
@@ -10,6 +12,7 @@ import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
10
12
  import { PropValueControl } from '../cms/prop-fields.js';
11
13
  import { useBuilder } from './builder-store.js';
12
14
  import { LayoutSection, layoutManagedPropNames } from './layout-inspector.js';
15
+ import { usePropSync } from './prop-sync-context.js';
13
16
  export function PropertyInspector() {
14
17
  const builder = useBuilder();
15
18
  const studio = getStudioRegistry();
@@ -55,10 +58,30 @@ function SettingsTab({ node, manifest, }) {
55
58
  }
56
59
  function PropField({ node, name, spec }) {
57
60
  const builder = useBuilder();
61
+ const sync = usePropSync();
62
+ const anchorRef = useRef(null);
58
63
  const value = node.props?.[name];
59
64
  const id = `prop-${node.id}-${name}`;
60
65
  // Coalesce rapid edits to the same prop into one undo step (EC-190).
61
66
  const coalesceKey = `${node.id}:${name}`;
62
- return (_jsx(Field, { label: localizedLabel(spec.form?.label, name), htmlFor: id, hint: localizedLabel(spec.form?.description) || undefined, children: _jsx(PropValueControl, { id: id, name: name, spec: spec, value: value, onSet: (v) => builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(v), coalesceKey }), onClear: () => builder.dispatch({ type: 'removeProp', id: node.id, name }) }) }));
67
+ // EC-408 FORWARD: a sub-part click that resolved to THIS prop scrolls its
68
+ // control into view and focuses the first focusable element. Keyed on the
69
+ // request object (a fresh nonce every request) so re-clicking re-focuses.
70
+ const request = sync?.focusRequest;
71
+ useEffect(() => {
72
+ if (!request || request.nodeId !== node.id || request.propName !== name)
73
+ return;
74
+ const anchor = anchorRef.current;
75
+ if (!anchor)
76
+ return;
77
+ anchor.scrollIntoView({ block: 'nearest' });
78
+ anchor
79
+ .querySelector('input, textarea, select, button, [contenteditable="true"], [tabindex]')
80
+ ?.focus();
81
+ }, [request, node.id, name]);
82
+ return (_jsx("div", { ref: anchorRef, "data-ec-prop-anchor": name, onFocus: () => sync?.setHighlighted({ nodeId: node.id, propName: name }), onBlur: (e) => {
83
+ if (!e.currentTarget.contains(e.relatedTarget))
84
+ sync?.setHighlighted(null);
85
+ }, children: _jsx(Field, { label: spec.form?.readOnly === true ? (_jsxs(Row, { gap: "2", children: [_jsx("span", { children: localizedLabel(spec.form?.label, name) }), _jsxs(Badge, { variant: "outline", children: [_jsx(Lock, { className: "size-3" }), " ", translate('cms.editor.readOnlyBadge')] })] })) : (localizedLabel(spec.form?.label, name)), htmlFor: id, hint: localizedLabel(spec.form?.description) || undefined, children: _jsx(PropValueControl, { id: id, name: name, spec: spec, value: value, onSet: (v) => builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(v), coalesceKey }), onClear: () => builder.dispatch({ type: 'removeProp', id: node.id, name }) }) }) }));
63
86
  }
64
87
  //# sourceMappingURL=property-inspector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"property-inspector.js","sourceRoot":"","sources":["../../../src/components/builder/property-inspector.tsx"],"names":[],"mappings":"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","sourcesContent":["import { staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport { effectivePropValues, manifestSlots } from '@elytracms/core/component-registry'\nimport { isFieldHidden } from '@elytracms/core/cms-core'\nimport type { FieldConditionContext, FieldDef } from '@elytracms/core/cms-core'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n Badge,\n Code,\n Field,\n InspectorSection,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { humanizeName } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { PropValueControl } from '../cms/prop-fields'\nimport { useBuilder } from './builder-store'\nimport { LayoutSection, layoutManagedPropNames } from './layout-inspector'\n\nexport function PropertyInspector() {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const node = builder.selectedNode\n\n if (!node) {\n return (\n <div className=\"grid h-full place-items-center p-6 text-center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('builder.inspector.emptySelection')}\n </Text>\n </div>\n )\n }\n\n const manifest = studio.registry.getManifest(node.componentId)\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.inspector.title')}\n </Text>\n <Spacer />\n <Code tone=\"muted\" title={node.componentId}>\n {componentDisplayName(node.componentId, manifest)}\n </Code>\n </PanelHeader>\n\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <SettingsTab node={node} manifest={manifest} />\n </div>\n </div>\n )\n}\n\n/**\n * The prop rows the inspector renders for a node (EC-393). Manifest props MINUS:\n * layout-managed props (they render in the Layout section, EC-162); `blocks` props\n * (they are child regions shown in Slots, EC-190); and any prop hidden RIGHT NOW by\n * its `form.hidden` — static `true`, or a predicate over the SIBLING prop values.\n *\n * The predicate's context is the component instance's EFFECTIVE prop values\n * (`effectivePropValues` — static values with manifest defaults filled), the prop\n * analogue of the document form's active-locale draft values (`isFieldHidden` there).\n * So a conditional row shows/hides live as a sibling prop changes and unmounts\n * exactly like a document-form field. Pure (no store) so the behaviour is testable.\n */\nexport function visibleInspectorProps(\n manifest: ComponentManifest,\n node: ComponentNode,\n layoutManaged: ReadonlySet<string>,\n): [string, PropField][] {\n const ctx: FieldConditionContext = { values: effectivePropValues(manifest, node) }\n return Object.entries(manifest.props).filter(\n ([name, spec]) =>\n !layoutManaged.has(name) &&\n spec.type !== 'blocks' &&\n !isFieldHidden({ ...spec, name } as FieldDef, ctx),\n )\n}\n\n/** The full property surface: node identity, static props, condition, slots. */\nfunction SettingsTab({\n node,\n manifest,\n}: {\n node: ComponentNode\n manifest: ComponentManifest | undefined\n}) {\n // Container layout props render in the dedicated Layout section (EC-162),\n // not as a second set of generic controls.\n const layoutManaged = new Set(layoutManagedPropNames(node))\n // EC-190: `blocks` props are slots; EC-393: `form.hidden` props are omitted —\n // evaluated against the node's effective sibling prop values, live as they change.\n const props = manifest ? visibleInspectorProps(manifest, node, layoutManaged) : []\n const slots = manifest ? manifestSlots(manifest) : []\n\n return (\n <>\n {/* EC-192: the editor-facing inspector no longer renders the raw node id or\n namespaced component id (the persona promise). The component's human name\n is in the panel header (with the manifest id in its `title`); the canvas\n and tree provide selection. */}\n {/* Contextual layout inspector for Stack/Grid containers (EC-162). */}\n <LayoutSection node={node} />\n\n {!manifest ? (\n <div className=\"p-3\">\n <Badge variant=\"error\">{translate('builder.inspector.unknownComponent')}</Badge>\n <Text size=\"xs\" tone=\"muted\" className=\"mt-2\">\n {translate('builder.inspector.noManifest')}\n </Text>\n </div>\n ) : (\n <InspectorSection title={translate('builder.inspector.props')}>\n {props.length === 0 ? (\n <Text size=\"xs\" tone=\"subtle\">\n {translate('builder.inspector.noEditableProps')}\n </Text>\n ) : (\n <Stack gap=\"4\">\n {props.map(([name, spec]) => (\n // Key by node id so per-node local control state never leaks\n // across a selection change to a same-named prop.\n <PropField key={`${node.id}:${name}`} node={node} name={name} spec={spec} />\n ))}\n </Stack>\n )}\n </InspectorSection>\n )}\n\n {slots.length > 0 ? (\n <InspectorSection title={translate('builder.inspector.slots')}>\n <Stack gap=\"1\">\n {slots.map((slot) => {\n const count = node.slots?.[slot.name]?.length ?? 0\n const missing = slot.required && count === 0\n return (\n <Row key={slot.name} gap=\"2\">\n <Text size=\"xs\" title={slot.name}>\n {humanizeName(slot.name)}\n </Text>\n {slot.required ? (\n <Badge variant={missing ? 'error' : 'outline'}>{translate('common.required')}</Badge>\n ) : null}\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {count}\n </Text>\n </Row>\n )\n })}\n </Stack>\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-2\">\n {translate('builder.inspector.slotsHint')}\n </Text>\n </InspectorSection>\n ) : null}\n </>\n )\n}\n\nfunction PropField({ node, name, spec }: { node: ComponentNode; name: string; spec: PropField }) {\n const builder = useBuilder()\n const value = node.props?.[name]\n const id = `prop-${node.id}-${name}`\n // Coalesce rapid edits to the same prop into one undo step (EC-190).\n const coalesceKey = `${node.id}:${name}`\n\n return (\n // EC-393: honor `form.description` as the field hint, at parity with the document\n // form (`detail-editor.tsx` passes the same to its `Field`).\n <Field\n label={localizedLabel(spec.form?.label, name)}\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={value}\n onSet={(v) => builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(v), coalesceKey })}\n onClear={() => builder.dispatch({ type: 'removeProp', id: node.id, name })}\n />\n </Field>\n )\n}\n\n"]}
1
+ {"version":3,"file":"property-inspector.js","sourceRoot":"","sources":["../../../src/components/builder/property-inspector.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef } from 'react'\nimport { Lock } from 'lucide-react'\nimport { staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport { effectivePropValues, manifestSlots } from '@elytracms/core/component-registry'\nimport { isFieldHidden } from '@elytracms/core/cms-core'\nimport type { FieldConditionContext, FieldDef } from '@elytracms/core/cms-core'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n Badge,\n Code,\n Field,\n InspectorSection,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { humanizeName } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { PropValueControl } from '../cms/prop-fields'\nimport { useBuilder } from './builder-store'\nimport { LayoutSection, layoutManagedPropNames } from './layout-inspector'\nimport { usePropSync } from './prop-sync-context'\n\nexport function PropertyInspector() {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const node = builder.selectedNode\n\n if (!node) {\n return (\n <div className=\"grid h-full place-items-center p-6 text-center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('builder.inspector.emptySelection')}\n </Text>\n </div>\n )\n }\n\n const manifest = studio.registry.getManifest(node.componentId)\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.inspector.title')}\n </Text>\n <Spacer />\n <Code tone=\"muted\" title={node.componentId}>\n {componentDisplayName(node.componentId, manifest)}\n </Code>\n </PanelHeader>\n\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <SettingsTab node={node} manifest={manifest} />\n </div>\n </div>\n )\n}\n\n/**\n * The prop rows the inspector renders for a node (EC-393). Manifest props MINUS:\n * layout-managed props (they render in the Layout section, EC-162); `blocks` props\n * (they are child regions shown in Slots, EC-190); and any prop hidden RIGHT NOW by\n * its `form.hidden` — static `true`, or a predicate over the SIBLING prop values.\n *\n * The predicate's context is the component instance's EFFECTIVE prop values\n * (`effectivePropValues` — static values with manifest defaults filled), the prop\n * analogue of the document form's active-locale draft values (`isFieldHidden` there).\n * So a conditional row shows/hides live as a sibling prop changes and unmounts\n * exactly like a document-form field. Pure (no store) so the behaviour is testable.\n */\nexport function visibleInspectorProps(\n manifest: ComponentManifest,\n node: ComponentNode,\n layoutManaged: ReadonlySet<string>,\n): [string, PropField][] {\n const ctx: FieldConditionContext = { values: effectivePropValues(manifest, node) }\n return Object.entries(manifest.props).filter(\n ([name, spec]) =>\n !layoutManaged.has(name) &&\n spec.type !== 'blocks' &&\n !isFieldHidden({ ...spec, name } as FieldDef, ctx),\n )\n}\n\n/** The full property surface: node identity, static props, condition, slots. */\nfunction SettingsTab({\n node,\n manifest,\n}: {\n node: ComponentNode\n manifest: ComponentManifest | undefined\n}) {\n // Container layout props render in the dedicated Layout section (EC-162),\n // not as a second set of generic controls.\n const layoutManaged = new Set(layoutManagedPropNames(node))\n // EC-190: `blocks` props are slots; EC-393: `form.hidden` props are omitted —\n // evaluated against the node's effective sibling prop values, live as they change.\n const props = manifest ? visibleInspectorProps(manifest, node, layoutManaged) : []\n const slots = manifest ? manifestSlots(manifest) : []\n\n return (\n <>\n {/* EC-192: the editor-facing inspector no longer renders the raw node id or\n namespaced component id (the persona promise). The component's human name\n is in the panel header (with the manifest id in its `title`); the canvas\n and tree provide selection. */}\n {/* Contextual layout inspector for Stack/Grid containers (EC-162). */}\n <LayoutSection node={node} />\n\n {!manifest ? (\n <div className=\"p-3\">\n <Badge variant=\"error\">{translate('builder.inspector.unknownComponent')}</Badge>\n <Text size=\"xs\" tone=\"muted\" className=\"mt-2\">\n {translate('builder.inspector.noManifest')}\n </Text>\n </div>\n ) : (\n <InspectorSection title={translate('builder.inspector.props')}>\n {props.length === 0 ? (\n <Text size=\"xs\" tone=\"subtle\">\n {translate('builder.inspector.noEditableProps')}\n </Text>\n ) : (\n <Stack gap=\"4\">\n {props.map(([name, spec]) => (\n // Key by node id so per-node local control state never leaks\n // across a selection change to a same-named prop.\n <PropField key={`${node.id}:${name}`} node={node} name={name} spec={spec} />\n ))}\n </Stack>\n )}\n </InspectorSection>\n )}\n\n {slots.length > 0 ? (\n <InspectorSection title={translate('builder.inspector.slots')}>\n <Stack gap=\"1\">\n {slots.map((slot) => {\n const count = node.slots?.[slot.name]?.length ?? 0\n const missing = slot.required && count === 0\n return (\n <Row key={slot.name} gap=\"2\">\n <Text size=\"xs\" title={slot.name}>\n {humanizeName(slot.name)}\n </Text>\n {slot.required ? (\n <Badge variant={missing ? 'error' : 'outline'}>{translate('common.required')}</Badge>\n ) : null}\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {count}\n </Text>\n </Row>\n )\n })}\n </Stack>\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-2\">\n {translate('builder.inspector.slotsHint')}\n </Text>\n </InspectorSection>\n ) : null}\n </>\n )\n}\n\nfunction PropField({ node, name, spec }: { node: ComponentNode; name: string; spec: PropField }) {\n const builder = useBuilder()\n const sync = usePropSync()\n const anchorRef = useRef<HTMLDivElement>(null)\n const value = node.props?.[name]\n const id = `prop-${node.id}-${name}`\n // Coalesce rapid edits to the same prop into one undo step (EC-190).\n const coalesceKey = `${node.id}:${name}`\n\n // EC-408 FORWARD: a sub-part click that resolved to THIS prop scrolls its\n // control into view and focuses the first focusable element. Keyed on the\n // request object (a fresh nonce every request) so re-clicking re-focuses.\n const request = sync?.focusRequest\n useEffect(() => {\n if (!request || request.nodeId !== node.id || request.propName !== name) return\n const anchor = anchorRef.current\n if (!anchor) return\n anchor.scrollIntoView({ block: 'nearest' })\n anchor\n .querySelector<HTMLElement>('input, textarea, select, button, [contenteditable=\"true\"], [tabindex]')\n ?.focus()\n }, [request, node.id, name])\n\n return (\n // EC-408: the stable per-prop anchor. Focus (bubbling) inside any control\n // highlights the matching sub-part on the canvas (REVERSE); blur that leaves\n // the field clears it.\n <div\n ref={anchorRef}\n data-ec-prop-anchor={name}\n onFocus={() => sync?.setHighlighted({ nodeId: node.id, propName: name })}\n onBlur={(e) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) sync?.setHighlighted(null)\n }}\n >\n {/* EC-393: honor `form.description` as the field hint, at parity with the document\n form (`detail-editor.tsx` passes the same to its `Field`). */}\n <Field\n label={\n spec.form?.readOnly === true ? (\n // EC-395: the SAME read-only badge the document form renders next to a\n // `read`-access field label (`detail-editor.tsx`'s `fieldLevelReadOnly`\n // row) — identical icon, i18n key, and `Badge` variant, so a locked prop\n // reads exactly like a locked document field, never a bespoke treatment.\n <Row gap=\"2\">\n <span>{localizedLabel(spec.form?.label, name)}</span>\n <Badge variant=\"outline\">\n <Lock className=\"size-3\" /> {translate('cms.editor.readOnlyBadge')}\n </Badge>\n </Row>\n ) : (\n localizedLabel(spec.form?.label, name)\n )\n }\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={value}\n onSet={(v) => builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(v), coalesceKey })}\n onClear={() => builder.dispatch({ type: 'removeProp', id: node.id, name })}\n />\n </Field>\n </div>\n )\n}\n\n"]}
@@ -38,7 +38,7 @@ export function ReuseProvider({ children }) {
38
38
  const node = getNode(builder.present, nodeId);
39
39
  if (!node)
40
40
  return;
41
- const manifestTitle = manifests.find((m) => m.id === node.componentId)?.title;
41
+ const manifestTitle = localizedLabel(manifests.find((m) => m.id === node.componentId)?.title) || undefined;
42
42
  const collection = capability ? cms.getCollection(capability.collectionId) : undefined;
43
43
  const defaultName = manifestTitle ?? (localizedLabel(collection?.form?.labelSingular) || localizedLabel(collection?.form?.label) || translate('builder.reuse.defaultElementName'));
44
44
  setPending({ nodeId, defaultName });
@@ -1 +1 @@
1
- {"version":3,"file":"reuse-context.js","sourceRoot":"","sources":["../../../src/components/builder/reuse-context.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useMemo, useState } from 'react'\nimport type { FormEvent, ReactNode } from 'react'\nimport { blockRootsOf, getNode, isStaticProp } from '@elytracms/core/project-graph'\nimport { readDocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Input,\n Stack,\n toast,\n} from '@elytracms/studio/ui'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { useCms } from '../cms/cms-store'\nimport { useBuilder } from './builder-store'\nimport { buildReusableValues, makeEmbedNode, resolveReuseCapability } from './reuse'\nimport type { ReuseCapability } from './reuse'\n\n/**\n * In-canvas reuse gestures (EC-282): \"Make reusable\" promotes a selected block to\n * a new reusable document and replaces it in place with a transcluding embed;\n * \"Detach to local copy\" inlines an embed's referenced blocks so a page can\n * diverge. Both round-trip through the existing persistence + in-Convex reference\n * index (EC-277), so where-used is correct immediately.\n *\n * The provider needs BOTH the builder store (the edited composition) and the CMS\n * store (to create/read the reusable document), so it mounts inside the\n * composition-field editor's `BuilderProvider`, itself inside the CMS document\n * editor. Where no provider is mounted (or the capability is absent), the menu\n * reads `null` and simply omits the gestures.\n */\nexport interface ReuseGestures {\n capability: ReuseCapability | null\n canMakeReusable: (nodeId: string) => boolean\n canDetach: (nodeId: string) => boolean\n /** Open the name dialog for promoting `nodeId`. */\n startMakeReusable: (nodeId: string) => void\n /** Replace a `Reusable` embed with an editable inline copy of its target's blocks. */\n detach: (nodeId: string) => void\n}\n\nconst ReuseContext = createContext<ReuseGestures | null>(null)\n\nexport function useReuse(): ReuseGestures | null {\n return useContext(ReuseContext)\n}\n\nexport function ReuseProvider({ children }: { children: ReactNode }) {\n const builder = useBuilder()\n const cms = useCms()\n const [pending, setPending] = useState<{ nodeId: string; defaultName: string } | null>(null)\n\n const manifests = useMemo(() => getStudioRegistry().registry.list(), [])\n const capability = useMemo(\n () => resolveReuseCapability(manifests, cms.collections),\n [manifests, cms.collections],\n )\n\n const canMakeReusable = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n const node = getNode(builder.present, nodeId)\n // Anything but the synthetic composition root can be promoted; nesting a\n // reusable inside a reusable is allowed (the renderer + bake are cycle-\n // guarded, EC-281), so an embed itself is still promotable.\n return node !== undefined && node.id !== builder.present.id\n },\n [capability, builder],\n )\n\n const canDetach = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n return getNode(builder.present, nodeId)?.componentId === capability.embedComponentId\n },\n [capability, builder],\n )\n\n const startMakeReusable = useCallback(\n (nodeId: string) => {\n if (!canMakeReusable(nodeId)) return\n const node = getNode(builder.present, nodeId)\n if (!node) return\n const manifestTitle = manifests.find((m) => m.id === node.componentId)?.title\n const collection = capability ? cms.getCollection(capability.collectionId) : undefined\n const defaultName =\n manifestTitle ?? (localizedLabel(collection?.form?.labelSingular) || localizedLabel(collection?.form?.label) || translate('builder.reuse.defaultElementName'))\n setPending({ nodeId, defaultName })\n },\n [canMakeReusable, builder, manifests, capability, cms],\n )\n\n const confirmMakeReusable = useCallback(\n (name: string, publish: boolean) => {\n const cap = capability\n if (!cap || !pending) return\n const node = getNode(builder.present, pending.nodeId)\n const collection = cms.getCollection(cap.collectionId)\n if (!node || !collection) {\n setPending(null)\n return\n }\n // Mint the reusable document, then write its title + content (createDocument\n // only seeds defaults). The new id is referenced by the embed below.\n const ref = cms.createDocument(cap.collectionId)\n const values = buildReusableValues(collection, cap, name, node, cms.documentsOf(cap.collectionId))\n cms.saveDocument({ collection: cap.collectionId, id: ref.id, values })\n // Replace the selection in place with a transcluding embed (the action\n // clones to a fresh id; the source `node` stays untouched in the new doc).\n builder.dispatch({ type: 'replace', id: pending.nodeId, nodes: [makeEmbedNode(cap, ref.id)] })\n setPending(null)\n // EC-300: the new reusable is a separate document with its own lifecycle. A\n // draft-only target resolves to nothing in the published perspective (delivery\n // + the published canvas view), so the editor chooses at creation: PUBLISH keeps\n // the section visible everywhere immediately (the continuity-preserving default);\n // SAVE DRAFT keeps it editable, visible in the draft canvas, surfaced as a\n // pending reference until published. No silent auto-publish (AD-16).\n if (publish) {\n void cms.publishDocument(ref).then(\n () => toast.success(translate('builder.reuse.madeReusablePublished', { name })),\n () => toast.error(translate('builder.reuse.madeReusablePublishFailed', { name })),\n )\n } else {\n toast.success(translate('builder.reuse.madeReusableDraft', { name }))\n }\n },\n [capability, pending, builder, cms],\n )\n\n const detach = useCallback(\n (nodeId: string) => {\n const cap = capability\n if (!cap) return\n const node = getNode(builder.present, nodeId)\n if (!node || node.componentId !== cap.embedComponentId) return\n const prop = node.props?.[cap.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n if (!ref) {\n toast.error(translate('builder.reuse.noTargetToDetach'))\n return\n }\n const target = cms.getDocument(ref)\n const blocks = blockRootsOf(target?.values?.[cap.contentField])\n if (blocks.length === 0) {\n toast.error(translate('builder.reuse.noContentToDetach'))\n return\n }\n builder.dispatch({ type: 'replace', id: nodeId, nodes: blocks })\n toast.success(translate('builder.reuse.detachedToCopy'))\n },\n [capability, builder, cms],\n )\n\n const value = useMemo<ReuseGestures>(\n () => ({ capability, canMakeReusable, canDetach, startMakeReusable, detach }),\n [capability, canMakeReusable, canDetach, startMakeReusable, detach],\n )\n\n return (\n <ReuseContext.Provider value={value}>\n {children}\n <MakeReusableDialog\n open={pending !== null}\n defaultName={pending?.defaultName ?? ''}\n onConfirm={confirmMakeReusable}\n onCancel={() => setPending(null)}\n />\n </ReuseContext.Provider>\n )\n}\n\n/** Name-and-create dialog for the \"Make reusable\" gesture. */\nfunction MakeReusableDialog({\n open,\n defaultName,\n onConfirm,\n onCancel,\n}: {\n open: boolean\n defaultName: string\n /** `publish` chooses the new reusable's initial state (EC-300/AD-16). */\n onConfirm: (name: string, publish: boolean) => void\n onCancel: () => void\n}) {\n const [name, setName] = useState(defaultName)\n // Re-seed the field each time the dialog opens for a new selection.\n const [seededFor, setSeededFor] = useState<string | null>(null)\n if (open && seededFor !== defaultName) {\n setName(defaultName)\n setSeededFor(defaultName)\n }\n\n const empty = name.trim() === ''\n function commit(publish: boolean) {\n if (empty) return\n onConfirm(name.trim(), publish)\n setSeededFor(null)\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) {\n onCancel()\n setSeededFor(null)\n }\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{translate('builder.reuse.makeReusable')}</DialogTitle>\n <DialogDescription>\n {translate('builder.reuse.makeReusableDescription')}\n </DialogDescription>\n </DialogHeader>\n {/* Enter submits the primary action (Publish) — the continuity-preserving default. */}\n <form\n onSubmit={(event: FormEvent) => {\n event.preventDefault()\n commit(true)\n }}\n >\n <Stack gap=\"3\">\n <Field label={translate('builder.reuse.nameLabel')} htmlFor=\"make-reusable-name\" required>\n <Input\n id=\"make-reusable-name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder={translate('builder.reuse.namePlaceholder')}\n autoFocus\n required\n />\n </Field>\n </Stack>\n <DialogFooter className=\"mt-4\">\n {/* Cancel pinned left; the two commit actions group right. */}\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"mr-auto\" onClick={onCancel}>\n {translate('common.cancel')}\n </Button>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={empty}\n onClick={() => commit(false)}\n data-testid=\"make-reusable-draft\"\n >\n {translate('builder.reuse.saveDraft')}\n </Button>\n <Button\n type=\"submit\"\n size=\"sm\"\n disabled={empty}\n data-testid=\"make-reusable-publish\"\n >\n {translate('builder.reuse.publish')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
1
+ {"version":3,"file":"reuse-context.js","sourceRoot":"","sources":["../../../src/components/builder/reuse-context.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useMemo, useState } from 'react'\nimport type { FormEvent, ReactNode } from 'react'\nimport { blockRootsOf, getNode, isStaticProp } from '@elytracms/core/project-graph'\nimport { readDocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Input,\n Stack,\n toast,\n} from '@elytracms/studio/ui'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { useCms } from '../cms/cms-store'\nimport { useBuilder } from './builder-store'\nimport { buildReusableValues, makeEmbedNode, resolveReuseCapability } from './reuse'\nimport type { ReuseCapability } from './reuse'\n\n/**\n * In-canvas reuse gestures (EC-282): \"Make reusable\" promotes a selected block to\n * a new reusable document and replaces it in place with a transcluding embed;\n * \"Detach to local copy\" inlines an embed's referenced blocks so a page can\n * diverge. Both round-trip through the existing persistence + in-Convex reference\n * index (EC-277), so where-used is correct immediately.\n *\n * The provider needs BOTH the builder store (the edited composition) and the CMS\n * store (to create/read the reusable document), so it mounts inside the\n * composition-field editor's `BuilderProvider`, itself inside the CMS document\n * editor. Where no provider is mounted (or the capability is absent), the menu\n * reads `null` and simply omits the gestures.\n */\nexport interface ReuseGestures {\n capability: ReuseCapability | null\n canMakeReusable: (nodeId: string) => boolean\n canDetach: (nodeId: string) => boolean\n /** Open the name dialog for promoting `nodeId`. */\n startMakeReusable: (nodeId: string) => void\n /** Replace a `Reusable` embed with an editable inline copy of its target's blocks. */\n detach: (nodeId: string) => void\n}\n\nconst ReuseContext = createContext<ReuseGestures | null>(null)\n\nexport function useReuse(): ReuseGestures | null {\n return useContext(ReuseContext)\n}\n\nexport function ReuseProvider({ children }: { children: ReactNode }) {\n const builder = useBuilder()\n const cms = useCms()\n const [pending, setPending] = useState<{ nodeId: string; defaultName: string } | null>(null)\n\n const manifests = useMemo(() => getStudioRegistry().registry.list(), [])\n const capability = useMemo(\n () => resolveReuseCapability(manifests, cms.collections),\n [manifests, cms.collections],\n )\n\n const canMakeReusable = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n const node = getNode(builder.present, nodeId)\n // Anything but the synthetic composition root can be promoted; nesting a\n // reusable inside a reusable is allowed (the renderer + bake are cycle-\n // guarded, EC-281), so an embed itself is still promotable.\n return node !== undefined && node.id !== builder.present.id\n },\n [capability, builder],\n )\n\n const canDetach = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n return getNode(builder.present, nodeId)?.componentId === capability.embedComponentId\n },\n [capability, builder],\n )\n\n const startMakeReusable = useCallback(\n (nodeId: string) => {\n if (!canMakeReusable(nodeId)) return\n const node = getNode(builder.present, nodeId)\n if (!node) return\n const manifestTitle = localizedLabel(manifests.find((m) => m.id === node.componentId)?.title) || undefined\n const collection = capability ? cms.getCollection(capability.collectionId) : undefined\n const defaultName =\n manifestTitle ?? (localizedLabel(collection?.form?.labelSingular) || localizedLabel(collection?.form?.label) || translate('builder.reuse.defaultElementName'))\n setPending({ nodeId, defaultName })\n },\n [canMakeReusable, builder, manifests, capability, cms],\n )\n\n const confirmMakeReusable = useCallback(\n (name: string, publish: boolean) => {\n const cap = capability\n if (!cap || !pending) return\n const node = getNode(builder.present, pending.nodeId)\n const collection = cms.getCollection(cap.collectionId)\n if (!node || !collection) {\n setPending(null)\n return\n }\n // Mint the reusable document, then write its title + content (createDocument\n // only seeds defaults). The new id is referenced by the embed below.\n const ref = cms.createDocument(cap.collectionId)\n const values = buildReusableValues(collection, cap, name, node, cms.documentsOf(cap.collectionId))\n cms.saveDocument({ collection: cap.collectionId, id: ref.id, values })\n // Replace the selection in place with a transcluding embed (the action\n // clones to a fresh id; the source `node` stays untouched in the new doc).\n builder.dispatch({ type: 'replace', id: pending.nodeId, nodes: [makeEmbedNode(cap, ref.id)] })\n setPending(null)\n // EC-300: the new reusable is a separate document with its own lifecycle. A\n // draft-only target resolves to nothing in the published perspective (delivery\n // + the published canvas view), so the editor chooses at creation: PUBLISH keeps\n // the section visible everywhere immediately (the continuity-preserving default);\n // SAVE DRAFT keeps it editable, visible in the draft canvas, surfaced as a\n // pending reference until published. No silent auto-publish (AD-16).\n if (publish) {\n void cms.publishDocument(ref).then(\n () => toast.success(translate('builder.reuse.madeReusablePublished', { name })),\n () => toast.error(translate('builder.reuse.madeReusablePublishFailed', { name })),\n )\n } else {\n toast.success(translate('builder.reuse.madeReusableDraft', { name }))\n }\n },\n [capability, pending, builder, cms],\n )\n\n const detach = useCallback(\n (nodeId: string) => {\n const cap = capability\n if (!cap) return\n const node = getNode(builder.present, nodeId)\n if (!node || node.componentId !== cap.embedComponentId) return\n const prop = node.props?.[cap.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n if (!ref) {\n toast.error(translate('builder.reuse.noTargetToDetach'))\n return\n }\n const target = cms.getDocument(ref)\n const blocks = blockRootsOf(target?.values?.[cap.contentField])\n if (blocks.length === 0) {\n toast.error(translate('builder.reuse.noContentToDetach'))\n return\n }\n builder.dispatch({ type: 'replace', id: nodeId, nodes: blocks })\n toast.success(translate('builder.reuse.detachedToCopy'))\n },\n [capability, builder, cms],\n )\n\n const value = useMemo<ReuseGestures>(\n () => ({ capability, canMakeReusable, canDetach, startMakeReusable, detach }),\n [capability, canMakeReusable, canDetach, startMakeReusable, detach],\n )\n\n return (\n <ReuseContext.Provider value={value}>\n {children}\n <MakeReusableDialog\n open={pending !== null}\n defaultName={pending?.defaultName ?? ''}\n onConfirm={confirmMakeReusable}\n onCancel={() => setPending(null)}\n />\n </ReuseContext.Provider>\n )\n}\n\n/** Name-and-create dialog for the \"Make reusable\" gesture. */\nfunction MakeReusableDialog({\n open,\n defaultName,\n onConfirm,\n onCancel,\n}: {\n open: boolean\n defaultName: string\n /** `publish` chooses the new reusable's initial state (EC-300/AD-16). */\n onConfirm: (name: string, publish: boolean) => void\n onCancel: () => void\n}) {\n const [name, setName] = useState(defaultName)\n // Re-seed the field each time the dialog opens for a new selection.\n const [seededFor, setSeededFor] = useState<string | null>(null)\n if (open && seededFor !== defaultName) {\n setName(defaultName)\n setSeededFor(defaultName)\n }\n\n const empty = name.trim() === ''\n function commit(publish: boolean) {\n if (empty) return\n onConfirm(name.trim(), publish)\n setSeededFor(null)\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) {\n onCancel()\n setSeededFor(null)\n }\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{translate('builder.reuse.makeReusable')}</DialogTitle>\n <DialogDescription>\n {translate('builder.reuse.makeReusableDescription')}\n </DialogDescription>\n </DialogHeader>\n {/* Enter submits the primary action (Publish) — the continuity-preserving default. */}\n <form\n onSubmit={(event: FormEvent) => {\n event.preventDefault()\n commit(true)\n }}\n >\n <Stack gap=\"3\">\n <Field label={translate('builder.reuse.nameLabel')} htmlFor=\"make-reusable-name\" required>\n <Input\n id=\"make-reusable-name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder={translate('builder.reuse.namePlaceholder')}\n autoFocus\n required\n />\n </Field>\n </Stack>\n <DialogFooter className=\"mt-4\">\n {/* Cancel pinned left; the two commit actions group right. */}\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"mr-auto\" onClick={onCancel}>\n {translate('common.cancel')}\n </Button>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={empty}\n onClick={() => commit(false)}\n data-testid=\"make-reusable-draft\"\n >\n {translate('builder.reuse.saveDraft')}\n </Button>\n <Button\n type=\"submit\"\n size=\"sm\"\n disabled={empty}\n data-testid=\"make-reusable-publish\"\n >\n {translate('builder.reuse.publish')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"]}