@elytracms/studio 0.0.31 → 0.0.33

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 (116) hide show
  1. package/dist/components/app-views/default-modules.js +63 -21
  2. package/dist/components/app-views/default-modules.js.map +1 -1
  3. package/dist/components/assets/asset-library.js +21 -13
  4. package/dist/components/assets/asset-library.js.map +1 -1
  5. package/dist/components/assets/asset-thumb.js +3 -2
  6. package/dist/components/assets/asset-thumb.js.map +1 -1
  7. package/dist/components/backend-gate.js +4 -3
  8. package/dist/components/backend-gate.js.map +1 -1
  9. package/dist/components/builder/blocks-panel.js +11 -2
  10. package/dist/components/builder/blocks-panel.js.map +1 -1
  11. package/dist/components/builder/canvas.js +6 -5
  12. package/dist/components/builder/canvas.js.map +1 -1
  13. package/dist/components/builder/clipboard-actions.js +12 -6
  14. package/dist/components/builder/clipboard-actions.js.map +1 -1
  15. package/dist/components/builder/composition-field-editor.js +7 -4
  16. package/dist/components/builder/composition-field-editor.js.map +1 -1
  17. package/dist/components/builder/fixture-thumbnail.js +2 -1
  18. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  19. package/dist/components/builder/layout-inspector.d.ts +5 -5
  20. package/dist/components/builder/layout-inspector.js +21 -20
  21. package/dist/components/builder/layout-inspector.js.map +1 -1
  22. package/dist/components/builder/page-tree.js +5 -4
  23. package/dist/components/builder/page-tree.js.map +1 -1
  24. package/dist/components/builder/property-inspector.js +10 -10
  25. package/dist/components/builder/property-inspector.js.map +1 -1
  26. package/dist/components/builder/reuse-context.js +9 -9
  27. package/dist/components/builder/reuse-context.js.map +1 -1
  28. package/dist/components/builder/rich-text-toolbar.js +4 -3
  29. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  30. package/dist/components/builder/spacing-handles.js +4 -3
  31. package/dist/components/builder/spacing-handles.js.map +1 -1
  32. package/dist/components/cms/asset-picker.js +8 -7
  33. package/dist/components/cms/asset-picker.js.map +1 -1
  34. package/dist/components/cms/bulk-bar.js +8 -5
  35. package/dist/components/cms/bulk-bar.js.map +1 -1
  36. package/dist/components/cms/cell.js +9 -5
  37. package/dist/components/cms/cell.js.map +1 -1
  38. package/dist/components/cms/cms-store.js +4 -3
  39. package/dist/components/cms/cms-store.js.map +1 -1
  40. package/dist/components/cms/collection-table.js +18 -7
  41. package/dist/components/cms/collection-table.js.map +1 -1
  42. package/dist/components/cms/detail-editor.js +38 -27
  43. package/dist/components/cms/detail-editor.js.map +1 -1
  44. package/dist/components/cms/editable-cell.js +4 -4
  45. package/dist/components/cms/editable-cell.js.map +1 -1
  46. package/dist/components/cms/field-control.js +29 -21
  47. package/dist/components/cms/field-control.js.map +1 -1
  48. package/dist/components/cms/filter-builder.js +9 -8
  49. package/dist/components/cms/filter-builder.js.map +1 -1
  50. package/dist/components/cms/reference-confirm.js +24 -10
  51. package/dist/components/cms/reference-confirm.js.map +1 -1
  52. package/dist/components/cms/relation-picker.js +5 -4
  53. package/dist/components/cms/relation-picker.js.map +1 -1
  54. package/dist/components/cms/scalar-value-editor.js +5 -5
  55. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  56. package/dist/components/cms/version-compare.js +36 -21
  57. package/dist/components/cms/version-compare.js.map +1 -1
  58. package/dist/components/cms/versions-pane.js +8 -5
  59. package/dist/components/cms/versions-pane.js.map +1 -1
  60. package/dist/components/cms/view-bar.js +3 -2
  61. package/dist/components/cms/view-bar.js.map +1 -1
  62. package/dist/components/components/component-detail.js +3 -2
  63. package/dist/components/components/component-detail.js.map +1 -1
  64. package/dist/components/components/component-status.js +8 -13
  65. package/dist/components/components/component-status.js.map +1 -1
  66. package/dist/components/components/components-index.js +5 -1
  67. package/dist/components/components/components-index.js.map +1 -1
  68. package/dist/components/components/field-inspector.js +4 -4
  69. package/dist/components/components/field-inspector.js.map +1 -1
  70. package/dist/components/components/fixture-runner.js +4 -1
  71. package/dist/components/components/fixture-runner.js.map +1 -1
  72. package/dist/components/components/registry-browser.js +4 -3
  73. package/dist/components/components/registry-browser.js.map +1 -1
  74. package/dist/components/delivery-health-banner.js +2 -1
  75. package/dist/components/delivery-health-banner.js.map +1 -1
  76. package/dist/components/dev-login-badge.js +2 -1
  77. package/dist/components/dev-login-badge.js.map +1 -1
  78. package/dist/components/extensions/extensions-view.js +22 -21
  79. package/dist/components/extensions/extensions-view.js.map +1 -1
  80. package/dist/components/guards.js +3 -2
  81. package/dist/components/guards.js.map +1 -1
  82. package/dist/components/operation-error.js +3 -2
  83. package/dist/components/operation-error.js.map +1 -1
  84. package/dist/components/playground-badge.js +2 -1
  85. package/dist/components/playground-badge.js.map +1 -1
  86. package/dist/components/project-list.js +5 -4
  87. package/dist/components/project-list.js.map +1 -1
  88. package/dist/components/project-session.js +3 -2
  89. package/dist/components/project-session.js.map +1 -1
  90. package/dist/components/project-switcher.js +4 -3
  91. package/dist/components/project-switcher.js.map +1 -1
  92. package/dist/components/redirects/redirects-module.js +3 -2
  93. package/dist/components/redirects/redirects-module.js.map +1 -1
  94. package/dist/components/references/references-panel.js +7 -6
  95. package/dist/components/references/references-panel.js.map +1 -1
  96. package/dist/components/section-nav.js +8 -5
  97. package/dist/components/section-nav.js.map +1 -1
  98. package/dist/components/section-state.js +13 -32
  99. package/dist/components/section-state.js.map +1 -1
  100. package/dist/components/settings-form.js +2 -1
  101. package/dist/components/settings-form.js.map +1 -1
  102. package/dist/components/studio-project-screen.js +3 -2
  103. package/dist/components/studio-project-screen.js.map +1 -1
  104. package/dist/components/studio-shell.js +3 -2
  105. package/dist/components/studio-shell.js.map +1 -1
  106. package/dist/components/users/users-module.js +5 -4
  107. package/dist/components/users/users-module.js.map +1 -1
  108. package/dist/components/workspace-gate.js +2 -1
  109. package/dist/components/workspace-gate.js.map +1 -1
  110. package/dist/lib/i18n/i18n.d.ts +18 -4
  111. package/dist/lib/i18n/i18n.js +22 -16
  112. package/dist/lib/i18n/i18n.js.map +1 -1
  113. package/dist/lib/i18n/messages.en.d.ts +11 -4
  114. package/dist/lib/i18n/messages.en.js +636 -6
  115. package/dist/lib/i18n/messages.en.js.map +1 -1
  116. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"composition-field-editor.js","sourceRoot":"","sources":["../../../src/components/builder/composition-field-editor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport { AlertTriangle, Plus, Redo2, SlidersHorizontal, Undo2 } from 'lucide-react'\nimport { DEFAULT_SLOT, blockRootsOf } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { BlocksFieldDef, Cardinality } from '@elytracms/core/cms-core'\nimport { Button, Panel, Row, Spacer, Text, Toolbar, ToolbarGroup, ToolbarSeparator } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { BuilderProvider, useBuilder } from './builder-store'\nimport { useCanvasViewports } from './canvas-breakpoints'\nimport type { ViewportOption } from './canvas-breakpoints'\nimport { BuilderCanvas } from './canvas'\nimport { PageTree } from './page-tree'\nimport { PropertyInspector } from './property-inspector'\nimport { InspectorSidebar } from '../inspector/inspector-sidebar'\nimport type { InspectorPane } from '../inspector/inspector-sidebar'\nimport { InsertMenu } from './insert-menu'\nimport type { InsertContext } from './insert-menu'\nimport { resolveInsertContext } from './insert-context'\nimport { CompositionScopeProvider } from './composition-scope'\nimport { ReuseProvider } from './reuse-context'\n\n/**\n * EC-188 — the block-field surface: one canvas engine mounted on a composition\n * **field value** (AD-12), not the project graph.\n *\n * The composition tree is wrapped in a synthetic single-page graph and edited by\n * a SCOPED `BuilderProvider` with **no `projectId`** (so the graph persister is a\n * no-op — builder-store.tsx). Every committed edit is lifted out via `onChange`\n * into the CMS document draft; the document's own Save persists it (the tree is\n * stored as document data, `v.any()`). This reuses the real canvas + inspector +\n * blocks panel unchanged — the same EC-158–164 mechanics — without touching the\n * project-wide builder store. Surfaced on a dedicated \"Canvas\" fieldset tab in\n * the CMS detail editor so the page builder gets full screen real estate rather\n * than being crammed into the field list.\n */\n\n/** Stable scope value (a fresh object each render would re-render consumers). */\nconst COMPOSITION_SCOPE = { active: true } as const\n\nconst SYNTH_ROOT_ID = 'composition.root'\n/** The implicit root container the field's blocks compose inside (like a page root). */\nconst SYNTH_ROOT_COMPONENT = 'base.primitives.Stack'\n\n/**\n * Wrap the field value's blocks into a synthetic root container (EC-287). The\n * builder store edits this single root-bearing composition directly; its\n * children are the field's blocks, lifted back out via `onChange`.\n */\nfunction buildSyntheticRoot(value: unknown): ComponentNode {\n return {\n id: SYNTH_ROOT_ID,\n componentId: SYNTH_ROOT_COMPONENT,\n slots: { [DEFAULT_SLOT]: blockRootsOf(value) },\n }\n}\n\n/** Unwrap the synthetic root's children back to a field value per cardinality. */\nfunction unwrapValue(blocks: readonly ComponentNode[], cardinality: Cardinality): unknown {\n return cardinality === 'one' ? blocks[0] : [...blocks]\n}\n\nexport function CompositionFieldEditor({\n field,\n value,\n onChange,\n readOnly,\n trailingPanes,\n}: {\n field: BlocksFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n /** Inspector panes the document editor appends after the canvas-supplied\n * `Properties` pane (EC-224 — the frame-owned `Versions` pane). */\n trailingPanes?: readonly InspectorPane[]\n}) {\n // Build the synthetic root ONCE from the value at mount. The editor is\n // uncontrolled thereafter — it is the source of truth while open and lifts\n // changes out via onChange; re-opening the tab remounts from the current draft.\n // (BuilderProvider reads `composition` only in its lazy initial state, so later\n // prop changes are ignored regardless.)\n const initialRoot = useMemo(() => buildSyntheticRoot(value), [])\n\n return (\n <BuilderProvider composition={initialRoot}>\n <CompositionLift cardinality={field.cardinality} onChange={onChange} readOnly={readOnly} />\n <ReuseProvider>\n <CompositionScopeProvider value={COMPOSITION_SCOPE}>\n <CompositionEditorShell\n readOnly={readOnly}\n fieldAllow={field.allow}\n trailingPanes={trailingPanes}\n />\n </CompositionScopeProvider>\n </ReuseProvider>\n </BuilderProvider>\n )\n}\n\n/**\n * Lifts committed edits out of the scoped store into the CMS draft. The first\n * render establishes the (container-normalized) baseline so simply opening the\n * tab never marks the document dirty; every subsequent change to the block list\n * emits the unwrapped value.\n */\nfunction CompositionLift({\n cardinality,\n onChange,\n readOnly,\n}: {\n cardinality: Cardinality\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const builder = useBuilder()\n const blocks = builder.present.slots?.[DEFAULT_SLOT] ?? EMPTY_BLOCKS\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const baselineRef = useRef<readonly ComponentNode[] | null>(null)\n\n useEffect(() => {\n // EC-226: never lift edits out of a READ-ONLY canvas (the Published view).\n // The canvas's keyboard listeners (clipboard paste/cut) aren't blocked by the\n // visual `pointer-events-none`, so without this a Cmd+V could mutate the\n // graph; refusing to lift keeps the document draft intact regardless. The\n // editor remounts per perspective, so any transient in-canvas change is\n // discarded on switch.\n if (readOnly) return\n if (baselineRef.current === null) {\n baselineRef.current = blocks\n return\n }\n if (blocks !== baselineRef.current) {\n baselineRef.current = blocks\n onChangeRef.current(unwrapValue(blocks, cardinality))\n }\n }, [blocks, cardinality, readOnly])\n\n return null\n}\n\nconst EMPTY_BLOCKS: readonly ComponentNode[] = []\n\n/** A breakpoint preview-width toggle (EC-211/EC-250). Switching the viewport\n * resizes only the preview frame (not an undoable edit, writes no graph command).\n * The label is the breakpoint name (`Full`/`sm`/`md`/…) with the real px width in\n * the tooltip — so the switcher reflects the site's actual screens, not a fixed\n * desktop/tablet/phone triple. */\nfunction ViewportButton({ option }: { option: ViewportOption }) {\n const builder = useBuilder()\n const active = builder.viewport === option.key\n return (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`${option.label} preview`}\n aria-pressed={active}\n title={option.px !== undefined ? `${option.label} · ${option.px}px` : `${option.label} width`}\n onClick={() => builder.dispatch({ type: 'viewport', viewport: option.key })}\n className={cn('px-2 text-xs', active && 'bg-accent text-accent-foreground')}\n >\n {option.label}\n </Button>\n )\n}\n\n/** The centered breakpoint switcher (EC-250): the resolved viewport set, plus a\n * visible validation issue when a project declared breakpoints but none are usable. */\nfunction BreakpointSwitcher() {\n const { viewports, issue } = useCanvasViewports()\n return (\n <Row gap=\"1\" align=\"center\">\n <ToolbarGroup>\n {viewports.map((v) => (\n <ViewportButton key={v.key} option={v} />\n ))}\n </ToolbarGroup>\n {issue ? (\n <Row gap=\"1\" align=\"center\" className=\"text-amber-600 dark:text-amber-400\" title={issue}>\n <AlertTriangle aria-hidden className=\"size-3.5\" />\n <Text size=\"xs\">breakpoints</Text>\n </Row>\n ) : null}\n </Row>\n )\n}\n\n/**\n * The slim editor surface — the canvas + layers + inspector, plus Insert and\n * undo/redo. It deliberately drops the project-builder *lifecycle* chrome\n * (publish, versions, AI, page SEO): the document owns the lifecycle, not the\n * field. It keeps spatial/preview controls — including the breakpoint\n * preview-width switcher (EC-211), a canvas concern, not a lifecycle one.\n */\nfunction CompositionEditorShell({\n readOnly,\n fieldAllow,\n trailingPanes,\n}: {\n readOnly: boolean\n /** The field's top-level component vocabulary (EC-188) — the leash applied to\n * inserts at the composition root so forbidden components aren't even offered. */\n fieldAllow?: readonly string[]\n /** Document-level panes appended after `Properties` (EC-224). */\n trailingPanes?: readonly InspectorPane[]\n}) {\n const builder = useBuilder()\n const dispatch = builder.dispatch\n const [insertContext, setInsertContext] = useState<InsertContext | null>(null)\n const [insertOpen, setInsertOpen] = useState(false)\n const rootId = builder.present.id\n\n // The canvas SUPPLIES the `Properties` pane (EC-224); the document editor\n // appends its own (`Versions`). Properties dims under read-only just as the\n // whole inspector column used to — but the appended panes stay interactive.\n const propertiesPane = useMemo<InspectorPane>(\n () => ({\n id: 'properties',\n label: 'Properties',\n icon: <SlidersHorizontal className=\"size-3.5\" />,\n render: () => (\n <div\n className={cn(\n 'h-full min-h-0 overflow-hidden',\n readOnly && 'pointer-events-none opacity-60',\n )}\n >\n <PropertyInspector />\n </div>\n ),\n }),\n [readOnly],\n )\n const panes = useMemo<InspectorPane[]>(\n () => [propertiesPane, ...(trailingPanes ?? [])],\n [propertiesPane, trailingPanes],\n )\n\n function openInsert(nodeId: string) {\n const ctx = resolveInsertContext(builder, nodeId, fieldAllow)\n if (ctx) {\n setInsertContext(ctx)\n setInsertOpen(true)\n }\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 border-b border-border\">\n <Toolbar>\n <ToolbarGroup>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label=\"Undo\"\n title=\"Undo\"\n disabled={readOnly || !builder.canUndo}\n onClick={() => dispatch({ type: 'undo' })}\n >\n <Undo2 />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label=\"Redo\"\n title=\"Redo\"\n disabled={readOnly || !builder.canRedo}\n onClick={() => dispatch({ type: 'redo' })}\n >\n <Redo2 />\n </Button>\n </ToolbarGroup>\n <ToolbarSeparator />\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={readOnly}\n onClick={() => openInsert(builder.selectedId ?? rootId)}\n >\n <Plus /> Insert\n </Button>\n {/* Breakpoint preview-width switcher (EC-211/EC-250), centered over the\n canvas via flanking spacers. A preview control, not an edit — stays\n active even in read-only published preview. */}\n <Spacer />\n <BreakpointSwitcher />\n <Spacer />\n </Toolbar>\n </div>\n\n <div className=\"grid min-h-0 flex-1 grid-cols-[minmax(200px,240px)_1fr_minmax(280px,320px)] gap-px bg-border\">\n <Panel\n className={cn('rounded-none border-0', readOnly && 'pointer-events-none opacity-60')}\n >\n <PageTree onAddChild={openInsert} />\n </Panel>\n <div\n className={cn(\n 'flex min-h-0 flex-col bg-background',\n readOnly && 'pointer-events-none opacity-60',\n )}\n >\n {/* EC-300/AD-16: the read-only Published view resolves references against\n the published perspective (honest preview); the editable Draft view\n resolves drafts (a just-made reusable shows immediately). */}\n <BuilderCanvas perspective={readOnly ? 'published' : 'draft'} />\n </div>\n <InspectorSidebar panes={panes} />\n </div>\n\n <InsertMenu context={insertContext} open={insertOpen} onOpenChange={setInsertOpen} />\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"composition-field-editor.js","sourceRoot":"","sources":["../../../src/components/builder/composition-field-editor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport { AlertTriangle, Plus, Redo2, SlidersHorizontal, Undo2 } from 'lucide-react'\nimport { DEFAULT_SLOT, blockRootsOf } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { BlocksFieldDef, Cardinality } from '@elytracms/core/cms-core'\nimport { Button, Panel, Row, Spacer, Text, Toolbar, ToolbarGroup, ToolbarSeparator } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { BuilderProvider, useBuilder } from './builder-store'\nimport { useCanvasViewports } from './canvas-breakpoints'\nimport type { ViewportOption } from './canvas-breakpoints'\nimport { BuilderCanvas } from './canvas'\nimport { PageTree } from './page-tree'\nimport { PropertyInspector } from './property-inspector'\nimport { InspectorSidebar } from '../inspector/inspector-sidebar'\nimport type { InspectorPane } from '../inspector/inspector-sidebar'\nimport { InsertMenu } from './insert-menu'\nimport type { InsertContext } from './insert-menu'\nimport { resolveInsertContext } from './insert-context'\nimport { CompositionScopeProvider } from './composition-scope'\nimport { ReuseProvider } from './reuse-context'\n\n/**\n * EC-188 — the block-field surface: one canvas engine mounted on a composition\n * **field value** (AD-12), not the project graph.\n *\n * The composition tree is wrapped in a synthetic single-page graph and edited by\n * a SCOPED `BuilderProvider` with **no `projectId`** (so the graph persister is a\n * no-op — builder-store.tsx). Every committed edit is lifted out via `onChange`\n * into the CMS document draft; the document's own Save persists it (the tree is\n * stored as document data, `v.any()`). This reuses the real canvas + inspector +\n * blocks panel unchanged — the same EC-158–164 mechanics — without touching the\n * project-wide builder store. Surfaced on a dedicated \"Canvas\" fieldset tab in\n * the CMS detail editor so the page builder gets full screen real estate rather\n * than being crammed into the field list.\n */\n\n/** Stable scope value (a fresh object each render would re-render consumers). */\nconst COMPOSITION_SCOPE = { active: true } as const\n\nconst SYNTH_ROOT_ID = 'composition.root'\n/** The implicit root container the field's blocks compose inside (like a page root). */\nconst SYNTH_ROOT_COMPONENT = 'base.primitives.Stack'\n\n/**\n * Wrap the field value's blocks into a synthetic root container (EC-287). The\n * builder store edits this single root-bearing composition directly; its\n * children are the field's blocks, lifted back out via `onChange`.\n */\nfunction buildSyntheticRoot(value: unknown): ComponentNode {\n return {\n id: SYNTH_ROOT_ID,\n componentId: SYNTH_ROOT_COMPONENT,\n slots: { [DEFAULT_SLOT]: blockRootsOf(value) },\n }\n}\n\n/** Unwrap the synthetic root's children back to a field value per cardinality. */\nfunction unwrapValue(blocks: readonly ComponentNode[], cardinality: Cardinality): unknown {\n return cardinality === 'one' ? blocks[0] : [...blocks]\n}\n\nexport function CompositionFieldEditor({\n field,\n value,\n onChange,\n readOnly,\n trailingPanes,\n}: {\n field: BlocksFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n /** Inspector panes the document editor appends after the canvas-supplied\n * `Properties` pane (EC-224 — the frame-owned `Versions` pane). */\n trailingPanes?: readonly InspectorPane[]\n}) {\n // Build the synthetic root ONCE from the value at mount. The editor is\n // uncontrolled thereafter — it is the source of truth while open and lifts\n // changes out via onChange; re-opening the tab remounts from the current draft.\n // (BuilderProvider reads `composition` only in its lazy initial state, so later\n // prop changes are ignored regardless.)\n const initialRoot = useMemo(() => buildSyntheticRoot(value), [])\n\n return (\n <BuilderProvider composition={initialRoot}>\n <CompositionLift cardinality={field.cardinality} onChange={onChange} readOnly={readOnly} />\n <ReuseProvider>\n <CompositionScopeProvider value={COMPOSITION_SCOPE}>\n <CompositionEditorShell\n readOnly={readOnly}\n fieldAllow={field.allow}\n trailingPanes={trailingPanes}\n />\n </CompositionScopeProvider>\n </ReuseProvider>\n </BuilderProvider>\n )\n}\n\n/**\n * Lifts committed edits out of the scoped store into the CMS draft. The first\n * render establishes the (container-normalized) baseline so simply opening the\n * tab never marks the document dirty; every subsequent change to the block list\n * emits the unwrapped value.\n */\nfunction CompositionLift({\n cardinality,\n onChange,\n readOnly,\n}: {\n cardinality: Cardinality\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const builder = useBuilder()\n const blocks = builder.present.slots?.[DEFAULT_SLOT] ?? EMPTY_BLOCKS\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const baselineRef = useRef<readonly ComponentNode[] | null>(null)\n\n useEffect(() => {\n // EC-226: never lift edits out of a READ-ONLY canvas (the Published view).\n // The canvas's keyboard listeners (clipboard paste/cut) aren't blocked by the\n // visual `pointer-events-none`, so without this a Cmd+V could mutate the\n // graph; refusing to lift keeps the document draft intact regardless. The\n // editor remounts per perspective, so any transient in-canvas change is\n // discarded on switch.\n if (readOnly) return\n if (baselineRef.current === null) {\n baselineRef.current = blocks\n return\n }\n if (blocks !== baselineRef.current) {\n baselineRef.current = blocks\n onChangeRef.current(unwrapValue(blocks, cardinality))\n }\n }, [blocks, cardinality, readOnly])\n\n return null\n}\n\nconst EMPTY_BLOCKS: readonly ComponentNode[] = []\n\n/** A breakpoint preview-width toggle (EC-211/EC-250). Switching the viewport\n * resizes only the preview frame (not an undoable edit, writes no graph command).\n * The label is the breakpoint name (`Full`/`sm`/`md`/…) with the real px width in\n * the tooltip — so the switcher reflects the site's actual screens, not a fixed\n * desktop/tablet/phone triple. */\nfunction ViewportButton({ option }: { option: ViewportOption }) {\n const builder = useBuilder()\n const active = builder.viewport === option.key\n return (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.viewportPreviewAria', { label: option.label })}\n aria-pressed={active}\n title={\n option.px !== undefined\n ? `${option.label} · ${option.px}px`\n : translate('builder.canvas.viewportWidthTitle', { label: option.label })\n }\n onClick={() => builder.dispatch({ type: 'viewport', viewport: option.key })}\n className={cn('px-2 text-xs', active && 'bg-accent text-accent-foreground')}\n >\n {option.label}\n </Button>\n )\n}\n\n/** The centered breakpoint switcher (EC-250): the resolved viewport set, plus a\n * visible validation issue when a project declared breakpoints but none are usable. */\nfunction BreakpointSwitcher() {\n const { viewports, issue } = useCanvasViewports()\n return (\n <Row gap=\"1\" align=\"center\">\n <ToolbarGroup>\n {viewports.map((v) => (\n <ViewportButton key={v.key} option={v} />\n ))}\n </ToolbarGroup>\n {issue ? (\n <Row gap=\"1\" align=\"center\" className=\"text-amber-600 dark:text-amber-400\" title={issue}>\n <AlertTriangle aria-hidden className=\"size-3.5\" />\n <Text size=\"xs\">{translate('builder.canvas.breakpointsIssueLabel')}</Text>\n </Row>\n ) : null}\n </Row>\n )\n}\n\n/**\n * The slim editor surface — the canvas + layers + inspector, plus Insert and\n * undo/redo. It deliberately drops the project-builder *lifecycle* chrome\n * (publish, versions, AI, page SEO): the document owns the lifecycle, not the\n * field. It keeps spatial/preview controls — including the breakpoint\n * preview-width switcher (EC-211), a canvas concern, not a lifecycle one.\n */\nfunction CompositionEditorShell({\n readOnly,\n fieldAllow,\n trailingPanes,\n}: {\n readOnly: boolean\n /** The field's top-level component vocabulary (EC-188) — the leash applied to\n * inserts at the composition root so forbidden components aren't even offered. */\n fieldAllow?: readonly string[]\n /** Document-level panes appended after `Properties` (EC-224). */\n trailingPanes?: readonly InspectorPane[]\n}) {\n const builder = useBuilder()\n const dispatch = builder.dispatch\n const [insertContext, setInsertContext] = useState<InsertContext | null>(null)\n const [insertOpen, setInsertOpen] = useState(false)\n const rootId = builder.present.id\n\n // The canvas SUPPLIES the `Properties` pane (EC-224); the document editor\n // appends its own (`Versions`). Properties dims under read-only just as the\n // whole inspector column used to — but the appended panes stay interactive.\n const propertiesPane = useMemo<InspectorPane>(\n () => ({\n id: 'properties',\n label: translate('builder.inspector.propertiesPane'),\n icon: <SlidersHorizontal className=\"size-3.5\" />,\n render: () => (\n <div\n className={cn(\n 'h-full min-h-0 overflow-hidden',\n readOnly && 'pointer-events-none opacity-60',\n )}\n >\n <PropertyInspector />\n </div>\n ),\n }),\n [readOnly],\n )\n const panes = useMemo<InspectorPane[]>(\n () => [propertiesPane, ...(trailingPanes ?? [])],\n [propertiesPane, trailingPanes],\n )\n\n function openInsert(nodeId: string) {\n const ctx = resolveInsertContext(builder, nodeId, fieldAllow)\n if (ctx) {\n setInsertContext(ctx)\n setInsertOpen(true)\n }\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 border-b border-border\">\n <Toolbar>\n <ToolbarGroup>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.undo')}\n title={translate('builder.canvas.undo')}\n disabled={readOnly || !builder.canUndo}\n onClick={() => dispatch({ type: 'undo' })}\n >\n <Undo2 />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.redo')}\n title={translate('builder.canvas.redo')}\n disabled={readOnly || !builder.canRedo}\n onClick={() => dispatch({ type: 'redo' })}\n >\n <Redo2 />\n </Button>\n </ToolbarGroup>\n <ToolbarSeparator />\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={readOnly}\n onClick={() => openInsert(builder.selectedId ?? rootId)}\n >\n <Plus /> {translate('builder.canvas.insert')}\n </Button>\n {/* Breakpoint preview-width switcher (EC-211/EC-250), centered over the\n canvas via flanking spacers. A preview control, not an edit — stays\n active even in read-only published preview. */}\n <Spacer />\n <BreakpointSwitcher />\n <Spacer />\n </Toolbar>\n </div>\n\n <div className=\"grid min-h-0 flex-1 grid-cols-[minmax(200px,240px)_1fr_minmax(280px,320px)] gap-px bg-border\">\n <Panel\n className={cn('rounded-none border-0', readOnly && 'pointer-events-none opacity-60')}\n >\n <PageTree onAddChild={openInsert} />\n </Panel>\n <div\n className={cn(\n 'flex min-h-0 flex-col bg-background',\n readOnly && 'pointer-events-none opacity-60',\n )}\n >\n {/* EC-300/AD-16: the read-only Published view resolves references against\n the published perspective (honest preview); the editable Draft view\n resolves drafts (a just-made reusable shows immediately). */}\n <BuilderCanvas perspective={readOnly ? 'published' : 'draft'} />\n </div>\n <InspectorSidebar panes={panes} />\n </div>\n\n <InsertMenu context={insertContext} open={insertOpen} onOpenChange={setInsertOpen} />\n </div>\n )\n}\n"]}
@@ -4,6 +4,7 @@ import { AlertTriangle } from 'lucide-react';
4
4
  import { renderNode } from '@elytracms/core/runtime-renderer';
5
5
  import { getStudioRegistry, previewContext } from '@elytracms/studio/lib/registry';
6
6
  import { cn } from '@elytracms/studio/lib/utils';
7
+ import { translate } from '@elytracms/studio/lib/i18n';
7
8
  /**
8
9
  * Live, scaled fixture thumbnail (EC-164). Renders a preview node through the
9
10
  * runtime renderer — the exact render path the canvas, fixture runner, and
@@ -35,7 +36,7 @@ class ThumbnailErrorBoundary extends Component {
35
36
  }
36
37
  render() {
37
38
  if (this.state.error) {
38
- return _jsx(ThumbnailFallback, { message: `Preview failed: ${this.state.error.message}` });
39
+ return (_jsx(ThumbnailFallback, { message: translate('builder.blocksPanel.previewFailed', { error: this.state.error.message }) }));
39
40
  }
40
41
  return this.props.children;
41
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fixture-thumbnail.js","sourceRoot":"","sources":["../../../src/components/builder/fixture-thumbnail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle } from 'lucide-react'\nimport { renderNode } from '@elytracms/core/runtime-renderer'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getStudioRegistry, previewContext } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * Live, scaled fixture thumbnail (EC-164). Renders a preview node through the\n * runtime renderer — the exact render path the canvas, fixture runner, and\n * export use — inside a fixed-aspect tile, scaled down with a CSS transform.\n * Never a screenshot, never interactive (pointer events are disabled), and\n * never a crash: render errors degrade to a visible fallback inside the tile.\n *\n * Off-screen thumbnails mount lazily via IntersectionObserver so a large\n * registry doesn't render its whole library up front; pass `eager` to render\n * immediately (first-fold tiles, non-DOM tests).\n */\n\n/** Width the preview is laid out at before being scaled into the tile. */\nconst DESIGN_WIDTH = 420\n/** Fallback scale used for the single frame before the tile is measured. */\nconst FALLBACK_SCALE = 0.45\n\nfunction LiveRender({ node, studio }: { node: ComponentNode; studio: StudioRegistry }) {\n const ctx = useMemo(() => previewContext(studio), [studio])\n // Called inside a component render so the boundary catches sync throws too.\n return <>{renderNode(node, ctx)}</>\n}\n\nfunction ThumbnailFallback({ message }: { message: string }) {\n return (\n <div\n data-block-thumbnail-fallback\n className=\"flex h-full w-full items-start gap-1.5 p-2 text-[0.7rem] leading-snug text-amber-700 dark:text-amber-400\"\n >\n <AlertTriangle aria-hidden className=\"mt-px size-3 shrink-0\" />\n <span className=\"line-clamp-3\">{message}</span>\n </div>\n )\n}\n\ninterface BoundaryState {\n error: Error | null\n}\n\n/** Tile-local error boundary: one broken fixture never takes down the panel. */\nclass ThumbnailErrorBoundary extends Component<{ children: ReactNode }, BoundaryState> {\n override state: BoundaryState = { error: null }\n\n static getDerivedStateFromError(error: Error): BoundaryState {\n return { error }\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n return <ThumbnailFallback message={`Preview failed: ${this.state.error.message}`} />\n }\n return this.props.children\n }\n}\n\nexport function ScaledFixtureThumbnail({\n node,\n studio,\n designWidth = DESIGN_WIDTH,\n eager = false,\n className,\n}: {\n /** The preview subtree to render live (deterministic ids). */\n node: ComponentNode\n /**\n * Registry the preview renders against. Defaults to the active project's live\n * registry (`getStudioRegistry()`) — the blocks panel's tiles and their\n * thumbnails share one registry. Pass an explicit registry to render against a\n * specific component set (deterministic previews/tests).\n */\n studio?: StudioRegistry\n /** Layout width of the unscaled preview, in CSS pixels. */\n designWidth?: number\n /** Mount the live render immediately instead of waiting for visibility. */\n eager?: boolean\n className?: string\n}) {\n const registry = studio ?? getStudioRegistry()\n const ref = useRef<HTMLDivElement>(null)\n const [inView, setInView] = useState(eager)\n const [scale, setScale] = useState<number | null>(null)\n\n // Lazy mount: render the live preview only once the tile scrolls into view.\n // IntersectionObserver always delivers an initial report (intersecting or\n // not) within a rendering frame — when no report arrives at all the page\n // isn't producing frames (occluded/headless automation windows), so a\n // safety-net timer mounts the preview rather than leaving tiles blank.\n // Frame-producing browsers report long before the timer and keep laziness.\n useEffect(() => {\n if (inView) return\n const el = ref.current\n if (!el || typeof IntersectionObserver === 'undefined') {\n setInView(true)\n return\n }\n let reported = false\n const observer = new IntersectionObserver((entries) => {\n reported = true\n if (entries.some((entry) => entry.isIntersecting)) setInView(true)\n })\n observer.observe(el)\n const fallback = setTimeout(() => {\n if (!reported) setInView(true)\n }, 400)\n return () => {\n observer.disconnect()\n clearTimeout(fallback)\n }\n }, [inView])\n\n // Fit-to-tile scale, kept in sync with the tile's measured width.\n useEffect(() => {\n const el = ref.current\n if (!el) return\n const update = () => {\n const width = el.clientWidth\n if (width > 0) setScale(width / designWidth)\n }\n update()\n if (typeof ResizeObserver === 'undefined') return\n const observer = new ResizeObserver(update)\n observer.observe(el)\n return () => observer.disconnect()\n }, [designWidth])\n\n return (\n <div\n ref={ref}\n aria-hidden\n data-block-thumbnail\n className={cn(\n 'relative w-full overflow-hidden rounded-md border border-border bg-background',\n className,\n )}\n style={{ aspectRatio: '16 / 10' }}\n >\n {inView ? (\n <ThumbnailErrorBoundary>\n <div\n className=\"pointer-events-none absolute left-0 top-0 origin-top-left select-none p-3\"\n style={{ width: designWidth, transform: `scale(${scale ?? FALLBACK_SCALE})` }}\n >\n <LiveRender node={node} studio={registry} />\n </div>\n </ThumbnailErrorBoundary>\n ) : null}\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"fixture-thumbnail.js","sourceRoot":"","sources":["../../../src/components/builder/fixture-thumbnail.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEvE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAE7D,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAElF,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD;;;;;;;;;;GAUG;AAEH,0EAA0E;AAC1E,MAAM,YAAY,GAAG,GAAG,CAAA;AACxB,4EAA4E;AAC5E,MAAM,cAAc,GAAG,IAAI,CAAA;AAE3B,SAAS,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAmD;IACnF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAC3D,4EAA4E;IAC5E,OAAO,4BAAG,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,GAAI,CAAA;AACrC,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,OAAO,EAAuB;IACzD,OAAO,CACL,sDAEE,SAAS,EAAC,0GAA0G,aAEpH,KAAC,aAAa,yBAAa,SAAS,EAAC,uBAAuB,GAAG,EAC/D,eAAM,SAAS,EAAC,cAAc,YAAE,OAAO,GAAQ,IAC3C,CACP,CAAA;AACH,CAAC;AAMD,gFAAgF;AAChF,MAAM,sBAAuB,SAAQ,SAAiD;IAC3E,KAAK,GAAkB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAE/C,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,KAAK,EAAE,CAAA;IAClB,CAAC;IAEQ,MAAM;QACb,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,CACL,KAAC,iBAAiB,IAChB,OAAO,EAAE,SAAS,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,GAC5F,CACH,CAAA;QACH,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;IAC5B,CAAC;CACF;AAED,MAAM,UAAU,sBAAsB,CAAC,EACrC,IAAI,EACJ,MAAM,EACN,WAAW,GAAG,YAAY,EAC1B,KAAK,GAAG,KAAK,EACb,SAAS,GAgBV;IACC,MAAM,QAAQ,GAAG,MAAM,IAAI,iBAAiB,EAAE,CAAA;IAC9C,MAAM,GAAG,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IACxC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAEvD,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,sEAAsE;IACtE,uEAAuE;IACvE,2EAA2E;IAC3E,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM;YAAE,OAAM;QAClB,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAA;QACtB,IAAI,CAAC,EAAE,IAAI,OAAO,oBAAoB,KAAK,WAAW,EAAE,CAAC;YACvD,SAAS,CAAC,IAAI,CAAC,CAAA;YACf,OAAM;QACR,CAAC;QACD,IAAI,QAAQ,GAAG,KAAK,CAAA;QACpB,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CAAC,CAAC,OAAO,EAAE,EAAE;YACpD,QAAQ,GAAG,IAAI,CAAA;YACf,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC;gBAAE,SAAS,CAAC,IAAI,CAAC,CAAA;QACpE,CAAC,CAAC,CAAA;QACF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACpB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,QAAQ;gBAAE,SAAS,CAAC,IAAI,CAAC,CAAA;QAChC,CAAC,EAAE,GAAG,CAAC,CAAA;QACP,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,UAAU,EAAE,CAAA;YACrB,YAAY,CAAC,QAAQ,CAAC,CAAA;QACxB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,kEAAkE;IAClE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAA;QACtB,IAAI,CAAC,EAAE;YAAE,OAAM;QACf,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAA;YAC5B,IAAI,KAAK,GAAG,CAAC;gBAAE,QAAQ,CAAC,KAAK,GAAG,WAAW,CAAC,CAAA;QAC9C,CAAC,CAAA;QACD,MAAM,EAAE,CAAA;QACR,IAAI,OAAO,cAAc,KAAK,WAAW;YAAE,OAAM;QACjD,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAA;QAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACpB,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAA;IACpC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAA;IAEjB,OAAO,CACL,cACE,GAAG,EAAE,GAAG,qDAGR,SAAS,EAAE,EAAE,CACX,+EAA+E,EAC/E,SAAS,CACV,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,YAEhC,MAAM,CAAC,CAAC,CAAC,CACR,KAAC,sBAAsB,cACrB,cACE,SAAS,EAAC,2EAA2E,EACrF,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,cAAc,GAAG,EAAE,YAE7E,KAAC,UAAU,IAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAI,GACxC,GACiB,CAC1B,CAAC,CAAC,CAAC,IAAI,GACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["import { Component, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle } from 'lucide-react'\nimport { renderNode } from '@elytracms/core/runtime-renderer'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getStudioRegistry, previewContext } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Live, scaled fixture thumbnail (EC-164). Renders a preview node through the\n * runtime renderer — the exact render path the canvas, fixture runner, and\n * export use — inside a fixed-aspect tile, scaled down with a CSS transform.\n * Never a screenshot, never interactive (pointer events are disabled), and\n * never a crash: render errors degrade to a visible fallback inside the tile.\n *\n * Off-screen thumbnails mount lazily via IntersectionObserver so a large\n * registry doesn't render its whole library up front; pass `eager` to render\n * immediately (first-fold tiles, non-DOM tests).\n */\n\n/** Width the preview is laid out at before being scaled into the tile. */\nconst DESIGN_WIDTH = 420\n/** Fallback scale used for the single frame before the tile is measured. */\nconst FALLBACK_SCALE = 0.45\n\nfunction LiveRender({ node, studio }: { node: ComponentNode; studio: StudioRegistry }) {\n const ctx = useMemo(() => previewContext(studio), [studio])\n // Called inside a component render so the boundary catches sync throws too.\n return <>{renderNode(node, ctx)}</>\n}\n\nfunction ThumbnailFallback({ message }: { message: string }) {\n return (\n <div\n data-block-thumbnail-fallback\n className=\"flex h-full w-full items-start gap-1.5 p-2 text-[0.7rem] leading-snug text-amber-700 dark:text-amber-400\"\n >\n <AlertTriangle aria-hidden className=\"mt-px size-3 shrink-0\" />\n <span className=\"line-clamp-3\">{message}</span>\n </div>\n )\n}\n\ninterface BoundaryState {\n error: Error | null\n}\n\n/** Tile-local error boundary: one broken fixture never takes down the panel. */\nclass ThumbnailErrorBoundary extends Component<{ children: ReactNode }, BoundaryState> {\n override state: BoundaryState = { error: null }\n\n static getDerivedStateFromError(error: Error): BoundaryState {\n return { error }\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n return (\n <ThumbnailFallback\n message={translate('builder.blocksPanel.previewFailed', { error: this.state.error.message })}\n />\n )\n }\n return this.props.children\n }\n}\n\nexport function ScaledFixtureThumbnail({\n node,\n studio,\n designWidth = DESIGN_WIDTH,\n eager = false,\n className,\n}: {\n /** The preview subtree to render live (deterministic ids). */\n node: ComponentNode\n /**\n * Registry the preview renders against. Defaults to the active project's live\n * registry (`getStudioRegistry()`) — the blocks panel's tiles and their\n * thumbnails share one registry. Pass an explicit registry to render against a\n * specific component set (deterministic previews/tests).\n */\n studio?: StudioRegistry\n /** Layout width of the unscaled preview, in CSS pixels. */\n designWidth?: number\n /** Mount the live render immediately instead of waiting for visibility. */\n eager?: boolean\n className?: string\n}) {\n const registry = studio ?? getStudioRegistry()\n const ref = useRef<HTMLDivElement>(null)\n const [inView, setInView] = useState(eager)\n const [scale, setScale] = useState<number | null>(null)\n\n // Lazy mount: render the live preview only once the tile scrolls into view.\n // IntersectionObserver always delivers an initial report (intersecting or\n // not) within a rendering frame — when no report arrives at all the page\n // isn't producing frames (occluded/headless automation windows), so a\n // safety-net timer mounts the preview rather than leaving tiles blank.\n // Frame-producing browsers report long before the timer and keep laziness.\n useEffect(() => {\n if (inView) return\n const el = ref.current\n if (!el || typeof IntersectionObserver === 'undefined') {\n setInView(true)\n return\n }\n let reported = false\n const observer = new IntersectionObserver((entries) => {\n reported = true\n if (entries.some((entry) => entry.isIntersecting)) setInView(true)\n })\n observer.observe(el)\n const fallback = setTimeout(() => {\n if (!reported) setInView(true)\n }, 400)\n return () => {\n observer.disconnect()\n clearTimeout(fallback)\n }\n }, [inView])\n\n // Fit-to-tile scale, kept in sync with the tile's measured width.\n useEffect(() => {\n const el = ref.current\n if (!el) return\n const update = () => {\n const width = el.clientWidth\n if (width > 0) setScale(width / designWidth)\n }\n update()\n if (typeof ResizeObserver === 'undefined') return\n const observer = new ResizeObserver(update)\n observer.observe(el)\n return () => observer.disconnect()\n }, [designWidth])\n\n return (\n <div\n ref={ref}\n aria-hidden\n data-block-thumbnail\n className={cn(\n 'relative w-full overflow-hidden rounded-md border border-border bg-background',\n className,\n )}\n style={{ aspectRatio: '16 / 10' }}\n >\n {inView ? (\n <ThumbnailErrorBoundary>\n <div\n className=\"pointer-events-none absolute left-0 top-0 origin-top-left select-none p-3\"\n style={{ width: designWidth, transform: `scale(${scale ?? FALLBACK_SCALE})` }}\n >\n <LiveRender node={node} studio={registry} />\n </div>\n </ThumbnailErrorBoundary>\n ) : null}\n </div>\n )\n}\n"]}
@@ -1,13 +1,13 @@
1
1
  import type { ComponentNode, SpacingTokenName } from '@elytracms/core/project-graph';
2
2
  export declare function layoutManagedPropNames(node: ComponentNode): readonly string[];
3
3
  /**
4
- * Plain-language label per spacing token step (EC-161 scale) — no CSS lengths.
4
+ * i18n message key per spacing token step (EC-161 scale) — no CSS lengths.
5
5
  * Shared with the canvas gap handles (EC-163): the live drag label and this
6
- * select speak the same vocabulary for the same token.
6
+ * select resolve the same catalog message for the same token.
7
7
  */
8
- export declare const GAP_LABELS: Record<SpacingTokenName, string>;
9
- /** Plain-language label for the "no gap" step (gap prop absent). */
10
- export declare const GAP_NONE_LABEL = "None";
8
+ export declare const GAP_LABEL_KEYS: Record<SpacingTokenName, string>;
9
+ /** i18n message key for the "no gap" step (gap prop absent). */
10
+ export declare const GAP_NONE_LABEL_KEY = "common.none";
11
11
  /** The contextual layout section. Renders nothing for non-container nodes. */
12
12
  export declare function LayoutSection({ node }: {
13
13
  node: ComponentNode;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { CONTAINER_ALIGNMENTS, GRID_MAX_COLUMNS, SPACING_TOKEN_NAMES, STACK_COMPONENT_ID, STACK_ON_MOBILE_PROP, gridStacksOnMobile, isLayoutContainerNode, isSpacingTokenName, spacingTokenFromLegacyGap, staticProp, } from '@elytracms/core/project-graph';
3
3
  import { Field, InspectorSection, Select, Stack } from '@elytracms/studio/ui';
4
+ import { translate } from '@elytracms/studio/lib/i18n';
4
5
  import { useBuilder } from './builder-store';
5
6
  /**
6
7
  * Contextual layout inspector (EC-162): when a `Stack`/`Grid` container is
@@ -16,27 +17,27 @@ export function layoutManagedPropNames(node) {
16
17
  return isLayoutContainerNode(node) ? LAYOUT_PROPS : [];
17
18
  }
18
19
  /**
19
- * Plain-language label per spacing token step (EC-161 scale) — no CSS lengths.
20
+ * i18n message key per spacing token step (EC-161 scale) — no CSS lengths.
20
21
  * Shared with the canvas gap handles (EC-163): the live drag label and this
21
- * select speak the same vocabulary for the same token.
22
+ * select resolve the same catalog message for the same token.
22
23
  */
23
- export const GAP_LABELS = {
24
- px: 'Hairline',
25
- '1': 'Extra small',
26
- '2': 'Small',
27
- '3': 'Medium',
28
- '4': 'Large',
29
- '6': 'Extra large',
30
- '8': 'Huge',
24
+ export const GAP_LABEL_KEYS = {
25
+ px: 'builder.layout.gapStep.hairline',
26
+ '1': 'builder.layout.gapStep.extraSmall',
27
+ '2': 'builder.layout.gapStep.small',
28
+ '3': 'builder.layout.gapStep.medium',
29
+ '4': 'builder.layout.gapStep.large',
30
+ '6': 'builder.layout.gapStep.extraLarge',
31
+ '8': 'builder.layout.gapStep.huge',
31
32
  };
32
- /** Plain-language label for the "no gap" step (gap prop absent). */
33
- export const GAP_NONE_LABEL = 'None';
33
+ /** i18n message key for the "no gap" step (gap prop absent). */
34
+ export const GAP_NONE_LABEL_KEY = 'common.none';
34
35
  const GAP_NONE = 'none';
35
- const ALIGN_LABELS = {
36
- start: 'Start',
37
- center: 'Center',
38
- end: 'End',
39
- stretch: 'Stretch',
36
+ const ALIGN_LABEL_KEYS = {
37
+ start: 'builder.layout.align.start',
38
+ center: 'builder.layout.align.center',
39
+ end: 'builder.layout.align.end',
40
+ stretch: 'builder.layout.align.stretch',
40
41
  };
41
42
  function staticValue(node, name) {
42
43
  const prop = node.props?.[name];
@@ -59,7 +60,7 @@ function currentDirection(node) {
59
60
  }
60
61
  function currentAlignment(node) {
61
62
  const value = staticValue(node, 'align');
62
- return typeof value === 'string' && value in ALIGN_LABELS ? value : 'stretch';
63
+ return typeof value === 'string' && value in ALIGN_LABEL_KEYS ? value : 'stretch';
63
64
  }
64
65
  /** The rendered column count: a valid static value, else 1 (the renderer's fallback). */
65
66
  function currentColumns(node) {
@@ -77,8 +78,8 @@ export function LayoutSection({ node }) {
77
78
  const isStack = node.componentId === STACK_COMPONENT_ID;
78
79
  const set = (name, value) => builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(value) });
79
80
  const clear = (name) => builder.dispatch({ type: 'removeProp', id: node.id, name });
80
- return (_jsx(InspectorSection, { title: "Layout", children: _jsxs(Stack, { gap: "3", children: [isStack ? (_jsx(Field, { label: "Direction", htmlFor: `layout-${node.id}-direction`, children: _jsxs(Select, { id: `layout-${node.id}-direction`, value: currentDirection(node), onChange: (e) => set('direction', e.target.value), children: [_jsx("option", { value: "vertical", children: "Vertical" }), _jsx("option", { value: "horizontal", children: "Horizontal" })] }) })) : (_jsx(Field, { label: "Columns", htmlFor: `layout-${node.id}-columns`, children: _jsx(Select, { id: `layout-${node.id}-columns`, value: String(currentColumns(node)), onChange: (e) => set('columns', Number(e.target.value)), children: COLUMN_OPTIONS.map((n) => (_jsx("option", { value: String(n), children: n === 1 ? '1 column' : `${n} columns` }, n))) }) })), _jsx(Field, { label: "Gap", htmlFor: `layout-${node.id}-gap`, children: _jsxs(Select, { id: `layout-${node.id}-gap`, value: currentGapToken(node), onChange: (e) => (e.target.value === GAP_NONE ? clear('gap') : set('gap', e.target.value)), children: [_jsx("option", { value: GAP_NONE, children: GAP_NONE_LABEL }), SPACING_TOKEN_NAMES.map((name) => (_jsx("option", { value: name, children: GAP_LABELS[name] }, name)))] }) }), _jsx(Field, { label: "Alignment", htmlFor: `layout-${node.id}-align`, children: _jsx(Select, { id: `layout-${node.id}-align`, value: currentAlignment(node), onChange: (e) => set('align', e.target.value), children: CONTAINER_ALIGNMENTS.map((name) => (_jsx("option", { value: name, children: ALIGN_LABELS[name] }, name))) }) }), !isStack ? (_jsx(Field, { label: "On phones", htmlFor: `layout-${node.id}-mobile`, children: _jsxs(Select, { id: `layout-${node.id}-mobile`, value: gridStacksOnMobile(node) ? 'stack' : 'keep', onChange: (e) => e.target.value === 'keep'
81
+ return (_jsx(InspectorSection, { title: translate('builder.layout.title'), children: _jsxs(Stack, { gap: "3", children: [isStack ? (_jsx(Field, { label: translate('builder.layout.direction'), htmlFor: `layout-${node.id}-direction`, children: _jsxs(Select, { id: `layout-${node.id}-direction`, value: currentDirection(node), onChange: (e) => set('direction', e.target.value), children: [_jsx("option", { value: "vertical", children: translate('builder.layout.vertical') }), _jsx("option", { value: "horizontal", children: translate('builder.layout.horizontal') })] }) })) : (_jsx(Field, { label: translate('builder.layout.columns'), htmlFor: `layout-${node.id}-columns`, children: _jsx(Select, { id: `layout-${node.id}-columns`, value: String(currentColumns(node)), onChange: (e) => set('columns', Number(e.target.value)), children: COLUMN_OPTIONS.map((n) => (_jsx("option", { value: String(n), children: translate(n === 1 ? 'builder.layout.columnCount.one' : 'builder.layout.columnCount.other', { count: n }) }, n))) }) })), _jsx(Field, { label: translate('builder.layout.gap'), htmlFor: `layout-${node.id}-gap`, children: _jsxs(Select, { id: `layout-${node.id}-gap`, value: currentGapToken(node), onChange: (e) => (e.target.value === GAP_NONE ? clear('gap') : set('gap', e.target.value)), children: [_jsx("option", { value: GAP_NONE, children: translate(GAP_NONE_LABEL_KEY) }), SPACING_TOKEN_NAMES.map((name) => (_jsx("option", { value: name, children: translate(GAP_LABEL_KEYS[name]) }, name)))] }) }), _jsx(Field, { label: translate('builder.layout.alignment'), htmlFor: `layout-${node.id}-align`, children: _jsx(Select, { id: `layout-${node.id}-align`, value: currentAlignment(node), onChange: (e) => set('align', e.target.value), children: CONTAINER_ALIGNMENTS.map((name) => (_jsx("option", { value: name, children: translate(ALIGN_LABEL_KEYS[name]) }, name))) }) }), !isStack ? (_jsx(Field, { label: translate('builder.layout.onPhones'), htmlFor: `layout-${node.id}-mobile`, children: _jsxs(Select, { id: `layout-${node.id}-mobile`, value: gridStacksOnMobile(node) ? 'stack' : 'keep', onChange: (e) => e.target.value === 'keep'
81
82
  ? set(STACK_ON_MOBILE_PROP, false)
82
- : clear(STACK_ON_MOBILE_PROP), children: [_jsx("option", { value: "stack", children: "Stack" }), _jsx("option", { value: "keep", children: "Keep columns" })] }) })) : null] }) }));
83
+ : clear(STACK_ON_MOBILE_PROP), children: [_jsx("option", { value: "stack", children: translate('builder.layout.stack') }), _jsx("option", { value: "keep", children: translate('builder.layout.keepColumns') })] }) })) : null] }) }));
83
84
  }
84
85
  //# sourceMappingURL=layout-inspector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout-inspector.js","sourceRoot":"","sources":["../../../src/components/builder/layout-inspector.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTAINER_ALIGNMENTS,\n GRID_MAX_COLUMNS,\n SPACING_TOKEN_NAMES,\n STACK_COMPONENT_ID,\n STACK_ON_MOBILE_PROP,\n gridStacksOnMobile,\n isLayoutContainerNode,\n isSpacingTokenName,\n spacingTokenFromLegacyGap,\n staticProp,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n ContainerAlignment,\n JsonValue,\n SpacingTokenName,\n StackDirection,\n} from '@elytracms/core/project-graph'\nimport { Field, InspectorSection, Select, Stack } from '@elytracms/studio/ui'\nimport { useBuilder } from './builder-store'\n\n/**\n * Contextual layout inspector (EC-162): when a `Stack`/`Grid` container is\n * selected, the Settings tab shows its canonical layout values — direction,\n * gap, alignment, columns — as plain-language controls bound to the EC-161\n * token scale. Gesture creates, this refines: every change is one ordinary\n * `setProp`/`removeProp` builder dispatch (one operations command, one undo\n * step). No CSS vocabulary leaks into the labels.\n */\n\n/** The props this section owns for a container (hidden from the generic Props list). */\nconst LAYOUT_PROPS = ['direction', 'gap', 'align', 'columns', STACK_ON_MOBILE_PROP] as const\n\nexport function layoutManagedPropNames(node: ComponentNode): readonly string[] {\n return isLayoutContainerNode(node) ? LAYOUT_PROPS : []\n}\n\n/**\n * Plain-language label per spacing token step (EC-161 scale) — no CSS lengths.\n * Shared with the canvas gap handles (EC-163): the live drag label and this\n * select speak the same vocabulary for the same token.\n */\nexport const GAP_LABELS: Record<SpacingTokenName, string> = {\n px: 'Hairline',\n '1': 'Extra small',\n '2': 'Small',\n '3': 'Medium',\n '4': 'Large',\n '6': 'Extra large',\n '8': 'Huge',\n}\n\n/** Plain-language label for the \"no gap\" step (gap prop absent). */\nexport const GAP_NONE_LABEL = 'None'\n\nconst GAP_NONE = 'none'\n\nconst ALIGN_LABELS: Record<ContainerAlignment, string> = {\n start: 'Start',\n center: 'Center',\n end: 'End',\n stretch: 'Stretch',\n}\n\nfunction staticValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return prop?.kind === 'static' ? prop.value : undefined\n}\n\n/** The node's gap as a token step, or `'none'` when absent/off-scale (renders gapless/default). */\nfunction currentGapToken(node: ComponentNode): SpacingTokenName | typeof GAP_NONE {\n const value = staticValue(node, 'gap')\n if (typeof value === 'string' && isSpacingTokenName(value)) return value\n if (typeof value === 'number') {\n const token = spacingTokenFromLegacyGap(value)\n if (token) return token\n }\n return GAP_NONE\n}\n\nfunction currentDirection(node: ComponentNode): StackDirection {\n return staticValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n}\n\nfunction currentAlignment(node: ComponentNode): ContainerAlignment {\n const value = staticValue(node, 'align')\n return typeof value === 'string' && value in ALIGN_LABELS ? (value as ContainerAlignment) : 'stretch'\n}\n\n/** The rendered column count: a valid static value, else 1 (the renderer's fallback). */\nfunction currentColumns(node: ComponentNode): number {\n const value = staticValue(node, 'columns')\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= GRID_MAX_COLUMNS\n ? value\n : 1\n}\n\nconst COLUMN_OPTIONS = Array.from({ length: GRID_MAX_COLUMNS }, (_, i) => i + 1)\n\n/** The contextual layout section. Renders nothing for non-container nodes. */\nexport function LayoutSection({ node }: { node: ComponentNode }) {\n const builder = useBuilder()\n if (!isLayoutContainerNode(node)) return null\n const isStack = node.componentId === STACK_COMPONENT_ID\n\n const set = (name: string, value: JsonValue) =>\n builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(value) })\n const clear = (name: string) => builder.dispatch({ type: 'removeProp', id: node.id, name })\n\n return (\n <InspectorSection title=\"Layout\">\n <Stack gap=\"3\">\n {isStack ? (\n <Field label=\"Direction\" htmlFor={`layout-${node.id}-direction`}>\n <Select\n id={`layout-${node.id}-direction`}\n value={currentDirection(node)}\n onChange={(e) => set('direction', e.target.value)}\n >\n <option value=\"vertical\">Vertical</option>\n <option value=\"horizontal\">Horizontal</option>\n </Select>\n </Field>\n ) : (\n <Field label=\"Columns\" htmlFor={`layout-${node.id}-columns`}>\n <Select\n id={`layout-${node.id}-columns`}\n value={String(currentColumns(node))}\n onChange={(e) => set('columns', Number(e.target.value))}\n >\n {COLUMN_OPTIONS.map((n) => (\n <option key={n} value={String(n)}>\n {n === 1 ? '1 column' : `${n} columns`}\n </option>\n ))}\n </Select>\n </Field>\n )}\n\n <Field label=\"Gap\" htmlFor={`layout-${node.id}-gap`}>\n <Select\n id={`layout-${node.id}-gap`}\n value={currentGapToken(node)}\n onChange={(e) => (e.target.value === GAP_NONE ? clear('gap') : set('gap', e.target.value))}\n >\n <option value={GAP_NONE}>{GAP_NONE_LABEL}</option>\n {SPACING_TOKEN_NAMES.map((name) => (\n <option key={name} value={name}>\n {GAP_LABELS[name]}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field label=\"Alignment\" htmlFor={`layout-${node.id}-align`}>\n <Select\n id={`layout-${node.id}-align`}\n value={currentAlignment(node)}\n onChange={(e) => set('align', e.target.value)}\n >\n {CONTAINER_ALIGNMENTS.map((name) => (\n <option key={name} value={name}>\n {ALIGN_LABELS[name]}\n </option>\n ))}\n </Select>\n </Field>\n\n {/* Responsive default (EC-163): grids stack on phones unless the editor\n explicitly opts out. \"Stack\" is the default (prop absent); \"Keep\n columns\" writes the explicit `stackOnMobile: false` override. */}\n {!isStack ? (\n <Field label=\"On phones\" htmlFor={`layout-${node.id}-mobile`}>\n <Select\n id={`layout-${node.id}-mobile`}\n value={gridStacksOnMobile(node) ? 'stack' : 'keep'}\n onChange={(e) =>\n e.target.value === 'keep'\n ? set(STACK_ON_MOBILE_PROP, false)\n : clear(STACK_ON_MOBILE_PROP)\n }\n >\n <option value=\"stack\">Stack</option>\n <option value=\"keep\">Keep columns</option>\n </Select>\n </Field>\n ) : null}\n </Stack>\n </InspectorSection>\n )\n}\n"]}
1
+ {"version":3,"file":"layout-inspector.js","sourceRoot":"","sources":["../../../src/components/builder/layout-inspector.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTAINER_ALIGNMENTS,\n GRID_MAX_COLUMNS,\n SPACING_TOKEN_NAMES,\n STACK_COMPONENT_ID,\n STACK_ON_MOBILE_PROP,\n gridStacksOnMobile,\n isLayoutContainerNode,\n isSpacingTokenName,\n spacingTokenFromLegacyGap,\n staticProp,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n ContainerAlignment,\n JsonValue,\n SpacingTokenName,\n StackDirection,\n} from '@elytracms/core/project-graph'\nimport { Field, InspectorSection, Select, Stack } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useBuilder } from './builder-store'\n\n/**\n * Contextual layout inspector (EC-162): when a `Stack`/`Grid` container is\n * selected, the Settings tab shows its canonical layout values — direction,\n * gap, alignment, columns — as plain-language controls bound to the EC-161\n * token scale. Gesture creates, this refines: every change is one ordinary\n * `setProp`/`removeProp` builder dispatch (one operations command, one undo\n * step). No CSS vocabulary leaks into the labels.\n */\n\n/** The props this section owns for a container (hidden from the generic Props list). */\nconst LAYOUT_PROPS = ['direction', 'gap', 'align', 'columns', STACK_ON_MOBILE_PROP] as const\n\nexport function layoutManagedPropNames(node: ComponentNode): readonly string[] {\n return isLayoutContainerNode(node) ? LAYOUT_PROPS : []\n}\n\n/**\n * i18n message key per spacing token step (EC-161 scale) — no CSS lengths.\n * Shared with the canvas gap handles (EC-163): the live drag label and this\n * select resolve the same catalog message for the same token.\n */\nexport const GAP_LABEL_KEYS: Record<SpacingTokenName, string> = {\n px: 'builder.layout.gapStep.hairline',\n '1': 'builder.layout.gapStep.extraSmall',\n '2': 'builder.layout.gapStep.small',\n '3': 'builder.layout.gapStep.medium',\n '4': 'builder.layout.gapStep.large',\n '6': 'builder.layout.gapStep.extraLarge',\n '8': 'builder.layout.gapStep.huge',\n}\n\n/** i18n message key for the \"no gap\" step (gap prop absent). */\nexport const GAP_NONE_LABEL_KEY = 'common.none'\n\nconst GAP_NONE = 'none'\n\nconst ALIGN_LABEL_KEYS: Record<ContainerAlignment, string> = {\n start: 'builder.layout.align.start',\n center: 'builder.layout.align.center',\n end: 'builder.layout.align.end',\n stretch: 'builder.layout.align.stretch',\n}\n\nfunction staticValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return prop?.kind === 'static' ? prop.value : undefined\n}\n\n/** The node's gap as a token step, or `'none'` when absent/off-scale (renders gapless/default). */\nfunction currentGapToken(node: ComponentNode): SpacingTokenName | typeof GAP_NONE {\n const value = staticValue(node, 'gap')\n if (typeof value === 'string' && isSpacingTokenName(value)) return value\n if (typeof value === 'number') {\n const token = spacingTokenFromLegacyGap(value)\n if (token) return token\n }\n return GAP_NONE\n}\n\nfunction currentDirection(node: ComponentNode): StackDirection {\n return staticValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n}\n\nfunction currentAlignment(node: ComponentNode): ContainerAlignment {\n const value = staticValue(node, 'align')\n return typeof value === 'string' && value in ALIGN_LABEL_KEYS ? (value as ContainerAlignment) : 'stretch'\n}\n\n/** The rendered column count: a valid static value, else 1 (the renderer's fallback). */\nfunction currentColumns(node: ComponentNode): number {\n const value = staticValue(node, 'columns')\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= GRID_MAX_COLUMNS\n ? value\n : 1\n}\n\nconst COLUMN_OPTIONS = Array.from({ length: GRID_MAX_COLUMNS }, (_, i) => i + 1)\n\n/** The contextual layout section. Renders nothing for non-container nodes. */\nexport function LayoutSection({ node }: { node: ComponentNode }) {\n const builder = useBuilder()\n if (!isLayoutContainerNode(node)) return null\n const isStack = node.componentId === STACK_COMPONENT_ID\n\n const set = (name: string, value: JsonValue) =>\n builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(value) })\n const clear = (name: string) => builder.dispatch({ type: 'removeProp', id: node.id, name })\n\n return (\n <InspectorSection title={translate('builder.layout.title')}>\n <Stack gap=\"3\">\n {isStack ? (\n <Field label={translate('builder.layout.direction')} htmlFor={`layout-${node.id}-direction`}>\n <Select\n id={`layout-${node.id}-direction`}\n value={currentDirection(node)}\n onChange={(e) => set('direction', e.target.value)}\n >\n <option value=\"vertical\">{translate('builder.layout.vertical')}</option>\n <option value=\"horizontal\">{translate('builder.layout.horizontal')}</option>\n </Select>\n </Field>\n ) : (\n <Field label={translate('builder.layout.columns')} htmlFor={`layout-${node.id}-columns`}>\n <Select\n id={`layout-${node.id}-columns`}\n value={String(currentColumns(node))}\n onChange={(e) => set('columns', Number(e.target.value))}\n >\n {COLUMN_OPTIONS.map((n) => (\n <option key={n} value={String(n)}>\n {translate(\n n === 1 ? 'builder.layout.columnCount.one' : 'builder.layout.columnCount.other',\n { count: n },\n )}\n </option>\n ))}\n </Select>\n </Field>\n )}\n\n <Field label={translate('builder.layout.gap')} htmlFor={`layout-${node.id}-gap`}>\n <Select\n id={`layout-${node.id}-gap`}\n value={currentGapToken(node)}\n onChange={(e) => (e.target.value === GAP_NONE ? clear('gap') : set('gap', e.target.value))}\n >\n <option value={GAP_NONE}>{translate(GAP_NONE_LABEL_KEY)}</option>\n {SPACING_TOKEN_NAMES.map((name) => (\n <option key={name} value={name}>\n {translate(GAP_LABEL_KEYS[name])}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field label={translate('builder.layout.alignment')} htmlFor={`layout-${node.id}-align`}>\n <Select\n id={`layout-${node.id}-align`}\n value={currentAlignment(node)}\n onChange={(e) => set('align', e.target.value)}\n >\n {CONTAINER_ALIGNMENTS.map((name) => (\n <option key={name} value={name}>\n {translate(ALIGN_LABEL_KEYS[name])}\n </option>\n ))}\n </Select>\n </Field>\n\n {/* Responsive default (EC-163): grids stack on phones unless the editor\n explicitly opts out. \"Stack\" is the default (prop absent); \"Keep\n columns\" writes the explicit `stackOnMobile: false` override. */}\n {!isStack ? (\n <Field label={translate('builder.layout.onPhones')} htmlFor={`layout-${node.id}-mobile`}>\n <Select\n id={`layout-${node.id}-mobile`}\n value={gridStacksOnMobile(node) ? 'stack' : 'keep'}\n onChange={(e) =>\n e.target.value === 'keep'\n ? set(STACK_ON_MOBILE_PROP, false)\n : clear(STACK_ON_MOBILE_PROP)\n }\n >\n <option value=\"stack\">{translate('builder.layout.stack')}</option>\n <option value=\"keep\">{translate('builder.layout.keepColumns')}</option>\n </Select>\n </Field>\n ) : null}\n </Stack>\n </InspectorSection>\n )\n}\n"]}
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react';
4
4
  import { getParentInfo } from '@elytracms/core/project-graph';
5
5
  import { canNestInto, containerFlowAxis, dropIndicatorFor, dropPositionForPointer, resolveDrop, } from '@elytracms/studio/lib/dnd/drop-rules';
6
6
  import { getStudioRegistry } from '@elytracms/studio/lib/registry';
7
+ import { translate } from '@elytracms/studio/lib/i18n';
7
8
  import { useData } from '@elytracms/studio/lib/data';
8
9
  import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Row, Spacer, Text, } from '@elytracms/studio/ui';
9
10
  import { cn } from '@elytracms/studio/lib/utils';
@@ -160,7 +161,7 @@ export function PageTree({ onAddChild }) {
160
161
  clearDrop();
161
162
  endDrag('cancelled');
162
163
  }
163
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs(Row, { gap: "2", className: "h-10 shrink-0 border-b border-border px-3", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Page tree" }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: rows.length })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-1.5", onMouseLeave: () => builder.dispatch({ type: 'hover', id: null }), onDragLeave: (e) => {
164
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs(Row, { gap: "2", className: "h-10 shrink-0 border-b border-border px-3", children: [_jsx(Text, { size: "sm", weight: "semibold", children: translate('builder.pageTree.title') }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: rows.length })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-1.5", onMouseLeave: () => builder.dispatch({ type: 'hover', id: null }), onDragLeave: (e) => {
164
165
  // Clear the indicator when the drag exits the tree panel.
165
166
  if (!e.currentTarget.contains(e.relatedTarget))
166
167
  clearDrop();
@@ -180,9 +181,9 @@ export function PageTree({ onAddChild }) {
180
181
  ? 'bg-muted text-foreground'
181
182
  : 'text-muted-foreground', drop?.rowId === row.id && drop.indicator
182
183
  ? ROW_DROP_CLASSES[drop.indicator]
183
- : undefined), children: [_jsxs("button", { type: "button", onClick: () => builder.dispatch({ type: 'select', id: row.id }), className: "flex min-w-0 flex-1 items-center gap-1.5 py-1", style: { paddingLeft: `${6 + row.depth * 14}px` }, children: [_jsx("span", { className: cn('truncate font-mono', condState === 'hidden' && 'line-through opacity-70'), children: shortId(row.componentId) }), manifest?.title ? (_jsx("span", { className: "truncate text-muted-foreground/70", children: manifest.title })) : null, condState === 'hidden' ? (_jsx(EyeOff, { className: "size-3 shrink-0 text-warning", "aria-label": "Hidden by condition" })) : condState === 'visible' ? (_jsx(Filter, { className: "size-3 shrink-0 text-muted-foreground/60", "aria-label": "Conditional" })) : null] }), _jsxs("div", { className: cn('flex shrink-0 items-center gap-0.5', selected
184
+ : undefined), children: [_jsxs("button", { type: "button", onClick: () => builder.dispatch({ type: 'select', id: row.id }), className: "flex min-w-0 flex-1 items-center gap-1.5 py-1", style: { paddingLeft: `${6 + row.depth * 14}px` }, children: [_jsx("span", { className: cn('truncate font-mono', condState === 'hidden' && 'line-through opacity-70'), children: shortId(row.componentId) }), manifest?.title ? (_jsx("span", { className: "truncate text-muted-foreground/70", children: manifest.title })) : null, condState === 'hidden' ? (_jsx(EyeOff, { className: "size-3 shrink-0 text-warning", "aria-label": translate('builder.pageTree.hiddenByCondition') })) : condState === 'visible' ? (_jsx(Filter, { className: "size-3 shrink-0 text-muted-foreground/60", "aria-label": translate('builder.pageTree.conditional') })) : null] }), _jsxs("div", { className: cn('flex shrink-0 items-center gap-0.5', selected
184
185
  ? 'opacity-100'
185
- : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100'), children: [_jsx(IconAction, { label: "Add child", onClick: () => onAddChild(row.id), children: _jsx(Plus, {}) }), _jsx(RowActionsMenu, { row: row, builder: builder, clipboard: clipboard })] })] }, row.id));
186
+ : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100'), children: [_jsx(IconAction, { label: translate('builder.pageTree.addChild'), onClick: () => onAddChild(row.id), children: _jsx(Plus, {}) }), _jsx(RowActionsMenu, { row: row, builder: builder, clipboard: clipboard })] })] }, row.id));
186
187
  }) })] }));
187
188
  }
188
189
  /** Right-aligned keyboard-shortcut hint in a menu item — informational only; the
@@ -204,7 +205,7 @@ function RowActionsMenu({ row, builder, clipboard, }) {
204
205
  const reuse = useReuse();
205
206
  const canMakeReusable = !row.isRoot && (reuse?.canMakeReusable(row.id) ?? false);
206
207
  const canDetach = reuse?.canDetach(row.id) ?? false;
207
- return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": "More actions", title: "More actions", className: "size-6", children: _jsx(MoreHorizontal, {}) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => clipboard.copyNode(row.id), children: [_jsx(ClipboardCopy, {}), " Copy ", _jsx(MenuShortcut, { children: "\u2318C" })] }), _jsxs(DropdownMenuItem, { onClick: () => void clipboard.pasteFromClipboard(row.id), children: [_jsx(ClipboardPaste, {}), " Paste ", _jsx(MenuShortcut, { children: "\u2318V" })] }), !row.isRoot ? (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onClick: () => clipboard.cutNode(row.id), children: [_jsx(ClipboardX, {}), " Cut ", _jsx(MenuShortcut, { children: "\u2318X" })] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { disabled: row.isFirst, onClick: () => builder.dispatch({ type: 'reorder', id: row.id, delta: -1 }), children: [_jsx(ChevronUp, {}), " Move up"] }), _jsxs(DropdownMenuItem, { disabled: row.isLast, onClick: () => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 }), children: [_jsx(ChevronDown, {}), " Move down"] }), _jsxs(DropdownMenuItem, { onClick: () => builder.dispatch({ type: 'duplicate', id: row.id }), children: [_jsx(Copy, {}), " Duplicate"] }), canMakeReusable || canDetach ? _jsx(DropdownMenuSeparator, {}) : null, canMakeReusable ? (_jsxs(DropdownMenuItem, { onClick: () => reuse?.startMakeReusable(row.id), children: [_jsx(Boxes, {}), " Make reusable"] })) : null, canDetach ? (_jsxs(DropdownMenuItem, { onClick: () => reuse?.detach(row.id), children: [_jsx(Unlink, {}), " Detach to copy"] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", onClick: () => builder.dispatch({ type: 'remove', id: row.id }), children: [_jsx(Trash2, {}), " Delete"] })] })) : null] })] }));
208
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": translate('builder.pageTree.moreActions'), title: translate('builder.pageTree.moreActions'), className: "size-6", children: _jsx(MoreHorizontal, {}) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => clipboard.copyNode(row.id), children: [_jsx(ClipboardCopy, {}), " ", translate('common.copy'), " ", _jsx(MenuShortcut, { children: "\u2318C" })] }), _jsxs(DropdownMenuItem, { onClick: () => void clipboard.pasteFromClipboard(row.id), children: [_jsx(ClipboardPaste, {}), " ", translate('builder.clipboard.paste'), " ", _jsx(MenuShortcut, { children: "\u2318V" })] }), !row.isRoot ? (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onClick: () => clipboard.cutNode(row.id), children: [_jsx(ClipboardX, {}), " ", translate('builder.clipboard.cut'), " ", _jsx(MenuShortcut, { children: "\u2318X" })] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { disabled: row.isFirst, onClick: () => builder.dispatch({ type: 'reorder', id: row.id, delta: -1 }), children: [_jsx(ChevronUp, {}), " ", translate('builder.pageTree.moveUp')] }), _jsxs(DropdownMenuItem, { disabled: row.isLast, onClick: () => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 }), children: [_jsx(ChevronDown, {}), " ", translate('builder.pageTree.moveDown')] }), _jsxs(DropdownMenuItem, { onClick: () => builder.dispatch({ type: 'duplicate', id: row.id }), children: [_jsx(Copy, {}), " ", translate('common.duplicate')] }), canMakeReusable || canDetach ? _jsx(DropdownMenuSeparator, {}) : null, canMakeReusable ? (_jsxs(DropdownMenuItem, { onClick: () => reuse?.startMakeReusable(row.id), children: [_jsx(Boxes, {}), " ", translate('builder.reuse.makeReusable')] })) : null, canDetach ? (_jsxs(DropdownMenuItem, { onClick: () => reuse?.detach(row.id), children: [_jsx(Unlink, {}), " ", translate('builder.reuse.detachToCopy')] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", onClick: () => builder.dispatch({ type: 'remove', id: row.id }), children: [_jsx(Trash2, {}), " ", translate('common.delete')] })] })) : null] })] }));
208
209
  }
209
210
  function IconAction({ label, onClick, disabled, destructive, children, }) {
210
211
  return (_jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": label, title: label, disabled: disabled, onClick: onClick, className: cn('size-6', destructive && 'hover:text-destructive'), children: children }));
@@ -1 +1 @@
1
- {"version":3,"file":"page-tree.js","sourceRoot":"","sources":["../../../src/components/builder/page-tree.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,KAAK,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,cAAc,EACd,UAAU,EACV,IAAI,EACJ,MAAM,EACN,MAAM,EACN,cAAc,EACd,IAAI,EACJ,MAAM,EACN,MAAM,GACP,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,WAAW,GACZ,MAAM,sCAAsC,CAAA;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AACpD,OAAO,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,GAAG,EACH,MAAM,EACN,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAYjF,SAAS,OAAO,CACd,IAAmB,EACnB,KAAa,EACb,IAAe,EACf,IAA4D;IAE5D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;IACpG,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACvD,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAC5B,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE;YAC9B,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,MAAM,EAAE,CAAC,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC;YACjC,MAAM,EAAE,KAAK;SACd,CAAC,CACH,CAAA;IACH,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,WAAmB;IAClC,OAAO,WAAW,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AAC5E,CAAC;AAED,wFAAwF;AACxF,MAAM,gBAAgB,GAAiD;IACrE,IAAI,EAAE,sDAAsD;IAC5D,MAAM,EAAE,kCAAkC;IAC1C,KAAK,EAAE,mCAAmC;IAC1C,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,EAAE,mDAAmD;IACzD,KAAK,EAAE,oDAAoD;IAC3D,OAAO,EAAE,oEAAoE;CAC9E,CAAA;AAED,MAAM,UAAU,QAAQ,CAAC,EAAE,UAAU,EAA8C;IACjF,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAc,EAAE,CAAA;IAC1B,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IAEhF,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,sCAAsC;IACtC,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAA;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,KAAK,WAAW,CAAA;IACnD,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IAChD,aAAa,CAAC,OAAO,GAAG,OAAO,CAAC,UAAU,CAAA;IAC1C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ;YAAE,OAAM;QACpB,OAAO,wBAAwB,CAAC,MAAM,EAAE;YACtC,SAAS;YACT,aAAa,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO;YAC1C,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE;SAC1C,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;IAEzB,0EAA0E;IAC1E,4EAA4E;IAC5E,+DAA+D;IAC/D,wEAAwE;IACxE,yDAAyD;IACzD,MAAM,WAAW,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IACjD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAqD,IAAI,CAAC,CAAA;IAC1F,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,WAAW,CAAC,OAAO,GAAG,IAAI,CAAA;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAA;IACf,CAAC,CAAA;IACD,SAAS,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;IACxD,MAAM,WAAW,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;IAEnE,SAAS,kBAAkB,CAAC,GAAY,EAAE,CAAkB;QAC1D,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,SAAS,CACP;YACE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE;YACvE,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;SAChC,EACD,CAAC,CAAC,YAAY,CACf,CAAA;IACH,CAAC;IAED,SAAS,iBAAiB,CAAC,GAAY,EAAE,CAAkB;QACzD,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,IAAI,QAAQ,GAAiB,MAAM,CAAA;QACnC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAA;YACpD,kEAAkE;YAClE,sEAAsE;YACtE,MAAM,IAAI,GAAG,iBAAiB,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAA;YAC9E,MAAM,KAAK,GACT,IAAI,KAAK,YAAY;gBACnB,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC;oBACd,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK;oBACtC,CAAC,CAAC,GAAG;gBACP,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;oBACf,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM;oBACtC,CAAC,CAAC,GAAG,CAAA;YACX,QAAQ,GAAG,sBAAsB,CAAC;gBAChC,KAAK;gBACL,QAAQ,EAAE,WAAW,CAAC,WAAW,EAAE,GAAG,CAAC,WAAW,CAAC;gBACnD,KAAK,EAAE,IAAI;aACZ,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,WAAW,CAAC;YAC7B,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,WAAW;YACX,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE;SACvC,CAAC,CAAA;QACF,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QACxD,WAAW,CAAC,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAA;QAC7D,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAA;QACrC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;YACxB,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,CAAC,CAAC,YAAY,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;QAC5E,CAAC;IACH,CAAC;IAED,SAAS,aAAa,CAAC,CAAkB;QACvC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAA;QAChC,SAAS,EAAE,CAAA;QACX,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,CAAC,CAAC,cAAc,EAAE,CAAA;QAClB,wEAAwE;QACxE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAC1E,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAChC,qEAAqE;YACrE,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,KAAK,SAAS;gBACvB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;gBAChD,CAAC,CAAC,IAAI,CAAC,SAAS;oBACd,CAAC,CAAC,EAAE,IAAI,EAAE,WAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;oBACxD,CAAC,CAAC,IAAI,CAAA;YACZ,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,WAAW,CAAC,CAAA;gBACpB,OAAM;YACR,CAAC;YACD,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;QAC7F,CAAC;aAAM,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC1B,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACnF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,WAAW,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,OAAO,CAAC,SAAS,CAAC,CAAA;IACpB,CAAC;IAED,SAAS,gBAAgB;QACvB,SAAS,EAAE,CAAA;QACX,OAAO,CAAC,WAAW,CAAC,CAAA;IACtB,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC3C,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,2CAA2C,aAChE,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,0BAE1B,EACP,KAAC,MAAM,KAAG,EACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,kBAC/B,IAAI,CAAC,MAAM,GACP,IACH,EACN,cACE,SAAS,EAAC,oCAAoC,EAC9C,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EACjE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;oBACjB,0DAA0D;oBAC1D,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,aAA4B,CAAC;wBAAE,SAAS,EAAE,CAAA;gBAC5E,CAAC,YAEA,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;oBAChB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;oBAC7D,MAAM,QAAQ,GAAG,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,UAAU,CAAA;oBAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,SAAS,CAAA;oBAC5C,0EAA0E;oBAC1E,IAAI,SAAS,GAAkC,MAAM,CAAA;oBACrD,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;wBAClB,MAAM,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;wBAC9C,SAAS,GAAG,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAA;oBACrE,CAAC;oBACD,OAAO,CACL,eAEE,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,EACnE,SAAS,EAAE,CAAC,GAAG,CAAC,MAAM,EACtB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,CAAC,EAC9C,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,CAAC,CAAC,EAC5C,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,gBAAgB,mBACZ,GAAG,CAAC,EAAE,oBACL,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,EAClF,SAAS,EAAE,EAAE,CACX,6EAA6E,EAC7E,QAAQ;4BACN,CAAC,CAAC,kCAAkC;4BACpC,CAAC,CAAC,OAAO;gCACP,CAAC,CAAC,0BAA0B;gCAC5B,CAAC,CAAC,uBAAuB,EAC7B,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,SAAS;4BACtC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC;4BAClC,CAAC,CAAC,SAAS,CACd,aAED,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,EAC/D,SAAS,EAAC,+CAA+C,EACzD,KAAK,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,aAEjD,eAAM,SAAS,EAAE,EAAE,CAAC,oBAAoB,EAAE,SAAS,KAAK,QAAQ,IAAI,yBAAyB,CAAC,YAC3F,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GACpB,EACN,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CACjB,eAAM,SAAS,EAAC,mCAAmC,YAAE,QAAQ,CAAC,KAAK,GAAQ,CAC5E,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,CACxB,KAAC,MAAM,IAAC,SAAS,EAAC,8BAA8B,gBAAY,qBAAqB,GAAG,CACrF,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,CAC5B,KAAC,MAAM,IAAC,SAAS,EAAC,0CAA0C,gBAAY,aAAa,GAAG,CACzF,CAAC,CAAC,CAAC,IAAI,IACD,EAET,eACE,SAAS,EAAE,EAAE,CACX,oCAAoC,EACpC,QAAQ;oCACN,CAAC,CAAC,aAAa;oCACf,CAAC,CAAC,kEAAkE,CACvE,aAKD,KAAC,UAAU,IAAC,KAAK,EAAC,WAAW,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,YAC7D,KAAC,IAAI,KAAG,GACG,EACb,KAAC,cAAc,IAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAI,IAChE,KAvDD,GAAG,CAAC,EAAE,CAwDP,CACP,CAAA;gBACH,CAAC,CAAC,GACE,IACF,CACP,CAAA;AACH,CAAC;AAED;iFACiF;AACjF,SAAS,YAAY,CAAC,EAAE,QAAQ,EAAiC;IAC/D,OAAO,eAAM,SAAS,EAAC,4DAA4D,YAAE,QAAQ,GAAQ,CAAA;AACvG,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,cAAc,CAAC,EACtB,GAAG,EACH,OAAO,EACP,SAAS,GAKV;IACC,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;IACxB,MAAM,eAAe,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,CAAA;IAChF,MAAM,SAAS,GAAG,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,CAAA;IACnD,OAAO,CACL,MAAC,YAAY,eACX,KAAC,mBAAmB,IAAC,OAAO,kBAC1B,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,gBACJ,cAAc,EACzB,KAAK,EAAC,cAAc,EACpB,SAAS,EAAC,QAAQ,YAElB,KAAC,cAAc,KAAG,GACX,GACW,EACtB,MAAC,mBAAmB,IAAC,KAAK,EAAC,KAAK,aAC9B,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,aACzD,KAAC,aAAa,KAAG,YAAM,KAAC,YAAY,0BAAkB,IACrC,EACnB,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,aACxE,KAAC,cAAc,KAAG,aAAO,KAAC,YAAY,0BAAkB,IACvC,EAClB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CACb,8BACE,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,aACxD,KAAC,UAAU,KAAG,WAAK,KAAC,YAAY,0BAAkB,IACjC,EACnB,KAAC,qBAAqB,KAAG,EACzB,MAAC,gBAAgB,IACf,QAAQ,EAAE,GAAG,CAAC,OAAO,EACrB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,aAE3E,KAAC,SAAS,KAAG,gBACI,EACnB,MAAC,gBAAgB,IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EACpB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,aAE1E,KAAC,WAAW,KAAG,kBACE,EACnB,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,aAClF,KAAC,IAAI,KAAG,kBACS,EAClB,eAAe,IAAI,SAAS,CAAC,CAAC,CAAC,KAAC,qBAAqB,KAAG,CAAC,CAAC,CAAC,IAAI,EAC/D,eAAe,CAAC,CAAC,CAAC,CACjB,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,aAC/D,KAAC,KAAK,KAAG,sBACQ,CACpB,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,CAAC,CAAC,CAAC,CACX,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,aACpD,KAAC,MAAM,KAAG,uBACO,CACpB,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,qBAAqB,KAAG,EACzB,MAAC,gBAAgB,IACf,OAAO,EAAC,aAAa,EACrB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,aAE/D,KAAC,MAAM,KAAG,eACO,IAClB,CACJ,CAAC,CAAC,CAAC,IAAI,IACY,IACT,CAChB,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,OAAO,EACP,QAAQ,EACR,WAAW,EACX,QAAQ,GAOT;IACC,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,gBACH,KAAK,EACjB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,EAAE,CAAC,QAAQ,EAAE,WAAW,IAAI,wBAAwB,CAAC,YAE/D,QAAQ,GACF,CACV,CAAA;AACH,CAAC","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 { 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\nfunction shortId(componentId: string): string {\n return componentId.replace('base.primitives.', '').replace('project.', '')\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 Page tree\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 className=\"size-3 shrink-0 text-warning\" aria-label=\"Hidden by condition\" />\n ) : condState === 'visible' ? (\n <Filter className=\"size-3 shrink-0 text-muted-foreground/60\" aria-label=\"Conditional\" />\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=\"Add child\" 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=\"More actions\"\n title=\"More actions\"\n className=\"size-6\"\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => clipboard.copyNode(row.id)}>\n <ClipboardCopy /> Copy <MenuShortcut>⌘C</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void clipboard.pasteFromClipboard(row.id)}>\n <ClipboardPaste /> Paste <MenuShortcut>⌘V</MenuShortcut>\n </DropdownMenuItem>\n {!row.isRoot ? (\n <>\n <DropdownMenuItem onClick={() => clipboard.cutNode(row.id)}>\n <ClipboardX /> 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 /> Move up\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={row.isLast}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 })}\n >\n <ChevronDown /> Move down\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => builder.dispatch({ type: 'duplicate', id: row.id })}>\n <Copy /> Duplicate\n </DropdownMenuItem>\n {canMakeReusable || canDetach ? <DropdownMenuSeparator /> : null}\n {canMakeReusable ? (\n <DropdownMenuItem onClick={() => reuse?.startMakeReusable(row.id)}>\n <Boxes /> Make reusable\n </DropdownMenuItem>\n ) : null}\n {canDetach ? (\n <DropdownMenuItem onClick={() => reuse?.detach(row.id)}>\n <Unlink /> Detach to copy\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuSeparator />\n <DropdownMenuItem\n variant=\"destructive\"\n onClick={() => builder.dispatch({ type: 'remove', id: row.id })}\n >\n <Trash2 /> 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":";AAAA,OAAO,EACL,KAAK,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,cAAc,EACd,UAAU,EACV,IAAI,EACJ,MAAM,EACN,MAAM,EACN,cAAc,EACd,IAAI,EACJ,MAAM,EACN,MAAM,GACP,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,WAAW,GACZ,MAAM,sCAAsC,CAAA;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AACpD,OAAO,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,GAAG,EACH,MAAM,EACN,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAYjF,SAAS,OAAO,CACd,IAAmB,EACnB,KAAa,EACb,IAAe,EACf,IAA4D;IAE5D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;IACpG,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACvD,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAC5B,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE;YAC9B,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,MAAM,EAAE,CAAC,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC;YACjC,MAAM,EAAE,KAAK;SACd,CAAC,CACH,CAAA;IACH,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,WAAmB;IAClC,OAAO,WAAW,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AAC5E,CAAC;AAED,wFAAwF;AACxF,MAAM,gBAAgB,GAAiD;IACrE,IAAI,EAAE,sDAAsD;IAC5D,MAAM,EAAE,kCAAkC;IAC1C,KAAK,EAAE,mCAAmC;IAC1C,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,EAAE,mDAAmD;IACzD,KAAK,EAAE,oDAAoD;IAC3D,OAAO,EAAE,oEAAoE;CAC9E,CAAA;AAED,MAAM,UAAU,QAAQ,CAAC,EAAE,UAAU,EAA8C;IACjF,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAc,EAAE,CAAA;IAC1B,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IAEhF,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,sCAAsC;IACtC,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAA;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,KAAK,WAAW,CAAA;IACnD,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IAChD,aAAa,CAAC,OAAO,GAAG,OAAO,CAAC,UAAU,CAAA;IAC1C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ;YAAE,OAAM;QACpB,OAAO,wBAAwB,CAAC,MAAM,EAAE;YACtC,SAAS;YACT,aAAa,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO;YAC1C,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE;SAC1C,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;IAEzB,0EAA0E;IAC1E,4EAA4E;IAC5E,+DAA+D;IAC/D,wEAAwE;IACxE,yDAAyD;IACzD,MAAM,WAAW,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IACjD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAqD,IAAI,CAAC,CAAA;IAC1F,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,WAAW,CAAC,OAAO,GAAG,IAAI,CAAA;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAA;IACf,CAAC,CAAA;IACD,SAAS,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;IACxD,MAAM,WAAW,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;IAEnE,SAAS,kBAAkB,CAAC,GAAY,EAAE,CAAkB;QAC1D,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,SAAS,CACP;YACE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE;YACvE,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;SAChC,EACD,CAAC,CAAC,YAAY,CACf,CAAA;IACH,CAAC;IAED,SAAS,iBAAiB,CAAC,GAAY,EAAE,CAAkB;QACzD,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,IAAI,QAAQ,GAAiB,MAAM,CAAA;QACnC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAA;YACpD,kEAAkE;YAClE,sEAAsE;YACtE,MAAM,IAAI,GAAG,iBAAiB,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAA;YAC9E,MAAM,KAAK,GACT,IAAI,KAAK,YAAY;gBACnB,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC;oBACd,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK;oBACtC,CAAC,CAAC,GAAG;gBACP,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;oBACf,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM;oBACtC,CAAC,CAAC,GAAG,CAAA;YACX,QAAQ,GAAG,sBAAsB,CAAC;gBAChC,KAAK;gBACL,QAAQ,EAAE,WAAW,CAAC,WAAW,EAAE,GAAG,CAAC,WAAW,CAAC;gBACnD,KAAK,EAAE,IAAI;aACZ,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,WAAW,CAAC;YAC7B,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,WAAW;YACX,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE;SACvC,CAAC,CAAA;QACF,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QACxD,WAAW,CAAC,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAA;QAC7D,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAA;QACrC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;YACxB,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,CAAC,CAAC,YAAY,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;QAC5E,CAAC;IACH,CAAC;IAED,SAAS,aAAa,CAAC,CAAkB;QACvC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAA;QAChC,SAAS,EAAE,CAAA;QACX,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,CAAC,CAAC,cAAc,EAAE,CAAA;QAClB,wEAAwE;QACxE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAC1E,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAChC,qEAAqE;YACrE,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,KAAK,SAAS;gBACvB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;gBAChD,CAAC,CAAC,IAAI,CAAC,SAAS;oBACd,CAAC,CAAC,EAAE,IAAI,EAAE,WAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;oBACxD,CAAC,CAAC,IAAI,CAAA;YACZ,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,WAAW,CAAC,CAAA;gBACpB,OAAM;YACR,CAAC;YACD,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;QAC7F,CAAC;aAAM,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC1B,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACnF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,WAAW,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,OAAO,CAAC,SAAS,CAAC,CAAA;IACpB,CAAC;IAED,SAAS,gBAAgB;QACvB,SAAS,EAAE,CAAA;QACX,OAAO,CAAC,WAAW,CAAC,CAAA;IACtB,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC3C,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,2CAA2C,aAChE,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,YAC9B,SAAS,CAAC,wBAAwB,CAAC,GAC/B,EACP,KAAC,MAAM,KAAG,EACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,kBAC/B,IAAI,CAAC,MAAM,GACP,IACH,EACN,cACE,SAAS,EAAC,oCAAoC,EAC9C,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EACjE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;oBACjB,0DAA0D;oBAC1D,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,aAA4B,CAAC;wBAAE,SAAS,EAAE,CAAA;gBAC5E,CAAC,YAEA,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;oBAChB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;oBAC7D,MAAM,QAAQ,GAAG,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,UAAU,CAAA;oBAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,SAAS,CAAA;oBAC5C,0EAA0E;oBAC1E,IAAI,SAAS,GAAkC,MAAM,CAAA;oBACrD,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;wBAClB,MAAM,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;wBAC9C,SAAS,GAAG,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAA;oBACrE,CAAC;oBACD,OAAO,CACL,eAEE,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,EACnE,SAAS,EAAE,CAAC,GAAG,CAAC,MAAM,EACtB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,CAAC,EAC9C,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,CAAC,CAAC,EAC5C,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,gBAAgB,mBACZ,GAAG,CAAC,EAAE,oBACL,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,EAClF,SAAS,EAAE,EAAE,CACX,6EAA6E,EAC7E,QAAQ;4BACN,CAAC,CAAC,kCAAkC;4BACpC,CAAC,CAAC,OAAO;gCACP,CAAC,CAAC,0BAA0B;gCAC5B,CAAC,CAAC,uBAAuB,EAC7B,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,SAAS;4BACtC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC;4BAClC,CAAC,CAAC,SAAS,CACd,aAED,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,EAC/D,SAAS,EAAC,+CAA+C,EACzD,KAAK,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,aAEjD,eAAM,SAAS,EAAE,EAAE,CAAC,oBAAoB,EAAE,SAAS,KAAK,QAAQ,IAAI,yBAAyB,CAAC,YAC3F,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GACpB,EACN,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CACjB,eAAM,SAAS,EAAC,mCAAmC,YAAE,QAAQ,CAAC,KAAK,GAAQ,CAC5E,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,CACxB,KAAC,MAAM,IACL,SAAS,EAAC,8BAA8B,gBAC5B,SAAS,CAAC,oCAAoC,CAAC,GAC3D,CACH,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,CAC5B,KAAC,MAAM,IACL,SAAS,EAAC,0CAA0C,gBACxC,SAAS,CAAC,8BAA8B,CAAC,GACrD,CACH,CAAC,CAAC,CAAC,IAAI,IACD,EAET,eACE,SAAS,EAAE,EAAE,CACX,oCAAoC,EACpC,QAAQ;oCACN,CAAC,CAAC,aAAa;oCACf,CAAC,CAAC,kEAAkE,CACvE,aAKD,KAAC,UAAU,IAAC,KAAK,EAAE,SAAS,CAAC,2BAA2B,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,YAC1F,KAAC,IAAI,KAAG,GACG,EACb,KAAC,cAAc,IAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAI,IAChE,KA7DD,GAAG,CAAC,EAAE,CA8DP,CACP,CAAA;gBACH,CAAC,CAAC,GACE,IACF,CACP,CAAA;AACH,CAAC;AAED;iFACiF;AACjF,SAAS,YAAY,CAAC,EAAE,QAAQ,EAAiC;IAC/D,OAAO,eAAM,SAAS,EAAC,4DAA4D,YAAE,QAAQ,GAAQ,CAAA;AACvG,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,cAAc,CAAC,EACtB,GAAG,EACH,OAAO,EACP,SAAS,GAKV;IACC,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;IACxB,MAAM,eAAe,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,CAAA;IAChF,MAAM,SAAS,GAAG,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,CAAA;IACnD,OAAO,CACL,MAAC,YAAY,eACX,KAAC,mBAAmB,IAAC,OAAO,kBAC1B,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,gBACH,SAAS,CAAC,8BAA8B,CAAC,EACrD,KAAK,EAAE,SAAS,CAAC,8BAA8B,CAAC,EAChD,SAAS,EAAC,QAAQ,YAElB,KAAC,cAAc,KAAG,GACX,GACW,EACtB,MAAC,mBAAmB,IAAC,KAAK,EAAC,KAAK,aAC9B,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,aACzD,KAAC,aAAa,KAAG,OAAE,SAAS,CAAC,aAAa,CAAC,OAAE,KAAC,YAAY,0BAAkB,IAC3D,EACnB,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,aACxE,KAAC,cAAc,KAAG,OAAE,SAAS,CAAC,yBAAyB,CAAC,OAAE,KAAC,YAAY,0BAAkB,IACxE,EAClB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CACb,8BACE,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,aACxD,KAAC,UAAU,KAAG,OAAE,SAAS,CAAC,uBAAuB,CAAC,OAAE,KAAC,YAAY,0BAAkB,IAClE,EACnB,KAAC,qBAAqB,KAAG,EACzB,MAAC,gBAAgB,IACf,QAAQ,EAAE,GAAG,CAAC,OAAO,EACrB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,aAE3E,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,yBAAyB,CAAC,IAClC,EACnB,MAAC,gBAAgB,IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EACpB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,aAE1E,KAAC,WAAW,KAAG,OAAE,SAAS,CAAC,2BAA2B,CAAC,IACtC,EACnB,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,aAClF,KAAC,IAAI,KAAG,OAAE,SAAS,CAAC,kBAAkB,CAAC,IACtB,EAClB,eAAe,IAAI,SAAS,CAAC,CAAC,CAAC,KAAC,qBAAqB,KAAG,CAAC,CAAC,CAAC,IAAI,EAC/D,eAAe,CAAC,CAAC,CAAC,CACjB,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,aAC/D,KAAC,KAAK,KAAG,OAAE,SAAS,CAAC,4BAA4B,CAAC,IACjC,CACpB,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,CAAC,CAAC,CAAC,CACX,MAAC,gBAAgB,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,aACpD,KAAC,MAAM,KAAG,OAAE,SAAS,CAAC,4BAA4B,CAAC,IAClC,CACpB,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,qBAAqB,KAAG,EACzB,MAAC,gBAAgB,IACf,OAAO,EAAC,aAAa,EACrB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,aAE/D,KAAC,MAAM,KAAG,OAAE,SAAS,CAAC,eAAe,CAAC,IACrB,IAClB,CACJ,CAAC,CAAC,CAAC,IAAI,IACY,IACT,CAChB,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,OAAO,EACP,QAAQ,EACR,WAAW,EACX,QAAQ,GAOT;IACC,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,gBACH,KAAK,EACjB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,EAAE,CAAC,QAAQ,EAAE,WAAW,IAAI,wBAAwB,CAAC,YAE/D,QAAQ,GACF,CACV,CAAA;AACH,CAAC","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 { 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\nfunction shortId(componentId: string): string {\n return componentId.replace('base.primitives.', '').replace('project.', '')\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"]}