@elytracms/studio 0.0.90 → 0.0.91
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.
- package/convex/asset-revalidate.test.ts +14 -1
- package/convex/cms.ts +3 -4
- package/convex/document-revalidate.test.ts +14 -1
- package/convex/redirect-revalidate.test.ts +14 -1
- package/dist/components/assets/asset-selection.d.ts +8 -9
- package/dist/components/assets/asset-selection.js +26 -16
- package/dist/components/assets/asset-selection.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +7 -5
- package/dist/components/builder/blocks-model.js +11 -9
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.js +1 -1
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +11 -3
- package/dist/components/builder/builder-store.js +12 -5
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +7 -1
- package/dist/components/builder/canvas-dnd.js +6 -3
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +13 -1
- package/dist/components/builder/canvas-render-context.js +28 -18
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas.js +1 -1
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +0 -6
- package/dist/components/builder/clipboard-actions.js +9 -4
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +22 -0
- package/dist/components/builder/composition-field-editor.js +47 -8
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/inline-edit.d.ts +7 -4
- package/dist/components/builder/inline-edit.js +11 -8
- package/dist/components/builder/inline-edit.js.map +1 -1
- package/dist/components/builder/insert-context.js +9 -5
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/layout-inspector.d.ts +2 -2
- package/dist/components/builder/layout-inspector.js +8 -6
- package/dist/components/builder/layout-inspector.js.map +1 -1
- package/dist/components/builder/node-factory.d.ts +31 -14
- package/dist/components/builder/node-factory.js +73 -93
- package/dist/components/builder/node-factory.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +3 -1
- package/dist/components/builder/page-tree.js +6 -4
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/property-inspector.js +2 -1
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +1 -1
- package/dist/components/builder/reuse.js +8 -5
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/builder/selection-overlay.d.ts +2 -2
- package/dist/components/builder/selection-overlay.js +3 -3
- package/dist/components/builder/selection-overlay.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +2 -2
- package/dist/components/builder/spacing-handles.js +3 -3
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-field.d.ts +1 -1
- package/dist/components/cms/asset-field.js +15 -13
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker-core.js +3 -4
- package/dist/components/cms/asset-picker-core.js.map +1 -1
- package/dist/components/cms/detail-editor.js +10 -5
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/discard-draft.d.ts +1 -2
- package/dist/components/cms/discard-draft.js +1 -1
- package/dist/components/cms/discard-draft.js.map +1 -1
- package/dist/components/cms/editable-cell.js +11 -7
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control-lib.d.ts +10 -0
- package/dist/components/cms/field-control-lib.js +20 -0
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/field-control.js +3 -3
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/object-array-field.js +3 -5
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/prop-fields.d.ts +16 -3
- package/dist/components/cms/prop-fields.js +57 -12
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.d.ts +6 -1
- package/dist/components/cms/rich-text/embed-props.js +7 -12
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/components/cms/rich-text/insert.d.ts +13 -6
- package/dist/components/cms/rich-text/insert.js +16 -17
- package/dist/components/cms/rich-text/insert.js.map +1 -1
- package/dist/components/cms/use-cell-edit.js +7 -12
- package/dist/components/cms/use-cell-edit.js.map +1 -1
- package/dist/components/cms/use-discard-controller.d.ts +1 -2
- package/dist/components/cms/use-discard-controller.js +1 -2
- package/dist/components/cms/use-discard-controller.js.map +1 -1
- package/dist/components/cms/versions-pane.js +2 -2
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/components/component-detail.d.ts +1 -1
- package/dist/components/components/component-detail.js +2 -4
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.js +9 -4
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/components-index.d.ts +2 -2
- package/dist/components/components/components-index.js +3 -6
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/components/field-inspector.js +5 -2
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/components/registry-browser.d.ts +6 -5
- package/dist/components/components/registry-browser.js +11 -37
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.d.ts +14 -21
- package/dist/components/components/taxonomy.js +5 -29
- package/dist/components/components/taxonomy.js.map +1 -1
- package/dist/components/components/transfer.d.ts +10 -8
- package/dist/components/components/transfer.js +17 -13
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +16 -14
- package/dist/lib/ai/constraints.js +64 -48
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/update-fields.js +5 -3
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/assets/asset-placements.d.ts +3 -10
- package/dist/lib/assets/asset-placements.js +17 -8
- package/dist/lib/assets/asset-placements.js.map +1 -1
- package/dist/lib/clipboard/paste-target.js +3 -3
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms-views/view-model.js +11 -8
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +10 -7
- package/dist/lib/data/collection-content.js +34 -15
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +22 -7
- package/dist/lib/dnd/drop-rules.js +21 -14
- package/dist/lib/dnd/drop-rules.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +25 -9
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +21 -7
- package/dist/lib/i18n/messages.en.js +28 -9
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/cms-persistence.js +7 -4
- package/dist/lib/operations/cms-persistence.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +4 -9
- package/dist/lib/persona/labels.js +5 -12
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/references/reference-model.d.ts +0 -2
- package/dist/lib/references/reference-model.js +3 -5
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/registry/__fixtures__/demo-components.d.ts +32 -0
- package/dist/lib/registry/{project-components.js → __fixtures__/demo-components.js} +18 -5
- package/dist/lib/registry/__fixtures__/demo-components.js.map +1 -0
- package/dist/lib/registry/index.d.ts +9 -13
- package/dist/lib/registry/index.js +34 -61
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/preview.d.ts +22 -7
- package/dist/lib/registry/preview.js +26 -89
- package/dist/lib/registry/preview.js.map +1 -1
- package/dist/previews/enumerate.d.ts +23 -27
- package/dist/previews/enumerate.js +27 -48
- package/dist/previews/enumerate.js.map +1 -1
- package/dist/previews/harness.js +1 -1
- package/dist/previews/harness.js.map +1 -1
- package/package.json +4 -3
- package/dist/lib/registry/project-components.d.ts +0 -16
- package/dist/lib/registry/project-components.js.map +0 -1
|
@@ -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 isStaticProp,\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 isStaticProp(prop) ? 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"]}
|
|
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_ON_MOBILE_PROP,\n gridStacksOnMobile,\n isLayoutContainerNode,\n layoutContainerKind,\n isSpacingTokenName,\n isStaticProp,\n spacingTokenFromLegacyGap,\n staticProp,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n ContainerAlignment,\n LayoutContainerIds,\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, layout: LayoutContainerIds): readonly string[] {\n return isLayoutContainerNode(node, layout) ? 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 isStaticProp(prop) ? 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 // EC-591: a layout container is whichever component fills a layout role.\n const kind = layoutContainerKind(node.componentId, builder.layout)\n if (!kind) return null\n const isStack = kind === 'stack'\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, builder.layout) ? '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"]}
|
|
@@ -1,33 +1,50 @@
|
|
|
1
1
|
import type { ComponentNode } from '@elytracms/core/project-graph';
|
|
2
|
-
import type { ComponentManifest } from '@elytracms/core/component-registry';
|
|
2
|
+
import type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry';
|
|
3
3
|
/**
|
|
4
|
-
* Build a fresh node for a component
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Build a fresh node for a component (brief §8.2, EC-066). The placeholder id is
|
|
5
|
+
* reassigned by the Builder store on insert. Required slots are left empty so
|
|
6
|
+
* the renderer shows a missing-slot placeholder (EC-063) prompting the author to
|
|
7
|
+
* fill them.
|
|
8
|
+
*
|
|
9
|
+
* EC-591 / ratified 2f: the node carries NO props. An absent prop is `undefined`
|
|
10
|
+
* and the implementation's own default applies — on the canvas and on delivery
|
|
11
|
+
* alike; the manifest `default` is an authoring hint (the inspector's greyed
|
|
12
|
+
* placeholder), never baked into a stored node. (Until EC-591 every manifest
|
|
13
|
+
* `default` was copied in here, so an insert and a "copy exact configuration"
|
|
14
|
+
* paste of the same fixture stored — and rendered — different nodes.)
|
|
8
15
|
*
|
|
9
16
|
* EC-547 (EC-500 D1): every factory node goes through `componentBlockNode`, so
|
|
10
17
|
* it carries the `elytra/component` discriminator from birth — a newly created
|
|
11
18
|
* canvas node is a v2 block-sequence citizen without any tagging on write.
|
|
12
19
|
*/
|
|
13
20
|
export declare function nodeFromManifest(manifest: ComponentManifest): ComponentNode;
|
|
14
|
-
/** Layout helper presets (brief §8.2, EC-067) —
|
|
21
|
+
/** Layout helper presets (brief §8.2, EC-067) — seeded layout containers. */
|
|
15
22
|
export interface LayoutPreset {
|
|
16
23
|
key: string;
|
|
17
24
|
label: string;
|
|
18
25
|
description: string;
|
|
19
26
|
build: () => ComponentNode;
|
|
20
27
|
}
|
|
21
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The layout presets the component LIST supports (EC-591): a seeded `Stack` and a
|
|
30
|
+
* seeded `Grid` — built from whichever components fill the `layout.stack` /
|
|
31
|
+
* `layout.grid` roles, seeded with the `text`-role component (a preset needs ≥2
|
|
32
|
+
* children to survive EC-161 normalization, EC-383). No hardcoded engine ids: a
|
|
33
|
+
* role nobody fills means its preset is not offered (the canvas notice says why),
|
|
34
|
+
* and with no `text` component there is no seed copy, so no preset at all.
|
|
35
|
+
* Container, Repeater and Switch are plain components — the blocks panel offers
|
|
36
|
+
* them as their own tiles, from their manifests.
|
|
37
|
+
*/
|
|
38
|
+
export declare function layoutPresets(registry: ComponentRegistry): LayoutPreset[];
|
|
22
39
|
/**
|
|
23
40
|
* Build a fresh node for insertion, seeding layout containers so they survive
|
|
24
41
|
* (EC-383). A bare `Stack`/`Grid` inserts empty, and EC-161 normalization
|
|
25
42
|
* dissolves BOTH empty and single-child containers on commit — so a bare
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* a
|
|
31
|
-
* non-container manifest is returned unchanged (
|
|
43
|
+
* container insert vanishes instantly and reads as a broken insert. Seeding a
|
|
44
|
+
* layout container exactly like its {@link layoutPresets} sibling (≥2 children)
|
|
45
|
+
* gives it visible, survivable content, so the registry "Default" tile behaves
|
|
46
|
+
* like the preset instead of disappearing. A component that fills no layout role
|
|
47
|
+
* (a Container, a Repeater) is not normalized, so its bare node survives
|
|
48
|
+
* untouched; every non-container manifest is returned unchanged (EC-066).
|
|
32
49
|
*/
|
|
33
|
-
export declare function seededNodeFromManifest(manifest: ComponentManifest): ComponentNode;
|
|
50
|
+
export declare function seededNodeFromManifest(manifest: ComponentManifest, registry: ComponentRegistry): ComponentNode;
|
|
@@ -1,112 +1,92 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { layoutContainerKind, staticProp } from '@elytracms/core/project-graph';
|
|
2
|
+
import { localizedStringVariants } from '@elytracms/core/cms-core';
|
|
2
3
|
import { componentBlockNode } from '@elytracms/core/rich-text';
|
|
3
4
|
/**
|
|
4
|
-
* Build a fresh node for a component
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Build a fresh node for a component (brief §8.2, EC-066). The placeholder id is
|
|
6
|
+
* reassigned by the Builder store on insert. Required slots are left empty so
|
|
7
|
+
* the renderer shows a missing-slot placeholder (EC-063) prompting the author to
|
|
8
|
+
* fill them.
|
|
9
|
+
*
|
|
10
|
+
* EC-591 / ratified 2f: the node carries NO props. An absent prop is `undefined`
|
|
11
|
+
* and the implementation's own default applies — on the canvas and on delivery
|
|
12
|
+
* alike; the manifest `default` is an authoring hint (the inspector's greyed
|
|
13
|
+
* placeholder), never baked into a stored node. (Until EC-591 every manifest
|
|
14
|
+
* `default` was copied in here, so an insert and a "copy exact configuration"
|
|
15
|
+
* paste of the same fixture stored — and rendered — different nodes.)
|
|
8
16
|
*
|
|
9
17
|
* EC-547 (EC-500 D1): every factory node goes through `componentBlockNode`, so
|
|
10
18
|
* it carries the `elytra/component` discriminator from birth — a newly created
|
|
11
19
|
* canvas node is a v2 block-sequence citizen without any tagging on write.
|
|
12
20
|
*/
|
|
13
21
|
export function nodeFromManifest(manifest) {
|
|
14
|
-
|
|
15
|
-
for (const [name, spec] of Object.entries(manifest.props)) {
|
|
16
|
-
if (spec.default !== undefined)
|
|
17
|
-
props[name] = staticProp(spec.default);
|
|
18
|
-
}
|
|
19
|
-
return componentBlockNode({ id: 'tmp', componentId: manifest.id, props });
|
|
22
|
+
return componentBlockNode({ id: 'tmp', componentId: manifest.id, props: {} });
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
/** The display title of a role-filling component (its manifest title, else its id). */
|
|
25
|
+
function titleOf(manifest) {
|
|
26
|
+
return localizedStringVariants(manifest.title)[0] ?? manifest.id;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The layout presets the component LIST supports (EC-591): a seeded `Stack` and a
|
|
30
|
+
* seeded `Grid` — built from whichever components fill the `layout.stack` /
|
|
31
|
+
* `layout.grid` roles, seeded with the `text`-role component (a preset needs ≥2
|
|
32
|
+
* children to survive EC-161 normalization, EC-383). No hardcoded engine ids: a
|
|
33
|
+
* role nobody fills means its preset is not offered (the canvas notice says why),
|
|
34
|
+
* and with no `text` component there is no seed copy, so no preset at all.
|
|
35
|
+
* Container, Repeater and Switch are plain components — the blocks panel offers
|
|
36
|
+
* them as their own tiles, from their manifests.
|
|
37
|
+
*/
|
|
38
|
+
export function layoutPresets(registry) {
|
|
39
|
+
const text = registry.roleComponent('text');
|
|
40
|
+
if (!text)
|
|
41
|
+
return [];
|
|
42
|
+
const copy = (value) => componentBlockNode({ id: 'tmp', componentId: text.id, props: { value: staticProp(value) } });
|
|
43
|
+
const presets = [];
|
|
44
|
+
const stack = registry.roleComponent('layout.stack');
|
|
45
|
+
if (stack) {
|
|
46
|
+
presets.push({
|
|
47
|
+
key: 'stack',
|
|
48
|
+
label: titleOf(stack),
|
|
49
|
+
description: 'Vertical flex layout',
|
|
50
|
+
build: () => componentBlockNode({
|
|
51
|
+
id: 'tmp',
|
|
52
|
+
componentId: stack.id,
|
|
53
|
+
props: { gap: staticProp(4) },
|
|
54
|
+
slots: { children: [copy('First item'), copy('Second item')] },
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const grid = registry.roleComponent('layout.grid');
|
|
59
|
+
if (grid) {
|
|
60
|
+
presets.push({
|
|
61
|
+
key: 'grid',
|
|
62
|
+
label: titleOf(grid),
|
|
63
|
+
description: 'Multi-column grid',
|
|
64
|
+
build: () => componentBlockNode({
|
|
65
|
+
id: 'tmp',
|
|
66
|
+
componentId: grid.id,
|
|
67
|
+
props: { columns: staticProp(2), gap: staticProp(4) },
|
|
68
|
+
slots: { children: [copy('Cell one'), copy('Cell two')] },
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return presets;
|
|
23
73
|
}
|
|
24
|
-
export const LAYOUT_PRESETS = [
|
|
25
|
-
{
|
|
26
|
-
key: 'stack',
|
|
27
|
-
label: 'Stack',
|
|
28
|
-
description: 'Vertical flex layout',
|
|
29
|
-
build: () => componentBlockNode({
|
|
30
|
-
id: 'tmp',
|
|
31
|
-
componentId: 'base.Stack',
|
|
32
|
-
props: { gap: staticProp(4) },
|
|
33
|
-
slots: { children: [text('First item'), text('Second item')] },
|
|
34
|
-
}),
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
key: 'grid',
|
|
38
|
-
label: 'Grid',
|
|
39
|
-
description: 'Multi-column grid',
|
|
40
|
-
build: () => componentBlockNode({
|
|
41
|
-
id: 'tmp',
|
|
42
|
-
componentId: 'base.Grid',
|
|
43
|
-
props: { columns: staticProp(2), gap: staticProp(4) },
|
|
44
|
-
slots: { children: [text('Cell one'), text('Cell two')] },
|
|
45
|
-
}),
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
key: 'container',
|
|
49
|
-
label: 'Container',
|
|
50
|
-
description: 'Generic block wrapper',
|
|
51
|
-
build: () => componentBlockNode({
|
|
52
|
-
id: 'tmp',
|
|
53
|
-
componentId: 'base.Container',
|
|
54
|
-
slots: { children: [text('Container content')] },
|
|
55
|
-
}),
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
key: 'repeater',
|
|
59
|
-
label: 'Repeater',
|
|
60
|
-
description: 'Repeat a template over a bound list',
|
|
61
|
-
build: () => componentBlockNode({
|
|
62
|
-
id: 'tmp',
|
|
63
|
-
componentId: 'base.Repeater',
|
|
64
|
-
// Inserts with an empty list and an unbound template — the studio never
|
|
65
|
-
// fabricates data (Oli, 2026-06-19). The author binds `items` to a real list
|
|
66
|
-
// source in the inspector; until then it renders nothing. (Repeater's binding
|
|
67
|
-
// story is parked anyway — see RepeaterManifest "slice 5 decides its fate".)
|
|
68
|
-
props: { items: staticProp([]) },
|
|
69
|
-
slots: {
|
|
70
|
-
item: [
|
|
71
|
-
componentBlockNode({
|
|
72
|
-
id: 'tmp',
|
|
73
|
-
componentId: 'base.Stack',
|
|
74
|
-
props: { gap: staticProp(1) },
|
|
75
|
-
slots: { children: [text('Item title'), text('Item body')] },
|
|
76
|
-
}),
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
}),
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: 'switch',
|
|
83
|
-
label: 'Switch',
|
|
84
|
-
description: 'Render one case by its key',
|
|
85
|
-
build: () => componentBlockNode({
|
|
86
|
-
id: 'tmp',
|
|
87
|
-
componentId: 'base.Switch',
|
|
88
|
-
// Start with the declared `fallback` slot (renders until a `case` matches).
|
|
89
|
-
// `case` + `case:<key>` slots are authored via the inspector. Keeping only
|
|
90
|
-
// declared slots here means the inserted node validates cleanly.
|
|
91
|
-
slots: { fallback: [text('Fallback')] },
|
|
92
|
-
}),
|
|
93
|
-
},
|
|
94
|
-
];
|
|
95
74
|
/**
|
|
96
75
|
* Build a fresh node for insertion, seeding layout containers so they survive
|
|
97
76
|
* (EC-383). A bare `Stack`/`Grid` inserts empty, and EC-161 normalization
|
|
98
77
|
* dissolves BOTH empty and single-child containers on commit — so a bare
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* a
|
|
104
|
-
* non-container manifest is returned unchanged (
|
|
78
|
+
* container insert vanishes instantly and reads as a broken insert. Seeding a
|
|
79
|
+
* layout container exactly like its {@link layoutPresets} sibling (≥2 children)
|
|
80
|
+
* gives it visible, survivable content, so the registry "Default" tile behaves
|
|
81
|
+
* like the preset instead of disappearing. A component that fills no layout role
|
|
82
|
+
* (a Container, a Repeater) is not normalized, so its bare node survives
|
|
83
|
+
* untouched; every non-container manifest is returned unchanged (EC-066).
|
|
105
84
|
*/
|
|
106
|
-
export function seededNodeFromManifest(manifest) {
|
|
107
|
-
|
|
85
|
+
export function seededNodeFromManifest(manifest, registry) {
|
|
86
|
+
const kind = layoutContainerKind(manifest.id, registry.layout);
|
|
87
|
+
if (!kind)
|
|
108
88
|
return nodeFromManifest(manifest);
|
|
109
|
-
const preset =
|
|
89
|
+
const preset = layoutPresets(registry).find((p) => p.key === kind);
|
|
110
90
|
return preset ? preset.build() : nodeFromManifest(manifest);
|
|
111
91
|
}
|
|
112
92
|
//# sourceMappingURL=node-factory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-factory.js","sourceRoot":"","sources":["../../../src/components/builder/node-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAG/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAE9D
|
|
1
|
+
{"version":3,"file":"node-factory.js","sourceRoot":"","sources":["../../../src/components/builder/node-factory.ts"],"names":[],"mappings":"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","sourcesContent":["import { layoutContainerKind, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { componentBlockNode } from '@elytracms/core/rich-text'\n\n/**\n * Build a fresh node for a component (brief §8.2, EC-066). The placeholder id is\n * reassigned by the Builder store on insert. Required slots are left empty so\n * the renderer shows a missing-slot placeholder (EC-063) prompting the author to\n * fill them.\n *\n * EC-591 / ratified 2f: the node carries NO props. An absent prop is `undefined`\n * and the implementation's own default applies — on the canvas and on delivery\n * alike; the manifest `default` is an authoring hint (the inspector's greyed\n * placeholder), never baked into a stored node. (Until EC-591 every manifest\n * `default` was copied in here, so an insert and a \"copy exact configuration\"\n * paste of the same fixture stored — and rendered — different nodes.)\n *\n * EC-547 (EC-500 D1): every factory node goes through `componentBlockNode`, so\n * it carries the `elytra/component` discriminator from birth — a newly created\n * canvas node is a v2 block-sequence citizen without any tagging on write.\n */\nexport function nodeFromManifest(manifest: ComponentManifest): ComponentNode {\n return componentBlockNode({ id: 'tmp', componentId: manifest.id, props: {} })\n}\n\n/** Layout helper presets (brief §8.2, EC-067) — seeded layout containers. */\nexport interface LayoutPreset {\n key: string\n label: string\n description: string\n build: () => ComponentNode\n}\n\n/** The display title of a role-filling component (its manifest title, else its id). */\nfunction titleOf(manifest: ComponentManifest): string {\n return localizedStringVariants(manifest.title)[0] ?? manifest.id\n}\n\n/**\n * The layout presets the component LIST supports (EC-591): a seeded `Stack` and a\n * seeded `Grid` — built from whichever components fill the `layout.stack` /\n * `layout.grid` roles, seeded with the `text`-role component (a preset needs ≥2\n * children to survive EC-161 normalization, EC-383). No hardcoded engine ids: a\n * role nobody fills means its preset is not offered (the canvas notice says why),\n * and with no `text` component there is no seed copy, so no preset at all.\n * Container, Repeater and Switch are plain components — the blocks panel offers\n * them as their own tiles, from their manifests.\n */\nexport function layoutPresets(registry: ComponentRegistry): LayoutPreset[] {\n const text = registry.roleComponent('text')\n if (!text) return []\n const copy = (value: string): ComponentNode =>\n componentBlockNode({ id: 'tmp', componentId: text.id, props: { value: staticProp(value) } })\n const presets: LayoutPreset[] = []\n const stack = registry.roleComponent('layout.stack')\n if (stack) {\n presets.push({\n key: 'stack',\n label: titleOf(stack),\n description: 'Vertical flex layout',\n build: () =>\n componentBlockNode({\n id: 'tmp',\n componentId: stack.id,\n props: { gap: staticProp(4) },\n slots: { children: [copy('First item'), copy('Second item')] },\n }),\n })\n }\n const grid = registry.roleComponent('layout.grid')\n if (grid) {\n presets.push({\n key: 'grid',\n label: titleOf(grid),\n description: 'Multi-column grid',\n build: () =>\n componentBlockNode({\n id: 'tmp',\n componentId: grid.id,\n props: { columns: staticProp(2), gap: staticProp(4) },\n slots: { children: [copy('Cell one'), copy('Cell two')] },\n }),\n })\n }\n return presets\n}\n\n/**\n * Build a fresh node for insertion, seeding layout containers so they survive\n * (EC-383). A bare `Stack`/`Grid` inserts empty, and EC-161 normalization\n * dissolves BOTH empty and single-child containers on commit — so a bare\n * container insert vanishes instantly and reads as a broken insert. Seeding a\n * layout container exactly like its {@link layoutPresets} sibling (≥2 children)\n * gives it visible, survivable content, so the registry \"Default\" tile behaves\n * like the preset instead of disappearing. A component that fills no layout role\n * (a Container, a Repeater) is not normalized, so its bare node survives\n * untouched; every non-container manifest is returned unchanged (EC-066).\n */\nexport function seededNodeFromManifest(manifest: ComponentManifest, registry: ComponentRegistry): ComponentNode {\n const kind = layoutContainerKind(manifest.id, registry.layout)\n if (!kind) return nodeFromManifest(manifest)\n const preset = layoutPresets(registry).find((p) => p.key === kind)\n return preset ? preset.build() : nodeFromManifest(manifest)\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentNode } from '@elytracms/core/project-graph';
|
|
1
|
+
import type { ComponentNode, LayoutContainerIds } from '@elytracms/core/project-graph';
|
|
2
2
|
import type { DragSubject, DropIndicator, DropResolution, ManifestLookup } from '@elytracms/studio/lib/dnd/drop-rules';
|
|
3
3
|
/**
|
|
4
4
|
* The tree's dragover resolution (EC-160/EC-162), pure: the row under the pointer
|
|
@@ -10,6 +10,8 @@ import type { DragSubject, DropIndicator, DropResolution, ManifestLookup } from
|
|
|
10
10
|
export declare function resolveTreeRowDrop(args: {
|
|
11
11
|
root: ComponentNode;
|
|
12
12
|
getManifest: ManifestLookup;
|
|
13
|
+
/** The layout-role component ids (EC-591) — `builder.layout`. */
|
|
14
|
+
layout: LayoutContainerIds;
|
|
13
15
|
/** The composition field's top-level `allow` (EC-554) — `builder.field.allow`. */
|
|
14
16
|
fieldAllow: readonly string[] | undefined;
|
|
15
17
|
subject: DragSubject;
|
|
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { getParentInfo } from '@elytracms/core/project-graph';
|
|
5
5
|
import { canNestInto, containerFlowAxis, dropIndicatorFor, dropPositionForPointer, manifestRules, resolveDrop, } from '@elytracms/studio/lib/dnd/drop-rules';
|
|
6
6
|
import { getStudioRegistry } from '@elytracms/studio/lib/registry';
|
|
7
|
-
import {
|
|
7
|
+
import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
|
|
8
8
|
import { translate, useUiLocale } from '@elytracms/studio/lib/i18n';
|
|
9
9
|
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Row, Spacer, Text, } from '@elytracms/studio/ui';
|
|
10
10
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
@@ -32,12 +32,12 @@ function flatten(node, depth, rows, meta) {
|
|
|
32
32
|
* are pinned without a DOM — the handler only measures the pointer and renders.
|
|
33
33
|
*/
|
|
34
34
|
export function resolveTreeRowDrop(args) {
|
|
35
|
-
const { root, getManifest, fieldAllow, subject, row, pointer } = args;
|
|
35
|
+
const { root, getManifest, layout, fieldAllow, subject, row, pointer } = args;
|
|
36
36
|
let position = 'into';
|
|
37
37
|
if (!row.isRoot) {
|
|
38
38
|
// The hit-band axis follows the parent container's flow (EC-162):
|
|
39
39
|
// children of horizontal Stacks/Grids reorder along the row's x axis.
|
|
40
|
-
const flow = containerFlowAxis(getParentInfo(root, row.id)?.parent);
|
|
40
|
+
const flow = containerFlowAxis(getParentInfo(root, row.id)?.parent, layout);
|
|
41
41
|
position = dropPositionForPointer({
|
|
42
42
|
ratio: flow === 'horizontal' ? pointer.x : pointer.y,
|
|
43
43
|
nestable: canNestInto(getManifest, row.componentId),
|
|
@@ -47,6 +47,7 @@ export function resolveTreeRowDrop(args) {
|
|
|
47
47
|
const resolution = resolveDrop({
|
|
48
48
|
root,
|
|
49
49
|
getManifest,
|
|
50
|
+
layout,
|
|
50
51
|
fieldAllow,
|
|
51
52
|
subject,
|
|
52
53
|
location: { nodeId: row.id, position },
|
|
@@ -117,7 +118,7 @@ export function PageTree({ onAddChild }) {
|
|
|
117
118
|
beginDrag({
|
|
118
119
|
subject: { kind: 'node', nodeId: row.id, componentId: row.componentId },
|
|
119
120
|
source: 'tree',
|
|
120
|
-
label:
|
|
121
|
+
label: componentDisplayName(row.componentId, studio.registry.getManifest(row.componentId)),
|
|
121
122
|
}, e.dataTransfer);
|
|
122
123
|
}
|
|
123
124
|
function handleRowDragOver(row, e) {
|
|
@@ -128,6 +129,7 @@ export function PageTree({ onAddChild }) {
|
|
|
128
129
|
const { resolution, indicator } = resolveTreeRowDrop({
|
|
129
130
|
root: builder.present,
|
|
130
131
|
getManifest,
|
|
132
|
+
layout: builder.layout,
|
|
131
133
|
fieldAllow: builder.field.allow,
|
|
132
134
|
subject: drag.subject,
|
|
133
135
|
row,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-tree.js","sourceRoot":"","sources":["../../../src/components/builder/page-tree.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Boxes,\n ChevronUp,\n ChevronDown,\n ClipboardCopy,\n ClipboardPaste,\n ClipboardX,\n Copy,\n MoreHorizontal,\n Plus,\n Trash2,\n Unlink,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPointer,\n manifestRules,\n resolveDrop,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport type {\n DragSubject,\n DropIndicator,\n DropPlan,\n DropPosition,\n DropResolution,\n ManifestLookup,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\nimport {\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 { useBuilder } from './builder-store'\nimport { attachClipboardListeners, useStudioClipboard } from './clipboard-actions'\nimport { attachDeleteKeyListener } from './delete-key'\nimport { nodeDisplayTitle } from './node-title'\nimport { useReuse } from './reuse-context'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\n\ninterface TreeRow {\n id: string\n componentId: string\n /** The node itself — EC-407 derives the instance title from its prop values. */\n node: ComponentNode\n depth: number\n isRoot: boolean\n isFirst: boolean\n isLast: boolean\n}\n\nfunction flatten(\n node: ComponentNode,\n depth: number,\n rows: TreeRow[],\n meta: { isFirst: boolean; isLast: boolean; isRoot: boolean },\n): void {\n rows.push({ id: node.id, componentId: node.componentId, node, depth, ...meta })\n for (const children of Object.values(node.slots ?? {})) {\n children.forEach((child, i) =>\n flatten(child, depth + 1, rows, {\n isFirst: i === 0,\n isLast: i === children.length - 1,\n isRoot: false,\n }),\n )\n }\n}\n\n/**\n * The tree's dragover resolution (EC-160/EC-162), pure: the row under the pointer\n * → a drop position (the hit band along the parent's flow axis; the root row is\n * always `into`) → the shared {@link resolveDrop} verdict → the row indicator.\n * Extracted from the React handler (EC-554) so the tree surface's placement rules\n * are pinned without a DOM — the handler only measures the pointer and renders.\n */\nexport function resolveTreeRowDrop(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n /** The composition field's top-level `allow` (EC-554) — `builder.field.allow`. */\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n row: { id: string; componentId: string; isRoot: boolean }\n /** Pointer offset within the row box, 0..1 per axis (unused for the root row). */\n pointer: { x: number; y: number }\n}): { resolution: DropResolution; indicator: DropIndicator } {\n const { root, getManifest, fieldAllow, subject, row, pointer } = args\n let position: DropPosition = 'into'\n if (!row.isRoot) {\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(root, row.id)?.parent)\n position = dropPositionForPointer({\n ratio: flow === 'horizontal' ? pointer.x : pointer.y,\n nestable: canNestInto(getManifest, row.componentId),\n edges: true,\n })\n }\n const resolution = resolveDrop({\n root,\n getManifest,\n fieldAllow,\n subject,\n location: { nodeId: row.id, position },\n })\n return { resolution, indicator: dropIndicatorFor(resolution, position) }\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 uiLocale = useUiLocale()\n const rows: TreeRow[] = []\n flatten(builder.present, 0, rows, { isFirst: true, isLast: true, isRoot: true })\n\n // Copy/cut/paste (EC-175): keyboard shortcuts for the whole studio window\n // (the canvas iframe wires its own document) plus per-row affordances. The\n // shared listener keeps shortcuts inert in form fields/inline editors. (A\n // \"published preview is read-only\" guard used to sit here, keyed on the\n // collaboration store's `previewMode`; that mode was a constant `'draft'` with\n // no setter caller, so the guard never fired — EC-574 deleted both.)\n const clipboard = useStudioClipboard()\n const selectedIdRef = useRef(builder.selectedId)\n selectedIdRef.current = builder.selectedId\n useEffect(\n () =>\n attachClipboardListeners(window, {\n clipboard,\n getSelectedId: () => selectedIdRef.current,\n getSelection: () => window.getSelection(),\n }),\n [clipboard],\n )\n\n // Delete/Backspace removes the selected node from the tree side too (EC-404),\n // beside the clipboard shortcuts. Inert while focus is in a form field /\n // inline editor (isEditingContext).\n const { dispatch } = builder\n const rootId = builder.present.id\n useEffect(\n () =>\n attachDeleteKeyListener(window, {\n getSelectedId: () => selectedIdRef.current,\n remove: (id) => dispatch({ type: 'remove', id }),\n getRootId: () => rootId,\n }),\n [rootId, dispatch],\n )\n\n // Drag & drop (EC-160): eligibility comes from the same pure rule set the\n // canvas uses (`lib/dnd/drop-rules`); the tree only renders the resolution.\n // One eligible drop dispatches exactly one move/insert action.\n // The current plan lives in a ref: `drop` fires synchronously after the\n // last `dragover`, before React flushes indicator state.\n const dropPlanRef = useRef<DropPlan | null>(null)\n const [drop, setDrop] = useState<{ rowId: string; indicator: DropIndicator } | null>(null)\n const clearDrop = () => {\n dropPlanRef.current = null\n setDrop(null)\n }\n useEffect(() => onDragSessionEnd(() => clearDrop()), [])\n // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a\n // Section's `body`) are drop targets with their `allow` leash, like raw slots.\n const getManifest = (id: string) => manifestRules(studio.registry.getManifest(id))\n\n function handleRowDragStart(row: TreeRow, e: React.DragEvent) {\n if (row.isRoot) {\n e.preventDefault()\n return\n }\n beginDrag(\n {\n subject: { kind: 'node', nodeId: row.id, componentId: row.componentId },\n source: 'tree',\n label: shortId(row.componentId),\n },\n e.dataTransfer,\n )\n }\n\n function handleRowDragOver(row: TreeRow, e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const rect = e.currentTarget.getBoundingClientRect()\n const { resolution, indicator } = resolveTreeRowDrop({\n root: builder.present,\n getManifest,\n fieldAllow: builder.field.allow,\n subject: drag.subject,\n row,\n pointer: {\n x: rect.width > 0 ? (e.clientX - rect.left) / rect.width : 0.5,\n y: rect.height > 0 ? (e.clientY - rect.top) / rect.height : 0.5,\n },\n })\n dropPlanRef.current = resolution.eligible ? resolution : null\n setDrop({ rowId: row.id, indicator })\n if (resolution.eligible) {\n e.preventDefault()\n e.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n }\n\n function handleRowDrop(e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlanRef.current\n clearDrop()\n if (!plan) return\n e.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n builder.dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162: shared plan vocabulary — the tree executes wrap plans too.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n builder.dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n builder.dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n function handleRowDragEnd() {\n clearDrop()\n endDrag('cancelled')\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"2\" className=\"h-10 shrink-0 border-b border-border px-3\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.pageTree.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {rows.length}\n </Text>\n </Row>\n <div\n className=\"min-h-0 flex-1 overflow-auto p-1.5\"\n onMouseLeave={() => builder.dispatch({ type: 'hover', id: null })}\n onDragLeave={(e) => {\n // Clear the indicator when the drag exits the tree panel.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) clearDrop()\n }}\n >\n {rows.map((row) => {\n const manifest = studio.registry.getManifest(row.componentId)\n const title = nodeDisplayTitle(manifest, row.node, uiLocale)\n const selected = row.id === builder.selectedId\n const hovered = row.id === builder.hoveredId\n return (\n <div\n key={row.id}\n onMouseEnter={() => builder.dispatch({ type: 'hover', id: row.id })}\n draggable={!row.isRoot}\n onDragStart={(e) => handleRowDragStart(row, e)}\n onDragOver={(e) => handleRowDragOver(row, e)}\n onDrop={handleRowDrop}\n onDragEnd={handleRowDragEnd}\n data-tree-row={row.id}\n data-tree-drop={drop?.rowId === row.id ? (drop.indicator ?? undefined) : undefined}\n className={cn(\n 'group flex items-center rounded-md pr-1 text-left text-xs transition-colors',\n selected\n ? 'bg-accent text-accent-foreground'\n : hovered\n ? 'bg-muted text-foreground'\n : 'text-muted-foreground',\n drop?.rowId === row.id && drop.indicator\n ? ROW_DROP_CLASSES[drop.indicator]\n : undefined,\n )}\n >\n <button\n type=\"button\"\n onClick={() => builder.dispatch({ type: 'select', id: row.id })}\n title={row.componentId}\n className=\"flex min-w-0 flex-1 items-center gap-1.5 py-1\"\n style={{ paddingLeft: `${6 + row.depth * 14}px` }}\n >\n {/* EC-407: instance title (its own text content) is primary; the\n localized component name is secondary context. The raw mono id\n left the row — it stays in the `title=` above + inspector header. */}\n <span className=\"truncate\">{title.primary}</span>\n {title.secondary ? (\n <span className=\"truncate text-muted-foreground/70\">{title.secondary}</span>\n ) : null}\n </button>\n\n <div\n className={cn(\n 'flex shrink-0 items-center gap-0.5',\n selected\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',\n )}\n >\n {/* Add child stays inline — the one constructive, high-frequency\n action. Everything else collapses into the ••• menu so labels\n stop cropping on the 200–240px panel (EC-211). */}\n <IconAction label={translate('builder.pageTree.addChild')} onClick={() => onAddChild(row.id)}>\n <Plus />\n </IconAction>\n <RowActionsMenu row={row} builder={builder} clipboard={clipboard} />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\n/** Right-aligned keyboard-shortcut hint in a menu item — informational only; the\n * shortcut fires globally via the clipboard listeners regardless of the menu. */\nfunction MenuShortcut({ children }: { children: React.ReactNode }) {\n return <span className=\"ml-auto pl-6 text-xs tracking-widest text-muted-foreground\">{children}</span>\n}\n\n/**\n * The per-row \"•••\" action menu (EC-211). Copy/Paste on every row; Cut, Move,\n * Duplicate, Delete for non-root rows. Collapsing the old inline icon cluster\n * (up to 8 buttons, cropping on the 200–240px panel) into a menu changes no\n * behavior — same clipboard fragment + dispatch paths, same Cmd/Ctrl shortcuts.\n * Delete stays a one-click (undoable) destructive item, consistent with the rest\n * of the studio.\n */\nfunction RowActionsMenu({\n row,\n builder,\n clipboard,\n}: {\n row: { id: string; isRoot: boolean; isFirst: boolean; isLast: boolean }\n builder: ReturnType<typeof useBuilder>\n clipboard: ReturnType<typeof useStudioClipboard>\n}) {\n // EC-282 in-canvas reuse gestures, present only where a reuse capability is\n // configured (and the row is eligible). Null outside the composition editor.\n const reuse = useReuse()\n const canMakeReusable = !row.isRoot && (reuse?.canMakeReusable(row.id) ?? false)\n const canDetach = reuse?.canDetach(row.id) ?? false\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.pageTree.moreActions')}\n title={translate('builder.pageTree.moreActions')}\n className=\"size-6\"\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => clipboard.copyNode(row.id)}>\n <ClipboardCopy /> {translate('common.copy')} <MenuShortcut>⌘C</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void clipboard.pasteFromClipboard(row.id)}>\n <ClipboardPaste /> {translate('builder.clipboard.paste')} <MenuShortcut>⌘V</MenuShortcut>\n </DropdownMenuItem>\n {!row.isRoot ? (\n <>\n <DropdownMenuItem onClick={() => clipboard.cutNode(row.id)}>\n <ClipboardX /> {translate('builder.clipboard.cut')} <MenuShortcut>⌘X</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n disabled={row.isFirst}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: -1 })}\n >\n <ChevronUp /> {translate('builder.pageTree.moveUp')}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={row.isLast}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 })}\n >\n <ChevronDown /> {translate('builder.pageTree.moveDown')}\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => builder.dispatch({ type: 'duplicate', id: row.id })}>\n <Copy /> {translate('common.duplicate')}\n </DropdownMenuItem>\n {canMakeReusable || canDetach ? <DropdownMenuSeparator /> : null}\n {canMakeReusable ? (\n <DropdownMenuItem onClick={() => reuse?.startMakeReusable(row.id)}>\n <Boxes /> {translate('builder.reuse.makeReusable')}\n </DropdownMenuItem>\n ) : null}\n {canDetach ? (\n <DropdownMenuItem onClick={() => reuse?.detach(row.id)}>\n <Unlink /> {translate('builder.reuse.detachToCopy')}\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuSeparator />\n <DropdownMenuItem\n variant=\"destructive\"\n onClick={() => builder.dispatch({ type: 'remove', id: row.id })}\n >\n <Trash2 /> {translate('common.delete')}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction IconAction({\n label,\n onClick,\n disabled,\n destructive,\n children,\n}: {\n label: string\n onClick: () => void\n disabled?: boolean\n destructive?: boolean\n children: React.ReactNode\n}) {\n return (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className={cn('size-6', destructive && 'hover:text-destructive')}\n >\n {children}\n </Button>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"page-tree.js","sourceRoot":"","sources":["../../../src/components/builder/page-tree.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Boxes,\n ChevronUp,\n ChevronDown,\n ClipboardCopy,\n ClipboardPaste,\n ClipboardX,\n Copy,\n MoreHorizontal,\n Plus,\n Trash2,\n Unlink,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { ComponentNode, LayoutContainerIds } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPointer,\n manifestRules,\n resolveDrop,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport type {\n DragSubject,\n DropIndicator,\n DropPlan,\n DropPosition,\n DropResolution,\n ManifestLookup,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\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 { useBuilder } from './builder-store'\nimport { attachClipboardListeners, useStudioClipboard } from './clipboard-actions'\nimport { attachDeleteKeyListener } from './delete-key'\nimport { nodeDisplayTitle } from './node-title'\nimport { useReuse } from './reuse-context'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\n\ninterface TreeRow {\n id: string\n componentId: string\n /** The node itself — EC-407 derives the instance title from its prop values. */\n node: ComponentNode\n depth: number\n isRoot: boolean\n isFirst: boolean\n isLast: boolean\n}\n\nfunction flatten(\n node: ComponentNode,\n depth: number,\n rows: TreeRow[],\n meta: { isFirst: boolean; isLast: boolean; isRoot: boolean },\n): void {\n rows.push({ id: node.id, componentId: node.componentId, node, depth, ...meta })\n for (const children of Object.values(node.slots ?? {})) {\n children.forEach((child, i) =>\n flatten(child, depth + 1, rows, {\n isFirst: i === 0,\n isLast: i === children.length - 1,\n isRoot: false,\n }),\n )\n }\n}\n\n/**\n * The tree's dragover resolution (EC-160/EC-162), pure: the row under the pointer\n * → a drop position (the hit band along the parent's flow axis; the root row is\n * always `into`) → the shared {@link resolveDrop} verdict → the row indicator.\n * Extracted from the React handler (EC-554) so the tree surface's placement rules\n * are pinned without a DOM — the handler only measures the pointer and renders.\n */\nexport function resolveTreeRowDrop(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n /** The layout-role component ids (EC-591) — `builder.layout`. */\n layout: LayoutContainerIds\n /** The composition field's top-level `allow` (EC-554) — `builder.field.allow`. */\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n row: { id: string; componentId: string; isRoot: boolean }\n /** Pointer offset within the row box, 0..1 per axis (unused for the root row). */\n pointer: { x: number; y: number }\n}): { resolution: DropResolution; indicator: DropIndicator } {\n const { root, getManifest, layout, fieldAllow, subject, row, pointer } = args\n let position: DropPosition = 'into'\n if (!row.isRoot) {\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(root, row.id)?.parent, layout)\n position = dropPositionForPointer({\n ratio: flow === 'horizontal' ? pointer.x : pointer.y,\n nestable: canNestInto(getManifest, row.componentId),\n edges: true,\n })\n }\n const resolution = resolveDrop({\n root,\n getManifest,\n layout,\n fieldAllow,\n subject,\n location: { nodeId: row.id, position },\n })\n return { resolution, indicator: dropIndicatorFor(resolution, position) }\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 uiLocale = useUiLocale()\n const rows: TreeRow[] = []\n flatten(builder.present, 0, rows, { isFirst: true, isLast: true, isRoot: true })\n\n // Copy/cut/paste (EC-175): keyboard shortcuts for the whole studio window\n // (the canvas iframe wires its own document) plus per-row affordances. The\n // shared listener keeps shortcuts inert in form fields/inline editors. (A\n // \"published preview is read-only\" guard used to sit here, keyed on the\n // collaboration store's `previewMode`; that mode was a constant `'draft'` with\n // no setter caller, so the guard never fired — EC-574 deleted both.)\n const clipboard = useStudioClipboard()\n const selectedIdRef = useRef(builder.selectedId)\n selectedIdRef.current = builder.selectedId\n useEffect(\n () =>\n attachClipboardListeners(window, {\n clipboard,\n getSelectedId: () => selectedIdRef.current,\n getSelection: () => window.getSelection(),\n }),\n [clipboard],\n )\n\n // Delete/Backspace removes the selected node from the tree side too (EC-404),\n // beside the clipboard shortcuts. Inert while focus is in a form field /\n // inline editor (isEditingContext).\n const { dispatch } = builder\n const rootId = builder.present.id\n useEffect(\n () =>\n attachDeleteKeyListener(window, {\n getSelectedId: () => selectedIdRef.current,\n remove: (id) => dispatch({ type: 'remove', id }),\n getRootId: () => rootId,\n }),\n [rootId, dispatch],\n )\n\n // Drag & drop (EC-160): eligibility comes from the same pure rule set the\n // canvas uses (`lib/dnd/drop-rules`); the tree only renders the resolution.\n // One eligible drop dispatches exactly one move/insert action.\n // The current plan lives in a ref: `drop` fires synchronously after the\n // last `dragover`, before React flushes indicator state.\n const dropPlanRef = useRef<DropPlan | null>(null)\n const [drop, setDrop] = useState<{ rowId: string; indicator: DropIndicator } | null>(null)\n const clearDrop = () => {\n dropPlanRef.current = null\n setDrop(null)\n }\n useEffect(() => onDragSessionEnd(() => clearDrop()), [])\n // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a\n // Section's `body`) are drop targets with their `allow` leash, like raw slots.\n const getManifest = (id: string) => manifestRules(studio.registry.getManifest(id))\n\n function handleRowDragStart(row: TreeRow, e: React.DragEvent) {\n if (row.isRoot) {\n e.preventDefault()\n return\n }\n beginDrag(\n {\n subject: { kind: 'node', nodeId: row.id, componentId: row.componentId },\n source: 'tree',\n label: componentDisplayName(row.componentId, studio.registry.getManifest(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 const rect = e.currentTarget.getBoundingClientRect()\n const { resolution, indicator } = resolveTreeRowDrop({\n root: builder.present,\n getManifest,\n layout: builder.layout,\n fieldAllow: builder.field.allow,\n subject: drag.subject,\n row,\n pointer: {\n x: rect.width > 0 ? (e.clientX - rect.left) / rect.width : 0.5,\n y: rect.height > 0 ? (e.clientY - rect.top) / rect.height : 0.5,\n },\n })\n dropPlanRef.current = resolution.eligible ? resolution : null\n setDrop({ rowId: row.id, indicator })\n if (resolution.eligible) {\n e.preventDefault()\n e.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n }\n\n function handleRowDrop(e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlanRef.current\n clearDrop()\n if (!plan) return\n e.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n builder.dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162: shared plan vocabulary — the tree executes wrap plans too.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n builder.dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n builder.dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n function handleRowDragEnd() {\n clearDrop()\n endDrag('cancelled')\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"2\" className=\"h-10 shrink-0 border-b border-border px-3\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.pageTree.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {rows.length}\n </Text>\n </Row>\n <div\n className=\"min-h-0 flex-1 overflow-auto p-1.5\"\n onMouseLeave={() => builder.dispatch({ type: 'hover', id: null })}\n onDragLeave={(e) => {\n // Clear the indicator when the drag exits the tree panel.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) clearDrop()\n }}\n >\n {rows.map((row) => {\n const manifest = studio.registry.getManifest(row.componentId)\n const title = nodeDisplayTitle(manifest, row.node, uiLocale)\n const selected = row.id === builder.selectedId\n const hovered = row.id === builder.hoveredId\n return (\n <div\n key={row.id}\n onMouseEnter={() => builder.dispatch({ type: 'hover', id: row.id })}\n draggable={!row.isRoot}\n onDragStart={(e) => handleRowDragStart(row, e)}\n onDragOver={(e) => handleRowDragOver(row, e)}\n onDrop={handleRowDrop}\n onDragEnd={handleRowDragEnd}\n data-tree-row={row.id}\n data-tree-drop={drop?.rowId === row.id ? (drop.indicator ?? undefined) : undefined}\n className={cn(\n 'group flex items-center rounded-md pr-1 text-left text-xs transition-colors',\n selected\n ? 'bg-accent text-accent-foreground'\n : hovered\n ? 'bg-muted text-foreground'\n : 'text-muted-foreground',\n drop?.rowId === row.id && drop.indicator\n ? ROW_DROP_CLASSES[drop.indicator]\n : undefined,\n )}\n >\n <button\n type=\"button\"\n onClick={() => builder.dispatch({ type: 'select', id: row.id })}\n title={row.componentId}\n className=\"flex min-w-0 flex-1 items-center gap-1.5 py-1\"\n style={{ paddingLeft: `${6 + row.depth * 14}px` }}\n >\n {/* EC-407: instance title (its own text content) is primary; the\n localized component name is secondary context. The raw mono id\n left the row — it stays in the `title=` above + inspector header. */}\n <span className=\"truncate\">{title.primary}</span>\n {title.secondary ? (\n <span className=\"truncate text-muted-foreground/70\">{title.secondary}</span>\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"]}
|
|
@@ -27,7 +27,8 @@ export function PropertyInspector() {
|
|
|
27
27
|
function SettingsTab({ node, manifest, }) {
|
|
28
28
|
// Container layout props render in the dedicated Layout section (EC-162),
|
|
29
29
|
// not as a second set of generic controls.
|
|
30
|
-
const
|
|
30
|
+
const builder = useBuilder();
|
|
31
|
+
const layoutManaged = new Set(layoutManagedPropNames(node, builder.layout));
|
|
31
32
|
// EC-190: `blocks` props are slots; EC-393: `form.hidden` props are omitted —
|
|
32
33
|
// evaluated against the node's effective sibling prop values, live as they change
|
|
33
34
|
// (the shared pure core in `cms/prop-visibility.ts`, EC-430).
|