@elytracms/studio 0.0.64 → 0.0.65

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 (123) 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/assets/asset-thumb.d.ts +4 -2
  5. package/dist/components/assets/asset-thumb.js +26 -4
  6. package/dist/components/assets/asset-thumb.js.map +1 -1
  7. package/dist/components/assets/assets-module.js +48 -2
  8. package/dist/components/assets/assets-module.js.map +1 -1
  9. package/dist/components/assets/assets-store.d.ts +11 -1
  10. package/dist/components/assets/assets-store.js +18 -1
  11. package/dist/components/assets/assets-store.js.map +1 -1
  12. package/dist/components/builder/blocks-model.d.ts +11 -0
  13. package/dist/components/builder/blocks-model.js +26 -8
  14. package/dist/components/builder/blocks-model.js.map +1 -1
  15. package/dist/components/builder/canvas-chrome-css.js +90 -8
  16. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  17. package/dist/components/builder/canvas-inline-editing.d.ts +7 -0
  18. package/dist/components/builder/canvas-inline-editing.js +3 -1
  19. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  20. package/dist/components/builder/canvas-overlay-geometry.d.ts +72 -0
  21. package/dist/components/builder/canvas-overlay-geometry.js +80 -0
  22. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -0
  23. package/dist/components/builder/canvas-rect-tracking.d.ts +124 -0
  24. package/dist/components/builder/canvas-rect-tracking.js +198 -0
  25. package/dist/components/builder/canvas-rect-tracking.js.map +1 -0
  26. package/dist/components/builder/canvas.js +61 -12
  27. package/dist/components/builder/canvas.js.map +1 -1
  28. package/dist/components/builder/composition-field-editor.js +22 -3
  29. package/dist/components/builder/composition-field-editor.js.map +1 -1
  30. package/dist/components/builder/delete-key.d.ts +38 -0
  31. package/dist/components/builder/delete-key.js +46 -0
  32. package/dist/components/builder/delete-key.js.map +1 -0
  33. package/dist/components/builder/drag-session.d.ts +8 -0
  34. package/dist/components/builder/drag-session.js +15 -0
  35. package/dist/components/builder/drag-session.js.map +1 -1
  36. package/dist/components/builder/insert-affordance-context.d.ts +19 -0
  37. package/dist/components/builder/insert-affordance-context.js +7 -0
  38. package/dist/components/builder/insert-affordance-context.js.map +1 -0
  39. package/dist/components/builder/insert-context.d.ts +40 -0
  40. package/dist/components/builder/insert-context.js +86 -3
  41. package/dist/components/builder/insert-context.js.map +1 -1
  42. package/dist/components/builder/node-title.d.ts +60 -0
  43. package/dist/components/builder/node-title.js +135 -0
  44. package/dist/components/builder/node-title.js.map +1 -0
  45. package/dist/components/builder/page-tree.js +21 -3
  46. package/dist/components/builder/page-tree.js.map +1 -1
  47. package/dist/components/builder/prop-selection.d.ts +52 -0
  48. package/dist/components/builder/prop-selection.js +38 -0
  49. package/dist/components/builder/prop-selection.js.map +1 -0
  50. package/dist/components/builder/prop-sync-context.d.ts +49 -0
  51. package/dist/components/builder/prop-sync-context.js +23 -0
  52. package/dist/components/builder/prop-sync-context.js.map +1 -0
  53. package/dist/components/builder/property-inspector.js +23 -1
  54. package/dist/components/builder/property-inspector.js.map +1 -1
  55. package/dist/components/builder/reuse-context.js +1 -1
  56. package/dist/components/builder/reuse-context.js.map +1 -1
  57. package/dist/components/builder/selection-overlay.d.ts +79 -0
  58. package/dist/components/builder/selection-overlay.js +244 -0
  59. package/dist/components/builder/selection-overlay.js.map +1 -0
  60. package/dist/components/builder/spacing-handles.d.ts +57 -1
  61. package/dist/components/builder/spacing-handles.js +142 -26
  62. package/dist/components/builder/spacing-handles.js.map +1 -1
  63. package/dist/components/cms/asset-picker.js +11 -5
  64. package/dist/components/cms/asset-picker.js.map +1 -1
  65. package/dist/components/cms/rich-text/block-style.d.ts +48 -0
  66. package/dist/components/cms/rich-text/block-style.js +30 -0
  67. package/dist/components/cms/rich-text/block-style.js.map +1 -0
  68. package/dist/components/cms/rich-text/embed-picker.js +4 -3
  69. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  70. package/dist/components/cms/rich-text/extensions.d.ts +2 -0
  71. package/dist/components/cms/rich-text/extensions.js +4 -0
  72. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  73. package/dist/components/cms/rich-text/font-size-control.d.ts +31 -0
  74. package/dist/components/cms/rich-text/font-size-control.js +40 -0
  75. package/dist/components/cms/rich-text/font-size-control.js.map +1 -0
  76. package/dist/components/cms/rich-text/font-size.d.ts +16 -0
  77. package/dist/components/cms/rich-text/font-size.js +40 -0
  78. package/dist/components/cms/rich-text/font-size.js.map +1 -0
  79. package/dist/components/cms/rich-text/toolbar-state.d.ts +3 -0
  80. package/dist/components/cms/rich-text/toolbar-state.js +3 -0
  81. package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
  82. package/dist/components/cms/rich-text/toolbar.d.ts +13 -0
  83. package/dist/components/cms/rich-text/toolbar.js +17 -5
  84. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  85. package/dist/components/components/component-detail.js +3 -3
  86. package/dist/components/components/component-detail.js.map +1 -1
  87. package/dist/components/components/component-preview.js +1 -1
  88. package/dist/components/components/component-preview.js.map +1 -1
  89. package/dist/components/components/registry-browser.js +2 -2
  90. package/dist/components/components/registry-browser.js.map +1 -1
  91. package/dist/components/components/taxonomy.js +3 -2
  92. package/dist/components/components/taxonomy.js.map +1 -1
  93. package/dist/components/components/transfer-dialog.js +1 -1
  94. package/dist/components/components/transfer-dialog.js.map +1 -1
  95. package/dist/lib/assets/backfill-thumbs.d.ts +67 -0
  96. package/dist/lib/assets/backfill-thumbs.js +120 -0
  97. package/dist/lib/assets/backfill-thumbs.js.map +1 -0
  98. package/dist/lib/assets/delete-asset.d.ts +2 -1
  99. package/dist/lib/assets/delete-asset.js +12 -0
  100. package/dist/lib/assets/delete-asset.js.map +1 -1
  101. package/dist/lib/assets/index.d.ts +2 -0
  102. package/dist/lib/assets/index.js +2 -0
  103. package/dist/lib/assets/index.js.map +1 -1
  104. package/dist/lib/assets/thumbnail.d.ts +145 -0
  105. package/dist/lib/assets/thumbnail.js +220 -0
  106. package/dist/lib/assets/thumbnail.js.map +1 -0
  107. package/dist/lib/assets/upload.d.ts +9 -0
  108. package/dist/lib/assets/upload.js +31 -0
  109. package/dist/lib/assets/upload.js.map +1 -1
  110. package/dist/lib/i18n/messages.de.js +16 -0
  111. package/dist/lib/i18n/messages.de.js.map +1 -1
  112. package/dist/lib/i18n/messages.en.d.ts +14 -0
  113. package/dist/lib/i18n/messages.en.js +16 -0
  114. package/dist/lib/i18n/messages.en.js.map +1 -1
  115. package/dist/lib/persistence/convex-adapter.js +4 -0
  116. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  117. package/dist/lib/persona/labels.d.ts +9 -5
  118. package/dist/lib/persona/labels.js +13 -8
  119. package/dist/lib/persona/labels.js.map +1 -1
  120. package/dist/lib/references/references-store.js +7 -1
  121. package/dist/lib/references/references-store.js.map +1 -1
  122. package/package.json +2 -2
  123. package/src/styles/studio.css +13 -0
@@ -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":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAG1D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EACL,KAAK,EACL,IAAI,EACJ,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,GAAG,EACH,MAAM,EACN,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAA;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,MAAM,UAAU,iBAAiB;IAC/B,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,CAAA;IAEjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CACL,cAAK,SAAS,EAAC,gDAAgD,YAC7D,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,kCAAkC,CAAC,GACzC,GACH,CACP,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAE9D,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC3C,MAAC,WAAW,eACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,YAC9B,SAAS,CAAC,yBAAyB,CAAC,GAChC,EACP,KAAC,MAAM,KAAG,EACV,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,EAAC,KAAK,EAAE,IAAI,CAAC,WAAW,YACvC,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GAC5C,IACK,EAEd,cAAK,SAAS,EAAC,8BAA8B,YAC3C,KAAC,WAAW,IAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAI,GAC3C,IACF,CACP,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAA2B,EAC3B,IAAmB,EACnB,aAAkC;IAElC,MAAM,GAAG,GAA0B,EAAE,MAAM,EAAE,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAA;IAClF,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,CAC1C,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CACf,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,IAAI,KAAK,QAAQ;QACtB,CAAC,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAc,EAAE,GAAG,CAAC,CACrD,CAAA;AACH,CAAC;AAED,gFAAgF;AAChF,SAAS,WAAW,CAAC,EACnB,IAAI,EACJ,QAAQ,GAIT;IACC,0EAA0E;IAC1E,2CAA2C;IAC3C,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAA;IAC3D,8EAA8E;IAC9E,mFAAmF;IACnF,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAC,QAAQ,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAClF,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAErD,OAAO,CACL,8BAME,KAAC,aAAa,IAAC,IAAI,EAAE,IAAI,GAAI,EAE5B,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,eAAK,SAAS,EAAC,KAAK,aAClB,KAAC,KAAK,IAAC,OAAO,EAAC,OAAO,YAAE,SAAS,CAAC,oCAAoC,CAAC,GAAS,EAChF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,MAAM,YAC1C,SAAS,CAAC,8BAA8B,CAAC,GACrC,IACH,CACP,CAAC,CAAC,CAAC,CACF,KAAC,gBAAgB,IAAC,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,YAC1D,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACpB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,mCAAmC,CAAC,GAC1C,CACR,CAAC,CAAC,CAAC,CACF,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,YACX,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAG3B,KAAC,SAAS,IAA4B,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,IAAxD,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,CAAwC,CAC7E,CAAC,GACI,CACT,GACgB,CACpB,EAEA,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAClB,MAAC,gBAAgB,IAAC,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,aAC3D,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,YACX,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;4BAClB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,CAAA;4BAClD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,KAAK,KAAK,CAAC,CAAA;4BAC5C,OAAO,CACL,MAAC,GAAG,IAAiB,GAAG,EAAC,GAAG,aAC1B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAE,IAAI,CAAC,IAAI,YAC7B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GACnB,EACN,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CACf,KAAC,KAAK,IAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,YAAG,SAAS,CAAC,iBAAiB,CAAC,GAAS,CACtF,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,MAAM,KAAG,EACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,kBAC/B,KAAK,GACD,KAVC,IAAI,CAAC,IAAI,CAWb,CACP,CAAA;wBACH,CAAC,CAAC,GACI,EACR,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,MAAM,YAC3C,SAAS,CAAC,6BAA6B,CAAC,GACpC,IACU,CACpB,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAA0D;IAC7F,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,IAAI,GAAG,WAAW,EAAE,CAAA;IAC1B,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAA;IAChC,MAAM,EAAE,GAAG,QAAQ,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,CAAA;IACpC,qEAAqE;IACrE,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,CAAA;IAExC,0EAA0E;IAC1E,0EAA0E;IAC1E,0EAA0E;IAC1E,MAAM,OAAO,GAAG,IAAI,EAAE,YAAY,CAAA;IAClC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI;YAAE,OAAM;QAC/E,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,MAAM,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;QAC3C,MAAM;aACH,aAAa,CAAc,uEAAuE,CAAC;YACpG,EAAE,KAAK,EAAE,CAAA;IACb,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAA;IAE5B,OAAO,CAIL,cACE,GAAG,EAAE,SAAS,yBACO,IAAI,EACzB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EACxE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;YACZ,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,aAA4B,CAAC;gBAAE,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,CAAA;QAC3F,CAAC,YAID,KAAC,KAAK,IACJ,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,EAC7C,OAAO,EAAE,EAAE,EACX,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,SAAS,YAEzD,KAAC,gBAAgB,IACf,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EACzG,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,GAC1E,GACI,GACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["import { useEffect, useRef } from '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={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 </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":";AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEjF,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,KAAK,EACL,KAAK,EACL,KAAK,EACL,KAAK,GACN,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AA0BpF,MAAM,YAAY,GAAG,aAAa,CAAuB,IAAI,CAAC,CAAA;AAE9D,MAAM,UAAU,QAAQ;IACtB,OAAO,UAAU,CAAC,YAAY,CAAC,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAA2B;IACjE,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAiD,IAAI,CAAC,CAAA;IAE5F,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IACxE,MAAM,UAAU,GAAG,OAAO,CACxB,GAAG,EAAE,CAAC,sBAAsB,CAAC,SAAS,EAAE,GAAG,CAAC,WAAW,CAAC,EACxD,CAAC,SAAS,EAAE,GAAG,CAAC,WAAW,CAAC,CAC7B,CAAA;IAED,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,MAAc,EAAW,EAAE;QAC1B,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAA;QAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC7C,yEAAyE;QACzE,wEAAwE;QACxE,4DAA4D;QAC5D,OAAO,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EAAE,CAAA;IAC7D,CAAC,EACD,CAAC,UAAU,EAAE,OAAO,CAAC,CACtB,CAAA;IAED,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,MAAc,EAAW,EAAE;QAC1B,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAA;QAC7B,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,UAAU,CAAC,gBAAgB,CAAA;IACtF,CAAC,EACD,CAAC,UAAU,EAAE,OAAO,CAAC,CACtB,CAAA;IAED,MAAM,iBAAiB,GAAG,WAAW,CACnC,CAAC,MAAc,EAAE,EAAE;QACjB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;YAAE,OAAM;QACpC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC7C,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,aAAa,GAAG,cAAc,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,IAAI,SAAS,CAAA;QAC1G,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACtF,MAAM,WAAW,GACf,aAAa,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,aAAa,CAAC,IAAI,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,kCAAkC,CAAC,CAAC,CAAA;QAChK,UAAU,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;IACrC,CAAC,EACD,CAAC,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,CAAC,CACvD,CAAA;IAED,MAAM,mBAAmB,GAAG,WAAW,CACrC,CAAC,IAAY,EAAE,OAAgB,EAAE,EAAE;QACjC,MAAM,GAAG,GAAG,UAAU,CAAA;QACtB,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO;YAAE,OAAM;QAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QACrD,MAAM,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,CAAA;YAChB,OAAM;QACR,CAAC;QACD,6EAA6E;QAC7E,qEAAqE;QACrE,MAAM,GAAG,GAAG,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAChD,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAA;QAClG,GAAG,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QACtE,uEAAuE;QACvE,2EAA2E;QAC3E,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;QAC9F,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,4EAA4E;QAC5E,+EAA+E;QAC/E,iFAAiF;QACjF,kFAAkF;QAClF,2EAA2E;QAC3E,qEAAqE;QACrE,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,CAChC,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,qCAAqC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAC/E,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,yCAAyC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAClF,CAAA;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,iCAAiC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACvE,CAAC;IACH,CAAC,EACD,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CACpC,CAAA;IAED,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,MAAc,EAAE,EAAE;QACjB,MAAM,GAAG,GAAG,UAAU,CAAA;QACtB,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC7C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,KAAK,GAAG,CAAC,gBAAgB;YAAE,OAAM;QAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACxE,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,gCAAgC,CAAC,CAAC,CAAA;YACxD,OAAM;QACR,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;QACnC,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAA;QAC/D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC,CAAA;YACzD,OAAM;QACR,CAAC;QACD,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAChE,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC,CAAA;IAC1D,CAAC,EACD,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,CAC3B,CAAA;IAED,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,EAAE,CAAC,EAC7E,CAAC,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,CAAC,CACpE,CAAA;IAED,OAAO,CACL,MAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,aAChC,QAAQ,EACT,KAAC,kBAAkB,IACjB,IAAI,EAAE,OAAO,KAAK,IAAI,EACtB,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,EAAE,EACvC,SAAS,EAAE,mBAAmB,EAC9B,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAChC,IACoB,CACzB,CAAA;AACH,CAAC;AAED,8DAA8D;AAC9D,SAAS,kBAAkB,CAAC,EAC1B,IAAI,EACJ,WAAW,EACX,SAAS,EACT,QAAQ,GAOT;IACC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC7C,oEAAoE;IACpE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAC/D,IAAI,IAAI,IAAI,SAAS,KAAK,WAAW,EAAE,CAAC;QACtC,OAAO,CAAC,WAAW,CAAC,CAAA;QACpB,YAAY,CAAC,WAAW,CAAC,CAAA;IAC3B,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;IAChC,SAAS,MAAM,CAAC,OAAgB;QAC9B,IAAI,KAAK;YAAE,OAAM;QACjB,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAA;QAC/B,YAAY,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC;IAED,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE;YACrB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,QAAQ,EAAE,CAAA;gBACV,YAAY,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,YAED,MAAC,aAAa,eACZ,MAAC,YAAY,eACX,KAAC,WAAW,cAAE,SAAS,CAAC,4BAA4B,CAAC,GAAe,EACpE,KAAC,iBAAiB,cACf,SAAS,CAAC,uCAAuC,CAAC,GACjC,IACP,EAEf,gBACE,QAAQ,EAAE,CAAC,KAAgB,EAAE,EAAE;wBAC7B,KAAK,CAAC,cAAc,EAAE,CAAA;wBACtB,MAAM,CAAC,IAAI,CAAC,CAAA;oBACd,CAAC,aAED,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,YACZ,KAAC,KAAK,IAAC,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,EAAE,OAAO,EAAC,oBAAoB,EAAC,QAAQ,kBACvF,KAAC,KAAK,IACJ,EAAE,EAAC,oBAAoB,EACvB,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAChD,WAAW,EAAE,SAAS,CAAC,+BAA+B,CAAC,EACvD,SAAS,QACT,QAAQ,SACR,GACI,GACF,EACR,MAAC,YAAY,IAAC,SAAS,EAAC,MAAM,aAE5B,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,SAAS,EAAC,OAAO,EAAE,QAAQ,YAClF,SAAS,CAAC,eAAe,CAAC,GACpB,EACT,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,IAAI,EACT,QAAQ,EAAE,KAAK,EACf,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,iBAChB,qBAAqB,YAEhC,SAAS,CAAC,yBAAyB,CAAC,GAC9B,EACT,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,IAAI,EACT,QAAQ,EAAE,KAAK,iBACH,uBAAuB,YAElC,SAAS,CAAC,uBAAuB,CAAC,GAC5B,IACI,IACV,IACO,GACT,CACV,CAAA;AACH,CAAC","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"]}
@@ -0,0 +1,79 @@
1
+ import type { InsertPosition } from './insert-context.js';
2
+ import type { AffordanceAnchor, FrameBarPlacement } from './canvas-overlay-geometry.js';
3
+ import type { Rect } from './canvas-rect-tracking.js';
4
+ /**
5
+ * Canvas selection/hover overlay (EC-404) — a topmost layer portaled into the
6
+ * iframe `body` that draws the selection and hover frames ABOVE the page from
7
+ * measured node rects, so a full-bleed background or a higher-`z-index` sibling
8
+ * can never paint over them (the structural fix for the in-document inset
9
+ * outline the findings batch flagged). The selected frame carries a compact tag:
10
+ * the component's display name plus move-up / move-down / duplicate / delete
11
+ * controls that dispatch the EXISTING tree actions.
12
+ *
13
+ * EC-414 AD-B chrome contract: everything here is `ec-*` classes / `data-ec-*`
14
+ * attributes / inline geometry only, styled by `canvas-chrome-css.ts`, with the
15
+ * `.ec-chrome` containment recipe on the root; no studio Tailwind utilities and
16
+ * no studio React UI primitives cross into the iframe. The rect-tracking core
17
+ * ({@link createRectTracker}) is a pure, reusable module — EC-405 rides this
18
+ * overlay layer next, and EC-406 can share the tracking.
19
+ */
20
+ /** The overlay's full measured geometry (both frames + the bar placement). */
21
+ export interface OverlayGeometry {
22
+ selected: Rect | null;
23
+ /** Chrome-bar placement for the selected frame (null when nothing to draw). */
24
+ selectedBar: FrameBarPlacement | null;
25
+ hovered: Rect | null;
26
+ /** EC-408: the focused prop's marker rect (reverse highlight), or null. */
27
+ propHighlight: Rect | null;
28
+ }
29
+ /** A drawn selection frame plus its control enablement. */
30
+ export interface SelectionFrameView {
31
+ rect: Rect;
32
+ bar: FrameBarPlacement;
33
+ label: string;
34
+ canMoveUp: boolean;
35
+ canMoveDown: boolean;
36
+ }
37
+ /** One "+" affordance button: its position and its placed anchor point. */
38
+ export interface AffordanceItemView extends AffordanceAnchor {
39
+ position: InsertPosition;
40
+ }
41
+ /**
42
+ * The affordance buttons for one node — grouped so a single `onInsert` binds the
43
+ * node id the inserts target (the selected node and, separately, a hovered one).
44
+ */
45
+ export interface AffordanceGroupView {
46
+ nodeId: string;
47
+ items: AffordanceItemView[];
48
+ onInsert: (position: InsertPosition) => void;
49
+ }
50
+ export interface SelectionOverlayViewProps {
51
+ selected: SelectionFrameView | null;
52
+ hovered: Rect | null;
53
+ /** In-canvas insertion affordances (EC-405); empty when suppressed (drag/editing). */
54
+ affordances: AffordanceGroupView[];
55
+ /** EC-408: the focused prop's marker rect (reverse highlight), or null. */
56
+ propHighlight?: Rect | null;
57
+ onMoveUp: () => void;
58
+ onMoveDown: () => void;
59
+ onDuplicate: () => void;
60
+ onDelete: () => void;
61
+ }
62
+ /**
63
+ * The overlay's rendered markup, portal-free so the AD-D chrome contract test
64
+ * can walk it with `renderToStaticMarkup`: every class is `ec-*`, the root
65
+ * carries the `.ec-chrome` containment recipe, geometry is inline (no `var()`).
66
+ */
67
+ export declare function SelectionOverlayView(props: SelectionOverlayViewProps): import("react").JSX.Element;
68
+ /**
69
+ * The live overlay: tracks the selected + hovered node rects (glued through
70
+ * scroll/resize/async layout via {@link subscribeCanvasLayout} + a coalescing
71
+ * {@link createRectTracker}) and portals the frames into the iframe `body`.
72
+ * Suppressed for the composition root (no frame around the whole page — mirrors
73
+ * the tree, which offers the root no actions) and while an inline text/rich-text
74
+ * edit is live (the `[data-ec-editing]` outline owns that state).
75
+ */
76
+ export declare function SelectionOverlay({ doc, editing }: {
77
+ doc: Document;
78
+ editing: boolean;
79
+ }): import("react").ReactPortal;
@@ -0,0 +1,244 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { getNode, getParentInfo } from '@elytracms/core/project-graph';
5
+ import { getStudioRegistry } from '@elytracms/studio/lib/registry';
6
+ import { translate, useUiLocale } from '@elytracms/studio/lib/i18n';
7
+ import { containerFlowAxis } from '@elytracms/studio/lib/dnd/drop-rules';
8
+ import { useBuilder } from './builder-store.js';
9
+ import { activeDrag, onDragSessionEnd, onDragSessionStart } from './drag-session.js';
10
+ import { useInsertAffordance } from './insert-affordance-context.js';
11
+ import { FRAME_BAR_HEIGHT, frameControlAction, frameControlsFor, placeAffordance, placeFrameBar, } from './canvas-overlay-geometry.js';
12
+ import { createRectTracker, measureNodeRect, measurePropRect, rectsEqual, scheduleFrame, subscribeCanvasLayout, } from './canvas-rect-tracking.js';
13
+ import { usePropSync } from './prop-sync-context.js';
14
+ import { nodeDisplayTitle } from './node-title.js';
15
+ const EMPTY_GEOMETRY = {
16
+ selected: null,
17
+ selectedBar: null,
18
+ hovered: null,
19
+ propHighlight: null,
20
+ };
21
+ function barsEqual(a, b) {
22
+ if (a === null || b === null)
23
+ return a === b;
24
+ return a.top === b.top && a.left === b.left && a.placement === b.placement;
25
+ }
26
+ function geometryEquals(a, b) {
27
+ return (rectsEqual(a.selected, b.selected) &&
28
+ rectsEqual(a.hovered, b.hovered) &&
29
+ rectsEqual(a.propHighlight, b.propHighlight) &&
30
+ barsEqual(a.selectedBar, b.selectedBar));
31
+ }
32
+ function frameStyle(rect) {
33
+ return { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
34
+ }
35
+ const AFFORDANCE_LABELS = {
36
+ before: 'builder.canvas.insertBefore',
37
+ after: 'builder.canvas.insertAfter',
38
+ into: 'builder.canvas.insertInto',
39
+ };
40
+ /**
41
+ * The overlay's rendered markup, portal-free so the AD-D chrome contract test
42
+ * can walk it with `renderToStaticMarkup`: every class is `ec-*`, the root
43
+ * carries the `.ec-chrome` containment recipe, geometry is inline (no `var()`).
44
+ */
45
+ export function SelectionOverlayView(props) {
46
+ const { selected, hovered, affordances, propHighlight } = props;
47
+ return (_jsxs("div", { className: "ec-chrome ec-overlay", "data-ec-overlay": "", children: [propHighlight ? (_jsx("div", { className: "ec-overlay-prop", "data-ec-prop-highlight": "", style: frameStyle(propHighlight) })) : null, hovered ? _jsx("div", { className: "ec-overlay-frame", "data-ec-hover": "", style: frameStyle(hovered) }) : null, affordances.map((group) => group.items.map((item) => (_jsx(AddButton, { item: item, onInsert: () => group.onInsert(item.position) }, `${group.nodeId}:${item.position}`)))), selected ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "ec-overlay-frame", "data-ec-selected": "", style: frameStyle(selected.rect) }), _jsxs("div", { className: "ec-overlay-bar", "data-ec-frame-bar": "", "data-ec-placement": selected.bar.placement, style: { left: selected.bar.left, top: selected.bar.top }, children: [_jsx("span", { className: "ec-overlay-label", title: selected.label, children: selected.label }), _jsx("span", { className: "ec-overlay-sep", "aria-hidden": "true" }), _jsx(FrameButton, { label: translate('builder.pageTree.moveUp'), disabled: !selected.canMoveUp, onClick: props.onMoveUp, children: _jsx(OverlayIcon, { kind: "moveUp" }) }), _jsx(FrameButton, { label: translate('builder.pageTree.moveDown'), disabled: !selected.canMoveDown, onClick: props.onMoveDown, children: _jsx(OverlayIcon, { kind: "moveDown" }) }), _jsx(FrameButton, { label: translate('common.duplicate'), onClick: props.onDuplicate, children: _jsx(OverlayIcon, { kind: "duplicate" }) }), _jsx(FrameButton, { label: translate('common.delete'), onClick: props.onDelete, children: _jsx(OverlayIcon, { kind: "delete" }) })] })] })) : null] }));
48
+ }
49
+ function FrameButton({ label, disabled, onClick, children, }) {
50
+ return (_jsx("button", { type: "button", className: "ec-overlay-btn", "aria-label": label, title: label, disabled: disabled,
51
+ // Keep the click on the control: it must not fall through to the canvas
52
+ // selection listener (the bar sits over the page) nor start a text drag.
53
+ onMouseDown: (e) => e.preventDefault(), onClick: (e) => {
54
+ e.preventDefault();
55
+ e.stopPropagation();
56
+ onClick();
57
+ }, children: children }));
58
+ }
59
+ /**
60
+ * A "+" insertion affordance (EC-405): a small round button centered on a block
61
+ * boundary (before/after edge, or the center of an empty container). Centered on
62
+ * its anchor by the chrome CSS `translate(-50%,-50%)`; `pointer-events` re-enabled
63
+ * on the button while the overlay root stays click-through. The gesture guards
64
+ * mirror `FrameButton` — the press must not fall through to the canvas selection
65
+ * listener nor start a native drag.
66
+ */
67
+ function AddButton({ item, onInsert }) {
68
+ const label = translate(AFFORDANCE_LABELS[item.position]);
69
+ return (_jsx("button", { type: "button", className: "ec-overlay-add", "data-ec-affordance": item.position, "aria-label": label, title: label, style: { left: item.left, top: item.top }, onMouseDown: (e) => e.preventDefault(), onClick: (e) => {
70
+ e.preventDefault();
71
+ e.stopPropagation();
72
+ onInsert();
73
+ }, children: _jsx("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "M8 3.5v9M3.5 8h9" }) }) }));
74
+ }
75
+ /** Hand-rolled inline icons — no `class` attribute (chrome contract). */
76
+ function OverlayIcon({ kind }) {
77
+ const common = {
78
+ width: 13,
79
+ height: 13,
80
+ viewBox: '0 0 16 16',
81
+ fill: 'none',
82
+ stroke: 'currentColor',
83
+ strokeWidth: 1.6,
84
+ strokeLinecap: 'round',
85
+ strokeLinejoin: 'round',
86
+ 'aria-hidden': true,
87
+ };
88
+ switch (kind) {
89
+ case 'moveUp':
90
+ return (_jsx("svg", { ...common, children: _jsx("path", { d: "M4 10l4-4 4 4" }) }));
91
+ case 'moveDown':
92
+ return (_jsx("svg", { ...common, children: _jsx("path", { d: "M4 6l4 4 4-4" }) }));
93
+ case 'duplicate':
94
+ return (_jsxs("svg", { ...common, children: [_jsx("rect", { x: "6", y: "6", width: "8", height: "8", rx: "1.5" }), _jsx("path", { d: "M10 6V4a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2" })] }));
95
+ case 'delete':
96
+ return (_jsxs("svg", { ...common, children: [_jsx("path", { d: "M3 4.5h10" }), _jsx("path", { d: "M6.5 4.5V3a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v1.5" }), _jsx("path", { d: "M5 4.5l.5 8a1 1 0 0 0 1 .9h3a1 1 0 0 0 1-.9l.5-8" })] }));
97
+ }
98
+ }
99
+ /**
100
+ * The live overlay: tracks the selected + hovered node rects (glued through
101
+ * scroll/resize/async layout via {@link subscribeCanvasLayout} + a coalescing
102
+ * {@link createRectTracker}) and portals the frames into the iframe `body`.
103
+ * Suppressed for the composition root (no frame around the whole page — mirrors
104
+ * the tree, which offers the root no actions) and while an inline text/rich-text
105
+ * edit is live (the `[data-ec-editing]` outline owns that state).
106
+ */
107
+ export function SelectionOverlay({ doc, editing }) {
108
+ const builder = useBuilder();
109
+ const { present, selectedId, hoveredId, dispatch } = builder;
110
+ const studio = getStudioRegistry();
111
+ const affordance = useInsertAffordance();
112
+ const propSync = usePropSync();
113
+ const uiLocale = useUiLocale();
114
+ const [geometry, setGeometry] = useState(EMPTY_GEOMETRY);
115
+ // EC-408 reverse highlight: the focused prop's marker, but only for the
116
+ // selected node (the inspector only shows that node's props) and never while
117
+ // inline-editing (the [data-ec-editing] outline owns the node then).
118
+ const highlight = propSync?.highlighted && propSync.highlighted.nodeId === selectedId && !editing
119
+ ? propSync.highlighted
120
+ : null;
121
+ const highlightNodeId = highlight?.nodeId ?? null;
122
+ const highlightPropName = highlight?.propName ?? null;
123
+ // EC-405 scope 4: insertion affordances hide while ANY drag is live (the
124
+ // DnD before/after/into indicators own the "where it lands" story then).
125
+ const [dragging, setDragging] = useState(() => activeDrag() !== null);
126
+ useEffect(() => {
127
+ const offStart = onDragSessionStart(() => setDragging(true));
128
+ const offEnd = onDragSessionEnd(() => setDragging(false));
129
+ return () => {
130
+ offStart();
131
+ offEnd();
132
+ };
133
+ }, []);
134
+ // The selected node's controls/label come from the graph; the root and a
135
+ // never-found selection draw nothing. Hover never frames the currently
136
+ // selected node (one frame at a time) nor the root.
137
+ const selectedNode = selectedId && !editing ? getNode(present, selectedId) : null;
138
+ const controls = selectedNode ? frameControlsFor(present, selectedId) : null;
139
+ const drawSelected = selectedNode !== null && controls !== null && !controls.isRoot;
140
+ const hoveredNode = hoveredId && hoveredId !== selectedId ? getNode(present, hoveredId) : null;
141
+ const drawHover = hoveredNode !== null && !frameControlsFor(present, hoveredId).isRoot;
142
+ useLayoutEffect(() => {
143
+ const win = doc.defaultView;
144
+ if (!win)
145
+ return;
146
+ const selId = drawSelected ? selectedId : null;
147
+ const hovId = drawHover ? hoveredId : null;
148
+ const measure = () => {
149
+ const selected = measureNodeRect(doc, selId);
150
+ const hovered = measureNodeRect(doc, hovId);
151
+ return {
152
+ selected,
153
+ selectedBar: selected ? placeFrameBar(selected, { barHeight: FRAME_BAR_HEIGHT, docTop: win.scrollY }) : null,
154
+ hovered,
155
+ propHighlight: measurePropRect(doc, highlightNodeId, highlightPropName),
156
+ };
157
+ };
158
+ const targets = () => {
159
+ const out = [];
160
+ for (const id of [selId, hovId]) {
161
+ if (!id)
162
+ continue;
163
+ const el = doc.querySelector(`[data-ec-node-id="${id}"]`);
164
+ if (el)
165
+ out.push(el);
166
+ }
167
+ // Track the highlighted marker element too, so it re-glues on async layout.
168
+ if (highlightNodeId) {
169
+ const wrapper = doc.querySelector(`[data-ec-node-id="${highlightNodeId}"]`);
170
+ if (wrapper)
171
+ out.push(wrapper);
172
+ }
173
+ return out;
174
+ };
175
+ // Coalesce on the PARENT window's frame loop (not the iframe's): this
176
+ // component + its state live in the parent React tree, and the iframe's rAF
177
+ // is throttled/paused whenever the preview is offscreen or backgrounded. The
178
+ // compound rAF+timeout scheduler (scheduleFrame) keeps the flush paint-
179
+ // aligned on screen while guaranteeing it still runs when rAF is paused.
180
+ const host = win.parent ?? window;
181
+ const tracker = createRectTracker({
182
+ measure,
183
+ equals: geometryEquals,
184
+ onChange: setGeometry,
185
+ schedule: (flush) => scheduleFrame(host, flush),
186
+ subscribe: (invalidate) => subscribeCanvasLayout(doc, targets, invalidate),
187
+ });
188
+ return () => tracker.dispose();
189
+ // Re-subscribe when the tracked ids / graph / highlighted prop change so the
190
+ // ResizeObserver re-points at the current (possibly remounted) wrappers and
191
+ // the measure closure reads the current highlight target.
192
+ }, [doc, drawSelected, drawHover, selectedId, hoveredId, present, highlightNodeId, highlightPropName]);
193
+ // EC-407: the frame label shares the tree row's resolution — the SAME
194
+ // nodeDisplayTitle.primary the selected row shows (instance title when
195
+ // derivable, else the localized component name).
196
+ const label = selectedNode
197
+ ? nodeDisplayTitle(studio.registry.getManifest(selectedNode.componentId), selectedNode, uiLocale).primary
198
+ : '';
199
+ const selectedView = drawSelected && geometry.selected && geometry.selectedBar && controls
200
+ ? {
201
+ rect: geometry.selected,
202
+ bar: geometry.selectedBar,
203
+ label,
204
+ canMoveUp: controls.canMoveUp,
205
+ canMoveDown: controls.canMoveDown,
206
+ }
207
+ : null;
208
+ const run = (kind) => {
209
+ if (!selectedId)
210
+ return;
211
+ dispatch(frameControlAction(kind, selectedId));
212
+ };
213
+ // EC-405: the "+" affordances for the selected node and (separately) a hovered
214
+ // one — computed from the SAME measured rects the frames use. A group is drawn
215
+ // only for a node with insertable positions, and only when no drag/inline edit
216
+ // is live; a node with no provider above it (component preview) shows none.
217
+ const affordanceGroups = affordance && !dragging && !editing
218
+ ? [
219
+ affordanceGroup(present, selectedId, drawSelected ? geometry.selected : null, affordance),
220
+ affordanceGroup(present, hoveredId, drawHover ? geometry.hovered : null, affordance),
221
+ ].filter((g) => g !== null)
222
+ : [];
223
+ return createPortal(_jsx(SelectionOverlayView, { selected: selectedView, hovered: drawHover ? geometry.hovered : null, affordances: affordanceGroups, propHighlight: highlight ? geometry.propHighlight : null, onMoveUp: () => run('moveUp'), onMoveDown: () => run('moveDown'), onDuplicate: () => run('duplicate'), onDelete: () => run('delete') }), doc.body);
224
+ }
225
+ /**
226
+ * Build the affordance group for one node from its measured rect: the insertable
227
+ * positions the provider allows (slot rules already applied), each placed on the
228
+ * node's boundary along the parent's flow axis. Returns `null` when there is no
229
+ * rect yet or no insertable position.
230
+ */
231
+ function affordanceGroup(present, nodeId, rect, affordance) {
232
+ if (!nodeId || !rect)
233
+ return null;
234
+ const positions = affordance.positionsFor(nodeId);
235
+ if (positions.length === 0)
236
+ return null;
237
+ const flow = containerFlowAxis(getParentInfo(present, nodeId)?.parent);
238
+ return {
239
+ nodeId,
240
+ items: positions.map((position) => ({ position, ...placeAffordance(rect, position, flow) })),
241
+ onInsert: (position) => affordance.openAt(nodeId, position),
242
+ };
243
+ }
244
+ //# sourceMappingURL=selection-overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-overlay.js","sourceRoot":"","sources":["../../../src/components/builder/selection-overlay.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useLayoutEffect, useState } from 'react'\nimport type { CSSProperties, ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\nimport { containerFlowAxis } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { useBuilder } from './builder-store'\nimport { activeDrag, onDragSessionEnd, onDragSessionStart } from './drag-session'\nimport { useInsertAffordance } from './insert-affordance-context'\nimport type { InsertAffordanceApi } from './insert-affordance-context'\nimport type { InsertPosition } from './insert-context'\nimport type { AffordanceAnchor, FrameBarPlacement, FrameControlKind } from './canvas-overlay-geometry'\nimport {\n FRAME_BAR_HEIGHT,\n frameControlAction,\n frameControlsFor,\n placeAffordance,\n placeFrameBar,\n} from './canvas-overlay-geometry'\nimport {\n createRectTracker,\n measureNodeRect,\n measurePropRect,\n rectsEqual,\n scheduleFrame,\n subscribeCanvasLayout,\n} from './canvas-rect-tracking'\nimport type { Rect } from './canvas-rect-tracking'\nimport { usePropSync } from './prop-sync-context'\nimport { nodeDisplayTitle } from './node-title'\n\n/**\n * Canvas selection/hover overlay (EC-404) — a topmost layer portaled into the\n * iframe `body` that draws the selection and hover frames ABOVE the page from\n * measured node rects, so a full-bleed background or a higher-`z-index` sibling\n * can never paint over them (the structural fix for the in-document inset\n * outline the findings batch flagged). The selected frame carries a compact tag:\n * the component's display name plus move-up / move-down / duplicate / delete\n * controls that dispatch the EXISTING tree actions.\n *\n * EC-414 AD-B chrome contract: everything here is `ec-*` classes / `data-ec-*`\n * attributes / inline geometry only, styled by `canvas-chrome-css.ts`, with the\n * `.ec-chrome` containment recipe on the root; no studio Tailwind utilities and\n * no studio React UI primitives cross into the iframe. The rect-tracking core\n * ({@link createRectTracker}) is a pure, reusable module — EC-405 rides this\n * overlay layer next, and EC-406 can share the tracking.\n */\n\n/** The overlay's full measured geometry (both frames + the bar placement). */\nexport interface OverlayGeometry {\n selected: Rect | null\n /** Chrome-bar placement for the selected frame (null when nothing to draw). */\n selectedBar: FrameBarPlacement | null\n hovered: Rect | null\n /** EC-408: the focused prop's marker rect (reverse highlight), or null. */\n propHighlight: Rect | null\n}\n\nconst EMPTY_GEOMETRY: OverlayGeometry = {\n selected: null,\n selectedBar: null,\n hovered: null,\n propHighlight: null,\n}\n\nfunction barsEqual(a: FrameBarPlacement | null, b: FrameBarPlacement | null): boolean {\n if (a === null || b === null) return a === b\n return a.top === b.top && a.left === b.left && a.placement === b.placement\n}\n\nfunction geometryEquals(a: OverlayGeometry, b: OverlayGeometry): boolean {\n return (\n rectsEqual(a.selected, b.selected) &&\n rectsEqual(a.hovered, b.hovered) &&\n rectsEqual(a.propHighlight, b.propHighlight) &&\n barsEqual(a.selectedBar, b.selectedBar)\n )\n}\n\n/** A drawn selection frame plus its control enablement. */\nexport interface SelectionFrameView {\n rect: Rect\n bar: FrameBarPlacement\n label: string\n canMoveUp: boolean\n canMoveDown: boolean\n}\n\n/** One \"+\" affordance button: its position and its placed anchor point. */\nexport interface AffordanceItemView extends AffordanceAnchor {\n position: InsertPosition\n}\n\n/**\n * The affordance buttons for one node — grouped so a single `onInsert` binds the\n * node id the inserts target (the selected node and, separately, a hovered one).\n */\nexport interface AffordanceGroupView {\n nodeId: string\n items: AffordanceItemView[]\n onInsert: (position: InsertPosition) => void\n}\n\nexport interface SelectionOverlayViewProps {\n selected: SelectionFrameView | null\n hovered: Rect | null\n /** In-canvas insertion affordances (EC-405); empty when suppressed (drag/editing). */\n affordances: AffordanceGroupView[]\n /** EC-408: the focused prop's marker rect (reverse highlight), or null. */\n propHighlight?: Rect | null\n onMoveUp: () => void\n onMoveDown: () => void\n onDuplicate: () => void\n onDelete: () => void\n}\n\nfunction frameStyle(rect: Rect): CSSProperties {\n return { left: rect.left, top: rect.top, width: rect.width, height: rect.height }\n}\n\nconst AFFORDANCE_LABELS: Record<InsertPosition, string> = {\n before: 'builder.canvas.insertBefore',\n after: 'builder.canvas.insertAfter',\n into: 'builder.canvas.insertInto',\n}\n\n/**\n * The overlay's rendered markup, portal-free so the AD-D chrome contract test\n * can walk it with `renderToStaticMarkup`: every class is `ec-*`, the root\n * carries the `.ec-chrome` containment recipe, geometry is inline (no `var()`).\n */\nexport function SelectionOverlayView(props: SelectionOverlayViewProps) {\n const { selected, hovered, affordances, propHighlight } = props\n return (\n <div className=\"ec-chrome ec-overlay\" data-ec-overlay=\"\">\n {/* EC-408 reverse highlight: a quiet filled tint on the focused prop's\n element, drawn UNDER the selection frame so its border stays crisp. */}\n {propHighlight ? (\n <div className=\"ec-overlay-prop\" data-ec-prop-highlight=\"\" style={frameStyle(propHighlight)} />\n ) : null}\n {hovered ? <div className=\"ec-overlay-frame\" data-ec-hover=\"\" style={frameStyle(hovered)} /> : null}\n {affordances.map((group) =>\n group.items.map((item) => (\n <AddButton\n key={`${group.nodeId}:${item.position}`}\n item={item}\n onInsert={() => group.onInsert(item.position)}\n />\n )),\n )}\n {selected ? (\n <>\n <div className=\"ec-overlay-frame\" data-ec-selected=\"\" style={frameStyle(selected.rect)} />\n <div\n className=\"ec-overlay-bar\"\n data-ec-frame-bar=\"\"\n data-ec-placement={selected.bar.placement}\n style={{ left: selected.bar.left, top: selected.bar.top }}\n >\n <span className=\"ec-overlay-label\" title={selected.label}>\n {selected.label}\n </span>\n <span className=\"ec-overlay-sep\" aria-hidden=\"true\" />\n <FrameButton\n label={translate('builder.pageTree.moveUp')}\n disabled={!selected.canMoveUp}\n onClick={props.onMoveUp}\n >\n <OverlayIcon kind=\"moveUp\" />\n </FrameButton>\n <FrameButton\n label={translate('builder.pageTree.moveDown')}\n disabled={!selected.canMoveDown}\n onClick={props.onMoveDown}\n >\n <OverlayIcon kind=\"moveDown\" />\n </FrameButton>\n <FrameButton label={translate('common.duplicate')} onClick={props.onDuplicate}>\n <OverlayIcon kind=\"duplicate\" />\n </FrameButton>\n <FrameButton label={translate('common.delete')} onClick={props.onDelete}>\n <OverlayIcon kind=\"delete\" />\n </FrameButton>\n </div>\n </>\n ) : null}\n </div>\n )\n}\n\nfunction FrameButton({\n label,\n disabled,\n onClick,\n children,\n}: {\n label: string\n disabled?: boolean\n onClick: () => void\n children: ReactNode\n}) {\n return (\n <button\n type=\"button\"\n className=\"ec-overlay-btn\"\n aria-label={label}\n title={label}\n disabled={disabled}\n // Keep the click on the control: it must not fall through to the canvas\n // selection listener (the bar sits over the page) nor start a text drag.\n onMouseDown={(e) => e.preventDefault()}\n onClick={(e) => {\n e.preventDefault()\n e.stopPropagation()\n onClick()\n }}\n >\n {children}\n </button>\n )\n}\n\n/**\n * A \"+\" insertion affordance (EC-405): a small round button centered on a block\n * boundary (before/after edge, or the center of an empty container). Centered on\n * its anchor by the chrome CSS `translate(-50%,-50%)`; `pointer-events` re-enabled\n * on the button while the overlay root stays click-through. The gesture guards\n * mirror `FrameButton` — the press must not fall through to the canvas selection\n * listener nor start a native drag.\n */\nfunction AddButton({ item, onInsert }: { item: AffordanceItemView; onInsert: () => void }) {\n const label = translate(AFFORDANCE_LABELS[item.position])\n return (\n <button\n type=\"button\"\n className=\"ec-overlay-add\"\n data-ec-affordance={item.position}\n aria-label={label}\n title={label}\n style={{ left: item.left, top: item.top }}\n onMouseDown={(e) => e.preventDefault()}\n onClick={(e) => {\n e.preventDefault()\n e.stopPropagation()\n onInsert()\n }}\n >\n <svg\n width={14}\n height={14}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M8 3.5v9M3.5 8h9\" />\n </svg>\n </button>\n )\n}\n\n/** Hand-rolled inline icons — no `class` attribute (chrome contract). */\nfunction OverlayIcon({ kind }: { kind: FrameControlKind }) {\n const common = {\n width: 13,\n height: 13,\n viewBox: '0 0 16 16',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.6,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n 'aria-hidden': true,\n }\n switch (kind) {\n case 'moveUp':\n return (\n <svg {...common}>\n <path d=\"M4 10l4-4 4 4\" />\n </svg>\n )\n case 'moveDown':\n return (\n <svg {...common}>\n <path d=\"M4 6l4 4 4-4\" />\n </svg>\n )\n case 'duplicate':\n return (\n <svg {...common}>\n <rect x=\"6\" y=\"6\" width=\"8\" height=\"8\" rx=\"1.5\" />\n <path d=\"M10 6V4a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2\" />\n </svg>\n )\n case 'delete':\n return (\n <svg {...common}>\n <path d=\"M3 4.5h10\" />\n <path d=\"M6.5 4.5V3a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v1.5\" />\n <path d=\"M5 4.5l.5 8a1 1 0 0 0 1 .9h3a1 1 0 0 0 1-.9l.5-8\" />\n </svg>\n )\n }\n}\n\n/**\n * The live overlay: tracks the selected + hovered node rects (glued through\n * scroll/resize/async layout via {@link subscribeCanvasLayout} + a coalescing\n * {@link createRectTracker}) and portals the frames into the iframe `body`.\n * Suppressed for the composition root (no frame around the whole page — mirrors\n * the tree, which offers the root no actions) and while an inline text/rich-text\n * edit is live (the `[data-ec-editing]` outline owns that state).\n */\nexport function SelectionOverlay({ doc, editing }: { doc: Document; editing: boolean }) {\n const builder = useBuilder()\n const { present, selectedId, hoveredId, dispatch } = builder\n const studio = getStudioRegistry()\n const affordance = useInsertAffordance()\n const propSync = usePropSync()\n const uiLocale = useUiLocale()\n const [geometry, setGeometry] = useState<OverlayGeometry>(EMPTY_GEOMETRY)\n\n // EC-408 reverse highlight: the focused prop's marker, but only for the\n // selected node (the inspector only shows that node's props) and never while\n // inline-editing (the [data-ec-editing] outline owns the node then).\n const highlight =\n propSync?.highlighted && propSync.highlighted.nodeId === selectedId && !editing\n ? propSync.highlighted\n : null\n const highlightNodeId = highlight?.nodeId ?? null\n const highlightPropName = highlight?.propName ?? null\n\n // EC-405 scope 4: insertion affordances hide while ANY drag is live (the\n // DnD before/after/into indicators own the \"where it lands\" story then).\n const [dragging, setDragging] = useState(() => activeDrag() !== null)\n useEffect(() => {\n const offStart = onDragSessionStart(() => setDragging(true))\n const offEnd = onDragSessionEnd(() => setDragging(false))\n return () => {\n offStart()\n offEnd()\n }\n }, [])\n\n // The selected node's controls/label come from the graph; the root and a\n // never-found selection draw nothing. Hover never frames the currently\n // selected node (one frame at a time) nor the root.\n const selectedNode = selectedId && !editing ? getNode(present, selectedId) : null\n const controls = selectedNode ? frameControlsFor(present, selectedId!) : null\n const drawSelected = selectedNode !== null && controls !== null && !controls.isRoot\n const hoveredNode = hoveredId && hoveredId !== selectedId ? getNode(present, hoveredId) : null\n const drawHover = hoveredNode !== null && !frameControlsFor(present, hoveredId!).isRoot\n\n useLayoutEffect(() => {\n const win = doc.defaultView\n if (!win) return\n const selId = drawSelected ? selectedId : null\n const hovId = drawHover ? hoveredId : null\n const measure = (): OverlayGeometry => {\n const selected = measureNodeRect(doc, selId)\n const hovered = measureNodeRect(doc, hovId)\n return {\n selected,\n selectedBar: selected ? placeFrameBar(selected, { barHeight: FRAME_BAR_HEIGHT, docTop: win.scrollY }) : null,\n hovered,\n propHighlight: measurePropRect(doc, highlightNodeId, highlightPropName),\n }\n }\n const targets = (): Element[] => {\n const out: Element[] = []\n for (const id of [selId, hovId]) {\n if (!id) continue\n const el = doc.querySelector(`[data-ec-node-id=\"${id}\"]`)\n if (el) out.push(el)\n }\n // Track the highlighted marker element too, so it re-glues on async layout.\n if (highlightNodeId) {\n const wrapper = doc.querySelector(`[data-ec-node-id=\"${highlightNodeId}\"]`)\n if (wrapper) out.push(wrapper)\n }\n return out\n }\n // Coalesce on the PARENT window's frame loop (not the iframe's): this\n // component + its state live in the parent React tree, and the iframe's rAF\n // is throttled/paused whenever the preview is offscreen or backgrounded. The\n // compound rAF+timeout scheduler (scheduleFrame) keeps the flush paint-\n // aligned on screen while guaranteeing it still runs when rAF is paused.\n const host = win.parent ?? window\n const tracker = createRectTracker<OverlayGeometry>({\n measure,\n equals: geometryEquals,\n onChange: setGeometry,\n schedule: (flush) => scheduleFrame(host, flush),\n subscribe: (invalidate) => subscribeCanvasLayout(doc, targets, invalidate),\n })\n return () => tracker.dispose()\n // Re-subscribe when the tracked ids / graph / highlighted prop change so the\n // ResizeObserver re-points at the current (possibly remounted) wrappers and\n // the measure closure reads the current highlight target.\n }, [doc, drawSelected, drawHover, selectedId, hoveredId, present, highlightNodeId, highlightPropName])\n\n // EC-407: the frame label shares the tree row's resolution — the SAME\n // nodeDisplayTitle.primary the selected row shows (instance title when\n // derivable, else the localized component name).\n const label = selectedNode\n ? nodeDisplayTitle(studio.registry.getManifest(selectedNode.componentId), selectedNode, uiLocale).primary\n : ''\n\n const selectedView: SelectionFrameView | null =\n drawSelected && geometry.selected && geometry.selectedBar && controls\n ? {\n rect: geometry.selected,\n bar: geometry.selectedBar,\n label,\n canMoveUp: controls.canMoveUp,\n canMoveDown: controls.canMoveDown,\n }\n : null\n\n const run = (kind: FrameControlKind): void => {\n if (!selectedId) return\n dispatch(frameControlAction(kind, selectedId))\n }\n\n // EC-405: the \"+\" affordances for the selected node and (separately) a hovered\n // one — computed from the SAME measured rects the frames use. A group is drawn\n // only for a node with insertable positions, and only when no drag/inline edit\n // is live; a node with no provider above it (component preview) shows none.\n const affordanceGroups: AffordanceGroupView[] =\n affordance && !dragging && !editing\n ? [\n affordanceGroup(present, selectedId, drawSelected ? geometry.selected : null, affordance),\n affordanceGroup(present, hoveredId, drawHover ? geometry.hovered : null, affordance),\n ].filter((g): g is AffordanceGroupView => g !== null)\n : []\n\n return createPortal(\n <SelectionOverlayView\n selected={selectedView}\n hovered={drawHover ? geometry.hovered : null}\n affordances={affordanceGroups}\n propHighlight={highlight ? geometry.propHighlight : null}\n onMoveUp={() => run('moveUp')}\n onMoveDown={() => run('moveDown')}\n onDuplicate={() => run('duplicate')}\n onDelete={() => run('delete')}\n />,\n doc.body,\n )\n}\n\n/**\n * Build the affordance group for one node from its measured rect: the insertable\n * positions the provider allows (slot rules already applied), each placed on the\n * node's boundary along the parent's flow axis. Returns `null` when there is no\n * rect yet or no insertable position.\n */\nfunction affordanceGroup(\n present: ComponentNode,\n nodeId: string | null,\n rect: Rect | null,\n affordance: InsertAffordanceApi,\n): AffordanceGroupView | null {\n if (!nodeId || !rect) return null\n const positions = affordance.positionsFor(nodeId)\n if (positions.length === 0) return null\n const flow = containerFlowAxis(getParentInfo(present, nodeId)?.parent)\n return {\n nodeId,\n items: positions.map((position) => ({ position, ...placeAffordance(rect, position, flow) })),\n onInsert: (position) => affordance.openAt(nodeId, position),\n }\n}\n"]}