@elytracms/studio 0.0.88 → 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 +49 -43
- package/convex/cms.ts +3 -4
- package/convex/delivery.ts +8 -11
- package/convex/document-revalidate.test.ts +188 -0
- package/convex/redirect-revalidate.test.ts +50 -37
- package/convex/revalidate.ts +18 -30
- package/convex/triggers.ts +47 -38
- package/convex/webhooks.ts +77 -62
- package/dist/components/account/self-account-view-source.js +2 -1
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/ai/ai-chat-panel.js +1 -1
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/app-views/default-modules.js +3 -3
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/assets/asset-grid.js +1 -1
- package/dist/components/assets/asset-grid.js.map +1 -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/assets/assets-collection-view-source.js +3 -2
- package/dist/components/assets/assets-collection-view-source.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-context.js +2 -2
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +7 -3
- package/dist/components/builder/reuse.js +19 -11
- 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/cell-edit.d.ts +0 -6
- package/dist/components/cms/cell-edit.js +5 -23
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +3 -2
- package/dist/components/cms/cms-store.js +23 -24
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +14 -4
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +28 -24
- 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/duplicate-document.d.ts +3 -2
- package/dist/components/cms/duplicate-document.js +5 -5
- package/dist/components/cms/duplicate-document.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 +10 -9
- 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/publish-clean.d.ts +13 -6
- package/dist/components/cms/publish-clean.js +14 -6
- package/dist/components/cms/publish-clean.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/use-publish-controller.js +3 -2
- package/dist/components/cms/use-publish-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-dialog.js +1 -0
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +20 -12
- package/dist/components/components/transfer.js +27 -21
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/delivery-health-banner.d.ts +3 -2
- package/dist/components/delivery-health-banner.js +3 -2
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.js +3 -2
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/users/users-collection-view-source.js +2 -1
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/lib/ai/apply-content.js +32 -28
- package/dist/lib/ai/apply-content.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/translate-document.js +11 -7
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/types.d.ts +9 -5
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.js +15 -32
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/ai/vite.d.ts +22 -2
- package/dist/lib/ai/vite.js +12 -3
- package/dist/lib/ai/vite.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/published-documents.d.ts +14 -3
- package/dist/lib/cms/published-documents.js +12 -3
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +4 -4
- package/dist/lib/cms-views/view-model.js +26 -20
- 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 +70 -29
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/delivery/instant-publish.d.ts +0 -3
- package/dist/lib/delivery/instant-publish.js +0 -1
- package/dist/lib/delivery/instant-publish.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/locale/fallback.d.ts +5 -19
- package/dist/lib/locale/fallback.js +18 -39
- package/dist/lib/locale/fallback.js.map +1 -1
- package/dist/lib/locale/locale-store.js +2 -1
- package/dist/lib/locale/locale-store.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/operations/studio-operations.js +4 -6
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/backend-mode.d.ts +5 -25
- package/dist/lib/persistence/backend-mode.js +13 -30
- package/dist/lib/persistence/backend-mode.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 +17 -5
- package/dist/lib/references/reference-model.js +20 -28
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.js +7 -0
- package/dist/lib/references/references-store.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/lib/workspace/index.d.ts +0 -7
- package/dist/lib/workspace/index.js +0 -9
- package/dist/lib/workspace/index.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 -7
- package/dist/lib/registry/project-components.d.ts +0 -16
- package/dist/lib/registry/project-components.js.map +0 -1
- package/dist/lib/webhooks/config.d.ts +0 -15
- package/dist/lib/webhooks/config.js +0 -65
- package/dist/lib/webhooks/config.js.map +0 -1
- package/dist/lib/webhooks/dispatcher.d.ts +0 -70
- package/dist/lib/webhooks/dispatcher.js +0 -61
- package/dist/lib/webhooks/dispatcher.js.map +0 -1
- package/dist/lib/webhooks/index.d.ts +0 -11
- package/dist/lib/webhooks/index.js +0 -10
- package/dist/lib/webhooks/index.js.map +0 -1
- package/dist/lib/webhooks/signing.d.ts +0 -15
- package/dist/lib/webhooks/signing.js +0 -24
- package/dist/lib/webhooks/signing.js.map +0 -1
- package/dist/lib/webhooks/tags.d.ts +0 -25
- package/dist/lib/webhooks/tags.js +0 -289
- package/dist/lib/webhooks/tags.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inline-edit.js","sourceRoot":"","sources":["../../../src/components/builder/inline-edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { inlineEditablePropNames } from '@elytracms/core/component-registry'\nimport { isStaticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { parseRichTextValue, richTextDocsEquivalent } from '@elytracms/core/rich-text'\nimport type { BlockNode, RichTextDoc, RichTextNode, RichTextValue } from '@elytracms/core/rich-text'\n\n/**\n * Inline text editing on the canvas (EC-158) — framework-free core.\n *\n * The canvas wires DOM events (dblclick/keydown/blur) to this module; the\n * module owns the editing-session state machine and the\n * one-gesture-one-command guarantee. A session finishes exactly once: the\n * first `commit()`/`cancel()` wins and every later call returns `null`, so\n * overlapping DOM signals (Enter immediately followed by blur, blur racing a\n * teardown) can never dispatch a second operations command or stack a second\n * undo step.\n *\n * Locale semantics (EC-176): inline editing targets STATIC graph props only,\n * and static graph props are locale-independent by design — the canonical\n * graph stores one value per prop, not per-locale variants. Bound props are\n * never inline-editable (below), so fallback-shown BOUND text on the canvas\n * cannot be edited in place either: translating localized content happens in\n * the CMS surfaces, where the active-locale variant is created explicitly.\n * Nothing here needs the ambient locale.\n */\n\n/** The single graph mutation a finished session may produce. */\nexport interface InlineEditCommit {\n kind: 'commit'\n nodeId: string\n propName: string\n /** The edited text to write as the prop's static value. */\n value: string\n}\n\n/** A cancelled session: restore this text on the DOM, dispatch nothing. */\nexport interface InlineEditCancel {\n kind: 'cancel'\n /** The original rendered value to restore. */\n value: string\n}\n\nexport interface InlineEditSession {\n readonly nodeId: string\n readonly propName: string\n readonly original: string\n /** False once the session committed or cancelled. */\n readonly active: boolean\n /**\n * Finish by committing `text`. Returns the one commit outcome, or `null`\n * when the session already finished or the text is unchanged (an unchanged\n * commit finishes the session but dispatches nothing — graph untouched).\n */\n commit(text: string): InlineEditCommit | null\n /** Finish by cancelling. Returns the restore outcome once, then `null`. */\n cancel(): InlineEditCancel | null\n}\n\nexport function createInlineEditSession(args: {\n nodeId: string\n propName: string\n original: string\n}): InlineEditSession {\n let active = true\n return {\n nodeId: args.nodeId,\n propName: args.propName,\n original: args.original,\n get active() {\n return active\n },\n commit(text: string): InlineEditCommit | null {\n if (!active) return null\n active = false\n if (text === args.original) return null\n return { kind: 'commit', nodeId: args.nodeId, propName: args.propName, value: text }\n },\n cancel(): InlineEditCancel | null {\n if (!active) return null\n active = false\n return { kind: 'cancel', value: args.original }\n },\n }\n}\n\n/** A node prop that can be edited in place right now. */\nexport interface InlineEditableProp {\n propName: string\n /** The current static string value (or string default when unset). */\n value: string\n}\n\n/**\n * Resolve which prop of `node` an inline-edit gesture should target: the\n * first manifest prop flagged `inlineEditable` whose *current* value is a\n * plain static string (or unset with a string default). Bound props and\n * non-string values are never inline-editable — they stay inspector-only.\n */\nexport function resolveInlineEditableProp(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n): InlineEditableProp | null {\n if (!manifest) return null\n for (const propName of inlineEditablePropNames(manifest)) {\n const current = node.props?.[propName]\n if (current === undefined) {\n const fallback = manifest.props[propName]?.default\n if (typeof fallback === 'string') return { propName, value: fallback }\n continue\n }\n if (isStaticProp(current) && typeof current.value === 'string') {\n return { propName, value: current.value }\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Rich-text inline editing (EC-159) — the same session contract over the\n// `@elytracms/core/rich-text` storage format.\n// ---------------------------------------------------------------------------\n\n/** A node prop carrying rich-text content that can be edited in place now. */\nexport interface InlineRichTextProp {\n propName: string\n /** The current stored value (or the manifest default when unset). */\n value: RichTextValue\n}\n\n/**\n * Resolve which rich-text prop of `node` an inline-edit gesture should\n * target: the first manifest prop flagged `inlineEditable` whose inspector\n * control is `richtext` and whose *current* value is a static, schema-valid\n * rich-text value (or unset with a valid rich-text default). Bound props are\n * never inline-editable — translating/bound content stays inspector- and\n * CMS-owned, exactly like EC-158's plain-text rule.\n */\nexport function resolveInlineRichTextProp(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n): InlineRichTextProp | null {\n if (!manifest) return null\n for (const propName of inlineEditablePropNames(manifest)) {\n // EC-190: a rich-text prop is the `richText` field-def type (was control.kind 'richtext').\n if (manifest.props[propName]?.type !== 'richText') continue\n const current = node.props?.[propName]\n const raw = current === undefined ? manifest.props[propName]?.default : undefined\n if (current !== undefined && !isStaticProp(current)) continue\n const parsed = parseRichTextValue(isStaticProp(current) ? current.value : raw)\n if (parsed.ok) return { propName, value: parsed.value }\n }\n return null\n}\n\n/** The single graph mutation a finished rich-text session may produce. */\nexport interface InlineRichTextCommit {\n kind: 'commit'\n nodeId: string\n propName: string\n /** The edited content to write as the prop's static value. */\n value: RichTextValue\n}\n\nexport interface InlineRichTextSession {\n readonly nodeId: string\n readonly propName: string\n readonly original: RichTextValue\n /** False once the session committed or cancelled. */\n readonly active: boolean\n /**\n * Finish by committing the parsed-back document. Returns the one commit\n * outcome, or `null` when the session already finished or the document is\n * equivalent to the original. An equivalent commit finishes the session and\n * dispatches nothing — the stored value is never rewritten, so untouched\n * content round-trips byte-identical.\n */\n commit(doc: RichTextDoc): InlineRichTextCommit | null\n /** Finish by cancelling. Returns `true` exactly once (restore the DOM). */\n cancel(): boolean\n}\n\n/**\n * One rich-text editing session (EC-159), with EC-158's one-finish guarantee:\n * the first `commit()`/`cancel()` wins and every later call is inert, so\n * overlapping DOM signals can never dispatch a second operations command or\n * stack a second undo step. The committed value keeps the original's stored\n * schema `version` stamp — only the document tree changes.\n */\nexport function createInlineRichTextSession(args: {\n nodeId: string\n propName: string\n original: RichTextValue\n}): InlineRichTextSession {\n let active = true\n return {\n nodeId: args.nodeId,\n propName: args.propName,\n original: args.original,\n get active() {\n return active\n },\n commit(doc: RichTextDoc): InlineRichTextCommit | null {\n if (!active) return null\n active = false\n if (richTextDocsEquivalent(doc, { type: 'doc', content: (args.original.content ?? []) as RichTextNode[] })) return null\n return {\n kind: 'commit',\n nodeId: args.nodeId,\n propName: args.propName,\n // EC-547: the committed doc is render/edit-time; storage is the v2 sequence.\n value: { version: args.original.version, content: (doc.content ?? []) as BlockNode[] },\n }\n },\n cancel(): boolean {\n if (!active) return false\n active = false\n return true\n },\n }\n}\n\n/**\n * The structural slice of `Element` the host finder needs — kept minimal so\n * the descent logic is unit-testable without a DOM.\n */\nexport interface InlineHostElement {\n readonly childElementCount: number\n readonly firstElementChild: InlineHostElement | null\n readonly textContent: string | null\n getAttribute(name: string): string | null\n}\n\n/**\n * Find the rendered element inside a node's `.ec-editable` wrapper that hosts\n * the editable text. Descends single-child chains as long as no text is lost,\n * never crossing into another node's wrapper, and only accepts a host whose\n * text content is *exactly* the stored prop value — if the component renders\n * the prop transformed (or alongside other text), inline editing is skipped\n * and the inspector remains the editing surface. That exactness is what makes\n * edit-mode typography pixel-identical to render state: we edit the rendered\n * element itself.\n */\nexport function findInlineTextHost(\n wrapper: InlineHostElement,\n expected: string,\n): InlineHostElement | null {\n let el = wrapper.firstElementChild\n if (!el) return null\n if (el.getAttribute('data-ec-node-id') !== null) return null\n while (\n el.childElementCount === 1 &&\n el.firstElementChild!.getAttribute('data-ec-node-id') === null &&\n el.firstElementChild!.textContent === el.textContent\n ) {\n el = el.firstElementChild!\n }\n return (el.textContent ?? '') === expected ? el : null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"inline-edit.js","sourceRoot":"","sources":["../../../src/components/builder/inline-edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { inlineEditablePropNames } from '@elytracms/core/component-registry'\nimport { isStaticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { parseRichTextValue, richTextDocsEquivalent } from '@elytracms/core/rich-text'\nimport type { BlockNode, RichTextDoc, RichTextNode, RichTextValue } from '@elytracms/core/rich-text'\n\n/**\n * Inline text editing on the canvas (EC-158) — framework-free core.\n *\n * The canvas wires DOM events (dblclick/keydown/blur) to this module; the\n * module owns the editing-session state machine and the\n * one-gesture-one-command guarantee. A session finishes exactly once: the\n * first `commit()`/`cancel()` wins and every later call returns `null`, so\n * overlapping DOM signals (Enter immediately followed by blur, blur racing a\n * teardown) can never dispatch a second operations command or stack a second\n * undo step.\n *\n * Locale semantics (EC-176): inline editing targets STATIC graph props only,\n * and static graph props are locale-independent by design — the canonical\n * graph stores one value per prop, not per-locale variants. Bound props are\n * never inline-editable (below), so fallback-shown BOUND text on the canvas\n * cannot be edited in place either: translating localized content happens in\n * the CMS surfaces, where the active-locale variant is created explicitly.\n * Nothing here needs the ambient locale.\n */\n\n/** The single graph mutation a finished session may produce. */\nexport interface InlineEditCommit {\n kind: 'commit'\n nodeId: string\n propName: string\n /** The edited text to write as the prop's static value. */\n value: string\n}\n\n/** A cancelled session: restore this text on the DOM, dispatch nothing. */\nexport interface InlineEditCancel {\n kind: 'cancel'\n /** The original rendered value to restore. */\n value: string\n}\n\nexport interface InlineEditSession {\n readonly nodeId: string\n readonly propName: string\n readonly original: string\n /** False once the session committed or cancelled. */\n readonly active: boolean\n /**\n * Finish by committing `text`. Returns the one commit outcome, or `null`\n * when the session already finished or the text is unchanged (an unchanged\n * commit finishes the session but dispatches nothing — graph untouched).\n */\n commit(text: string): InlineEditCommit | null\n /** Finish by cancelling. Returns the restore outcome once, then `null`. */\n cancel(): InlineEditCancel | null\n}\n\nexport function createInlineEditSession(args: {\n nodeId: string\n propName: string\n original: string\n}): InlineEditSession {\n let active = true\n return {\n nodeId: args.nodeId,\n propName: args.propName,\n original: args.original,\n get active() {\n return active\n },\n commit(text: string): InlineEditCommit | null {\n if (!active) return null\n active = false\n if (text === args.original) return null\n return { kind: 'commit', nodeId: args.nodeId, propName: args.propName, value: text }\n },\n cancel(): InlineEditCancel | null {\n if (!active) return null\n active = false\n return { kind: 'cancel', value: args.original }\n },\n }\n}\n\n/** A node prop that can be edited in place right now. */\nexport interface InlineEditableProp {\n propName: string\n /** The current static string value (or string default when unset). */\n value: string\n}\n\n/**\n * Resolve which prop of `node` an inline-edit gesture should target: the\n * first manifest prop flagged `inlineEditable` whose *current* value is a\n * plain static string, or that is unset and `text`-typed — an unset prop starts\n * EMPTY (EC-591/2f: absent is `undefined`; the manifest `default` is a hint, never\n * the value being edited). Bound props and non-string values are never\n * inline-editable — they stay inspector-only.\n */\nexport function resolveInlineEditableProp(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n): InlineEditableProp | null {\n if (!manifest) return null\n for (const propName of inlineEditablePropNames(manifest)) {\n const current = node.props?.[propName]\n if (current === undefined) {\n if (manifest.props[propName]?.type === 'text') return { propName, value: '' }\n continue\n }\n if (isStaticProp(current) && typeof current.value === 'string') {\n return { propName, value: current.value }\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Rich-text inline editing (EC-159) — the same session contract over the\n// `@elytracms/core/rich-text` storage format.\n// ---------------------------------------------------------------------------\n\n/** A node prop carrying rich-text content that can be edited in place now. */\nexport interface InlineRichTextProp {\n propName: string\n /** The current stored value (an empty document when unset — EC-591/2f). */\n value: RichTextValue\n}\n\n/**\n * Resolve which rich-text prop of `node` an inline-edit gesture should\n * target: the first manifest prop flagged `inlineEditable` whose inspector\n * control is `richtext` and whose *current* value is a static, schema-valid\n * rich-text value, or that is unset — then the session starts from an EMPTY\n * document (EC-591/2f: never the manifest `default`, a hint only). Bound props are\n * never inline-editable — translating/bound content stays inspector- and\n * CMS-owned, exactly like EC-158's plain-text rule.\n */\nexport function resolveInlineRichTextProp(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n): InlineRichTextProp | null {\n if (!manifest) return null\n for (const propName of inlineEditablePropNames(manifest)) {\n // EC-190: a rich-text prop is the `richText` field-def type (was control.kind 'richtext').\n if (manifest.props[propName]?.type !== 'richText') continue\n const current = node.props?.[propName]\n if (current !== undefined && !isStaticProp(current)) continue\n const parsed = parseRichTextValue(isStaticProp(current) ? current.value : EMPTY_RICH_TEXT)\n if (parsed.ok) return { propName, value: parsed.value }\n }\n return null\n}\n\n/** The document an unset rich-text prop starts its inline session from (EC-591/2f). */\nconst EMPTY_RICH_TEXT = { version: 2, content: [{ type: 'paragraph' }] }\n\n/** The single graph mutation a finished rich-text session may produce. */\nexport interface InlineRichTextCommit {\n kind: 'commit'\n nodeId: string\n propName: string\n /** The edited content to write as the prop's static value. */\n value: RichTextValue\n}\n\nexport interface InlineRichTextSession {\n readonly nodeId: string\n readonly propName: string\n readonly original: RichTextValue\n /** False once the session committed or cancelled. */\n readonly active: boolean\n /**\n * Finish by committing the parsed-back document. Returns the one commit\n * outcome, or `null` when the session already finished or the document is\n * equivalent to the original. An equivalent commit finishes the session and\n * dispatches nothing — the stored value is never rewritten, so untouched\n * content round-trips byte-identical.\n */\n commit(doc: RichTextDoc): InlineRichTextCommit | null\n /** Finish by cancelling. Returns `true` exactly once (restore the DOM). */\n cancel(): boolean\n}\n\n/**\n * One rich-text editing session (EC-159), with EC-158's one-finish guarantee:\n * the first `commit()`/`cancel()` wins and every later call is inert, so\n * overlapping DOM signals can never dispatch a second operations command or\n * stack a second undo step. The committed value keeps the original's stored\n * schema `version` stamp — only the document tree changes.\n */\nexport function createInlineRichTextSession(args: {\n nodeId: string\n propName: string\n original: RichTextValue\n}): InlineRichTextSession {\n let active = true\n return {\n nodeId: args.nodeId,\n propName: args.propName,\n original: args.original,\n get active() {\n return active\n },\n commit(doc: RichTextDoc): InlineRichTextCommit | null {\n if (!active) return null\n active = false\n if (richTextDocsEquivalent(doc, { type: 'doc', content: (args.original.content ?? []) as RichTextNode[] })) return null\n return {\n kind: 'commit',\n nodeId: args.nodeId,\n propName: args.propName,\n // EC-547: the committed doc is render/edit-time; storage is the v2 sequence.\n value: { version: args.original.version, content: (doc.content ?? []) as BlockNode[] },\n }\n },\n cancel(): boolean {\n if (!active) return false\n active = false\n return true\n },\n }\n}\n\n/**\n * The structural slice of `Element` the host finder needs — kept minimal so\n * the descent logic is unit-testable without a DOM.\n */\nexport interface InlineHostElement {\n readonly childElementCount: number\n readonly firstElementChild: InlineHostElement | null\n readonly textContent: string | null\n getAttribute(name: string): string | null\n}\n\n/**\n * Find the rendered element inside a node's `.ec-editable` wrapper that hosts\n * the editable text. Descends single-child chains as long as no text is lost,\n * never crossing into another node's wrapper, and only accepts a host whose\n * text content is *exactly* the stored prop value — if the component renders\n * the prop transformed (or alongside other text), inline editing is skipped\n * and the inspector remains the editing surface. That exactness is what makes\n * edit-mode typography pixel-identical to render state: we edit the rendered\n * element itself.\n */\nexport function findInlineTextHost(\n wrapper: InlineHostElement,\n expected: string,\n): InlineHostElement | null {\n let el = wrapper.firstElementChild\n if (!el) return null\n if (el.getAttribute('data-ec-node-id') !== null) return null\n while (\n el.childElementCount === 1 &&\n el.firstElementChild!.getAttribute('data-ec-node-id') === null &&\n el.firstElementChild!.textContent === el.textContent\n ) {\n el = el.firstElementChild!\n }\n return (el.textContent ?? '') === expected ? el : null\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getNode, getParentInfo } from '@elytracms/core/project-graph';
|
|
2
2
|
import { getStudioRegistry } from '@elytracms/studio/lib/registry';
|
|
3
|
-
import {
|
|
3
|
+
import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
|
|
4
4
|
import { fieldAllowAt, manifestRules, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules';
|
|
5
5
|
import { hasInsertableComponent, intersectAllow } from './blocks-model.js';
|
|
6
6
|
/**
|
|
@@ -39,7 +39,7 @@ export function resolveInsertContext(builder, nodeId, fieldAllow, studio = getSt
|
|
|
39
39
|
? {
|
|
40
40
|
target: { parentId: nodeId, slot: childSlot.name },
|
|
41
41
|
slotAllow: childSlot.allow,
|
|
42
|
-
destinationLabel: `${
|
|
42
|
+
destinationLabel: `${componentDisplayName(node.componentId, manifest)} · ${childSlot.name}`,
|
|
43
43
|
}
|
|
44
44
|
: resolveSiblingContext(studio, root, nodeId);
|
|
45
45
|
// The field vocabulary governs only the top level (a direct child of the page
|
|
@@ -57,6 +57,10 @@ function withFieldLeash(root, ctx, fieldAllow) {
|
|
|
57
57
|
const leash = fieldAllowAt(root, ctx.target.parentId, fieldAllow);
|
|
58
58
|
return leash ? { ...ctx, slotAllow: intersectAllow(ctx.slotAllow, leash) } : ctx;
|
|
59
59
|
}
|
|
60
|
+
/** A destination's component label — its manifest title (EC-591: never a stripped id prefix). */
|
|
61
|
+
function parentLabel(studio, componentId) {
|
|
62
|
+
return componentDisplayName(componentId, studio.registry.getManifest(componentId));
|
|
63
|
+
}
|
|
60
64
|
/** Sibling-or-root insert context for a node with no insertable child slot. */
|
|
61
65
|
function resolveSiblingContext(studio, root, nodeId) {
|
|
62
66
|
const info = getParentInfo(root, nodeId);
|
|
@@ -68,7 +72,7 @@ function resolveSiblingContext(studio, root, nodeId) {
|
|
|
68
72
|
return {
|
|
69
73
|
target: { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },
|
|
70
74
|
slotAllow: allow,
|
|
71
|
-
destinationLabel: `${
|
|
75
|
+
destinationLabel: `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
78
|
return { target: { parentId: nodeId, slot: 'children' }, destinationLabel: 'root' };
|
|
@@ -106,7 +110,7 @@ export function resolveInsertContextAt(builder, nodeId, position, fieldAllow, st
|
|
|
106
110
|
index: (node.slots?.[slot.name] ?? []).length,
|
|
107
111
|
},
|
|
108
112
|
slotAllow: slot.allow,
|
|
109
|
-
destinationLabel: `${
|
|
113
|
+
destinationLabel: `${componentDisplayName(node.componentId, manifest)} · ${slot.name}`,
|
|
110
114
|
};
|
|
111
115
|
}
|
|
112
116
|
else {
|
|
@@ -122,7 +126,7 @@ export function resolveInsertContextAt(builder, nodeId, position, fieldAllow, st
|
|
|
122
126
|
index: info.index + (position === 'after' ? 1 : 0),
|
|
123
127
|
},
|
|
124
128
|
slotAllow: pManifest?.slots.find((s) => s.name === info.slot)?.allow,
|
|
125
|
-
destinationLabel: `${
|
|
129
|
+
destinationLabel: `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,
|
|
126
130
|
};
|
|
127
131
|
}
|
|
128
132
|
// EC-544: closed-set reading, same as `resolveInsertContext` above.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert-context.js","sourceRoot":"","sources":["../../../src/components/builder/insert-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\nimport { fieldAllowAt, manifestRules, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { hasInsertableComponent, intersectAllow } from './blocks-model'\nimport type { InsertContext } from './insert-menu'\n\n/**\n * Where an insert lands: the canvas insert-context resolution, shared by every\n * surface that mounts the canvas engine.\n *\n * Extracted from the retired standalone `BuilderShell` (EC-210) so the in-CMS\n * Canvas editor (`composition-field-editor.tsx`, EC-188/AD-12) can own it without\n * importing the dead shell. No UI here — pure graph/registry resolution.\n */\n\n/**\n * Resolve where an insert lands: into the node's child slot, else as a sibling.\n *\n * `fieldAllow` (EC-188) is the composition field's TOP-LEVEL vocabulary\n * (`field.allow`). It constrains ONLY inserts that land as a direct child of the\n * active page root — the top level of a composition field value — where it is\n * intersected into `slotAllow` so forbidden components are never even offered,\n * mirroring the top-level leash in `validateCompositionValue`. Deeper inserts keep\n * following each component's own slot `allow`. Callers that omit `fieldAllow` get\n * an unconstrained top level. EC-554: which placements the field governs is read\n * through `fieldAllowAt` — the SAME reader drag-and-drop and paste judge with, so\n * the offer and the placement can never disagree.\n */\nexport function resolveInsertContext(\n builder: { present: ComponentNode },\n nodeId: string,\n fieldAllow?: readonly string[],\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n // EC-386: resolve against the merged slot vocabulary (`manifestRules` folds\n // blocks-prop regions in), through the SAME `primaryInsertSlot` rule a DnD\n // `into` uses — so a `project.Section.body`-style region receives the insert\n // instead of silently degrading to a sibling insert.\n const manifest = studio.registry.getManifest(node.componentId)\n const childSlot = primaryInsertSlot(manifestRules(manifest))\n const ctx: InsertContext = childSlot\n ? {\n target: { parentId: nodeId, slot: childSlot.name },\n slotAllow: childSlot.allow,\n destinationLabel: `${shortId(node.componentId)} · ${childSlot.name}`,\n }\n : resolveSiblingContext(studio, root, nodeId)\n\n // The field vocabulary governs only the top level (a direct child of the page\n // root). EC-544: an `allow` list is a closed set — absent = any, a list (even\n // an empty one) = exactly those ids — the same reading the validator applies,\n // so an empty intersection here is the \"No allowed components match\" state.\n return withFieldLeash(root, ctx, fieldAllow)\n}\n\n/**\n * Layer the composition field's top-level vocabulary over the resolved slot\n * `allow` (EC-188) — for a direct child of the root only, as decided by\n * {@link fieldAllowAt} (EC-554: the one reader every placement path shares).\n */\nfunction withFieldLeash(\n root: ComponentNode,\n ctx: InsertContext,\n fieldAllow: readonly string[] | undefined,\n): InsertContext {\n const leash = fieldAllowAt(root, ctx.target.parentId, fieldAllow)\n return leash ? { ...ctx, slotAllow: intersectAllow(ctx.slotAllow, leash) } : ctx\n}\n\n/** Sibling-or-root insert context for a node with no insertable child slot. */\nfunction resolveSiblingContext(studio: StudioRegistry, root: ComponentNode, nodeId: string): InsertContext {\n const info = getParentInfo(root, nodeId)\n if (info) {\n // EC-386: the parent slot may be a blocks-prop region — look it up on the\n // merged vocabulary so its `allow` leash still filters sibling inserts.\n const pManifest = manifestRules(studio.registry.getManifest(info.parent.componentId))\n const allow = pManifest?.slots.find((s) => s.name === info.slot)?.allow\n return {\n target: { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },\n slotAllow: allow,\n destinationLabel: `${shortId(info.parent.componentId)} · ${info.slot}`,\n }\n }\n return { target: { parentId: nodeId, slot: 'children' }, destinationLabel: 'root' }\n}\n\n/**\n * An in-canvas insertion position relative to a reference node (EC-405) — the\n * DnD position vocabulary (`lib/dnd/drop-rules`), narrowed to the three the\n * insertion affordances offer: `before`/`after` a sibling, or `into` an (empty)\n * container. `left`/`right` (the wrap-into-columns gesture) are DnD-only.\n */\nexport type InsertPosition = 'before' | 'after' | 'into'\n\n/**\n * Resolve an `InsertContext` for an EXPLICIT position relative to `nodeId`\n * (EC-405) — the position-carrying twin of {@link resolveInsertContext}, so an\n * affordance click at a chosen edge lands the insert at exactly that spot rather\n * than the panel re-deriving it from the selection.\n *\n * The slot resolution reuses the shared DnD rules — `into` uses\n * {@link primaryInsertSlot} (the same slot a drop `into` picks; `null` when the\n * node accepts no children), `before`/`after` use the reference's parent slot at\n * `index`/`index + 1` — so this surface and drag-insert can never disagree about\n * where a position lands or what it allows. `fieldAllow` (EC-188) still constrains\n * ONLY a direct child of the composition root, mirroring `resolveInsertContext`.\n */\nexport function resolveInsertContextAt(\n builder: { present: ComponentNode },\n nodeId: string,\n position: InsertPosition,\n fieldAllow?: readonly string[],\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n\n let ctx: InsertContext\n if (position === 'into') {\n // EC-386: `manifestRules` folds blocks-prop regions into the slot set, so\n // an affordance/drop `into` a Section-style body lands in that region.\n const manifest = studio.registry.getManifest(node.componentId)\n const slot = primaryInsertSlot(manifestRules(manifest))\n if (!slot) return null // the node accepts no children — no `into` position\n ctx = {\n target: {\n parentId: nodeId,\n slot: slot.name,\n index: (node.slots?.[slot.name] ?? []).length,\n },\n slotAllow: slot.allow,\n destinationLabel: `${shortId(node.componentId)} · ${slot.name}`,\n }\n } else {\n const info = getParentInfo(root, nodeId)\n if (!info) return null // the root has no siblings — no before/after position\n // EC-386: merged vocabulary — a blocks-prop parent region keeps its leash.\n const pManifest = manifestRules(studio.registry.getManifest(info.parent.componentId))\n ctx = {\n target: {\n parentId: info.parent.id,\n slot: info.slot,\n index: info.index + (position === 'after' ? 1 : 0),\n },\n slotAllow: pManifest?.slots.find((s) => s.name === info.slot)?.allow,\n destinationLabel: `${shortId(info.parent.componentId)} · ${info.slot}`,\n }\n }\n\n // EC-544: closed-set reading, same as `resolveInsertContext` above.\n return withFieldLeash(root, ctx, fieldAllow)\n}\n\n/**\n * Which affordance positions are actually insertable at `nodeId` (EC-405 scope 3):\n * a position is offered only when it structurally exists AND at least one registry\n * component may land there (the SAME slot-rule filter the tree/DnD and the blocks\n * panel apply — {@link hasInsertableComponent} over the resolved `slotAllow`), so a\n * blocked spot shows no dead button.\n *\n * An EMPTY, insertable container surfaces ONLY `into` — the meaningful action there\n * is \"add the first child\", and an empty container often renders zero-height, so\n * its own before/after edges are degenerate and would pile the three \"+\" buttons\n * onto one point. A filled container and every leaf get before/after instead (each\n * child already carries its own edge affordances, so a filled container needs no\n * center \"+\").\n */\nexport function insertablePositions(\n builder: { present: ComponentNode },\n nodeId: string,\n fieldAllow?: readonly string[],\n studio: StudioRegistry = getStudioRegistry(),\n): InsertPosition[] {\n // Empty insertable container → into only.\n const into = resolveInsertContextAt(builder, nodeId, 'into', fieldAllow, studio)\n if (into && (into.target.index ?? 0) === 0 && hasInsertableComponent(studio, into.slotAllow)) {\n return ['into']\n }\n // Otherwise the sibling edges that accept something.\n const out: InsertPosition[] = []\n for (const position of ['before', 'after'] as const) {\n const ctx = resolveInsertContextAt(builder, nodeId, position, fieldAllow, studio)\n if (ctx && hasInsertableComponent(studio, ctx.slotAllow)) out.push(position)\n }\n return out\n}\n"]}
|
|
1
|
+
{"version":3,"file":"insert-context.js","sourceRoot":"","sources":["../../../src/components/builder/insert-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { fieldAllowAt, manifestRules, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { hasInsertableComponent, intersectAllow } from './blocks-model'\nimport type { InsertContext } from './insert-menu'\n\n/**\n * Where an insert lands: the canvas insert-context resolution, shared by every\n * surface that mounts the canvas engine.\n *\n * Extracted from the retired standalone `BuilderShell` (EC-210) so the in-CMS\n * Canvas editor (`composition-field-editor.tsx`, EC-188/AD-12) can own it without\n * importing the dead shell. No UI here — pure graph/registry resolution.\n */\n\n/**\n * Resolve where an insert lands: into the node's child slot, else as a sibling.\n *\n * `fieldAllow` (EC-188) is the composition field's TOP-LEVEL vocabulary\n * (`field.allow`). It constrains ONLY inserts that land as a direct child of the\n * active page root — the top level of a composition field value — where it is\n * intersected into `slotAllow` so forbidden components are never even offered,\n * mirroring the top-level leash in `validateCompositionValue`. Deeper inserts keep\n * following each component's own slot `allow`. Callers that omit `fieldAllow` get\n * an unconstrained top level. EC-554: which placements the field governs is read\n * through `fieldAllowAt` — the SAME reader drag-and-drop and paste judge with, so\n * the offer and the placement can never disagree.\n */\nexport function resolveInsertContext(\n builder: { present: ComponentNode },\n nodeId: string,\n fieldAllow?: readonly string[],\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n // EC-386: resolve against the merged slot vocabulary (`manifestRules` folds\n // blocks-prop regions in), through the SAME `primaryInsertSlot` rule a DnD\n // `into` uses — so a `project.Section.body`-style region receives the insert\n // instead of silently degrading to a sibling insert.\n const manifest = studio.registry.getManifest(node.componentId)\n const childSlot = primaryInsertSlot(manifestRules(manifest))\n const ctx: InsertContext = childSlot\n ? {\n target: { parentId: nodeId, slot: childSlot.name },\n slotAllow: childSlot.allow,\n destinationLabel: `${componentDisplayName(node.componentId, manifest)} · ${childSlot.name}`,\n }\n : resolveSiblingContext(studio, root, nodeId)\n\n // The field vocabulary governs only the top level (a direct child of the page\n // root). EC-544: an `allow` list is a closed set — absent = any, a list (even\n // an empty one) = exactly those ids — the same reading the validator applies,\n // so an empty intersection here is the \"No allowed components match\" state.\n return withFieldLeash(root, ctx, fieldAllow)\n}\n\n/**\n * Layer the composition field's top-level vocabulary over the resolved slot\n * `allow` (EC-188) — for a direct child of the root only, as decided by\n * {@link fieldAllowAt} (EC-554: the one reader every placement path shares).\n */\nfunction withFieldLeash(\n root: ComponentNode,\n ctx: InsertContext,\n fieldAllow: readonly string[] | undefined,\n): InsertContext {\n const leash = fieldAllowAt(root, ctx.target.parentId, fieldAllow)\n return leash ? { ...ctx, slotAllow: intersectAllow(ctx.slotAllow, leash) } : ctx\n}\n\n/** A destination's component label — its manifest title (EC-591: never a stripped id prefix). */\nfunction parentLabel(studio: StudioRegistry, componentId: string): string {\n return componentDisplayName(componentId, studio.registry.getManifest(componentId))\n}\n\n/** Sibling-or-root insert context for a node with no insertable child slot. */\nfunction resolveSiblingContext(studio: StudioRegistry, root: ComponentNode, nodeId: string): InsertContext {\n const info = getParentInfo(root, nodeId)\n if (info) {\n // EC-386: the parent slot may be a blocks-prop region — look it up on the\n // merged vocabulary so its `allow` leash still filters sibling inserts.\n const pManifest = manifestRules(studio.registry.getManifest(info.parent.componentId))\n const allow = pManifest?.slots.find((s) => s.name === info.slot)?.allow\n return {\n target: { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },\n slotAllow: allow,\n destinationLabel: `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,\n }\n }\n return { target: { parentId: nodeId, slot: 'children' }, destinationLabel: 'root' }\n}\n\n/**\n * An in-canvas insertion position relative to a reference node (EC-405) — the\n * DnD position vocabulary (`lib/dnd/drop-rules`), narrowed to the three the\n * insertion affordances offer: `before`/`after` a sibling, or `into` an (empty)\n * container. `left`/`right` (the wrap-into-columns gesture) are DnD-only.\n */\nexport type InsertPosition = 'before' | 'after' | 'into'\n\n/**\n * Resolve an `InsertContext` for an EXPLICIT position relative to `nodeId`\n * (EC-405) — the position-carrying twin of {@link resolveInsertContext}, so an\n * affordance click at a chosen edge lands the insert at exactly that spot rather\n * than the panel re-deriving it from the selection.\n *\n * The slot resolution reuses the shared DnD rules — `into` uses\n * {@link primaryInsertSlot} (the same slot a drop `into` picks; `null` when the\n * node accepts no children), `before`/`after` use the reference's parent slot at\n * `index`/`index + 1` — so this surface and drag-insert can never disagree about\n * where a position lands or what it allows. `fieldAllow` (EC-188) still constrains\n * ONLY a direct child of the composition root, mirroring `resolveInsertContext`.\n */\nexport function resolveInsertContextAt(\n builder: { present: ComponentNode },\n nodeId: string,\n position: InsertPosition,\n fieldAllow?: readonly string[],\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n\n let ctx: InsertContext\n if (position === 'into') {\n // EC-386: `manifestRules` folds blocks-prop regions into the slot set, so\n // an affordance/drop `into` a Section-style body lands in that region.\n const manifest = studio.registry.getManifest(node.componentId)\n const slot = primaryInsertSlot(manifestRules(manifest))\n if (!slot) return null // the node accepts no children — no `into` position\n ctx = {\n target: {\n parentId: nodeId,\n slot: slot.name,\n index: (node.slots?.[slot.name] ?? []).length,\n },\n slotAllow: slot.allow,\n destinationLabel: `${componentDisplayName(node.componentId, manifest)} · ${slot.name}`,\n }\n } else {\n const info = getParentInfo(root, nodeId)\n if (!info) return null // the root has no siblings — no before/after position\n // EC-386: merged vocabulary — a blocks-prop parent region keeps its leash.\n const pManifest = manifestRules(studio.registry.getManifest(info.parent.componentId))\n ctx = {\n target: {\n parentId: info.parent.id,\n slot: info.slot,\n index: info.index + (position === 'after' ? 1 : 0),\n },\n slotAllow: pManifest?.slots.find((s) => s.name === info.slot)?.allow,\n destinationLabel: `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,\n }\n }\n\n // EC-544: closed-set reading, same as `resolveInsertContext` above.\n return withFieldLeash(root, ctx, fieldAllow)\n}\n\n/**\n * Which affordance positions are actually insertable at `nodeId` (EC-405 scope 3):\n * a position is offered only when it structurally exists AND at least one registry\n * component may land there (the SAME slot-rule filter the tree/DnD and the blocks\n * panel apply — {@link hasInsertableComponent} over the resolved `slotAllow`), so a\n * blocked spot shows no dead button.\n *\n * An EMPTY, insertable container surfaces ONLY `into` — the meaningful action there\n * is \"add the first child\", and an empty container often renders zero-height, so\n * its own before/after edges are degenerate and would pile the three \"+\" buttons\n * onto one point. A filled container and every leaf get before/after instead (each\n * child already carries its own edge affordances, so a filled container needs no\n * center \"+\").\n */\nexport function insertablePositions(\n builder: { present: ComponentNode },\n nodeId: string,\n fieldAllow?: readonly string[],\n studio: StudioRegistry = getStudioRegistry(),\n): InsertPosition[] {\n // Empty insertable container → into only.\n const into = resolveInsertContextAt(builder, nodeId, 'into', fieldAllow, studio)\n if (into && (into.target.index ?? 0) === 0 && hasInsertableComponent(studio, into.slotAllow)) {\n return ['into']\n }\n // Otherwise the sibling edges that accept something.\n const out: InsertPosition[] = []\n for (const position of ['before', 'after'] as const) {\n const ctx = resolveInsertContextAt(builder, nodeId, position, fieldAllow, studio)\n if (ctx && hasInsertableComponent(studio, ctx.slotAllow)) out.push(position)\n }\n return out\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ComponentNode, SpacingTokenName } from '@elytracms/core/project-graph';
|
|
2
|
-
export declare function layoutManagedPropNames(node: ComponentNode): readonly string[];
|
|
1
|
+
import type { ComponentNode, LayoutContainerIds, SpacingTokenName } from '@elytracms/core/project-graph';
|
|
2
|
+
export declare function layoutManagedPropNames(node: ComponentNode, layout: LayoutContainerIds): readonly string[];
|
|
3
3
|
/**
|
|
4
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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CONTAINER_ALIGNMENTS, GRID_MAX_COLUMNS, SPACING_TOKEN_NAMES,
|
|
2
|
+
import { CONTAINER_ALIGNMENTS, GRID_MAX_COLUMNS, SPACING_TOKEN_NAMES, STACK_ON_MOBILE_PROP, gridStacksOnMobile, isLayoutContainerNode, layoutContainerKind, isSpacingTokenName, isStaticProp, spacingTokenFromLegacyGap, staticProp, } from '@elytracms/core/project-graph';
|
|
3
3
|
import { Field, InspectorSection, Select, Stack } from '@elytracms/studio/ui';
|
|
4
4
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
5
5
|
import { useBuilder } from './builder-store.js';
|
|
@@ -13,8 +13,8 @@ import { useBuilder } from './builder-store.js';
|
|
|
13
13
|
*/
|
|
14
14
|
/** The props this section owns for a container (hidden from the generic Props list). */
|
|
15
15
|
const LAYOUT_PROPS = ['direction', 'gap', 'align', 'columns', STACK_ON_MOBILE_PROP];
|
|
16
|
-
export function layoutManagedPropNames(node) {
|
|
17
|
-
return isLayoutContainerNode(node) ? LAYOUT_PROPS : [];
|
|
16
|
+
export function layoutManagedPropNames(node, layout) {
|
|
17
|
+
return isLayoutContainerNode(node, layout) ? LAYOUT_PROPS : [];
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* i18n message key per spacing token step (EC-161 scale) — no CSS lengths.
|
|
@@ -73,12 +73,14 @@ const COLUMN_OPTIONS = Array.from({ length: GRID_MAX_COLUMNS }, (_, i) => i + 1)
|
|
|
73
73
|
/** The contextual layout section. Renders nothing for non-container nodes. */
|
|
74
74
|
export function LayoutSection({ node }) {
|
|
75
75
|
const builder = useBuilder();
|
|
76
|
-
|
|
76
|
+
// EC-591: a layout container is whichever component fills a layout role.
|
|
77
|
+
const kind = layoutContainerKind(node.componentId, builder.layout);
|
|
78
|
+
if (!kind)
|
|
77
79
|
return null;
|
|
78
|
-
const isStack =
|
|
80
|
+
const isStack = kind === 'stack';
|
|
79
81
|
const set = (name, value) => builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(value) });
|
|
80
82
|
const clear = (name) => builder.dispatch({ type: 'removeProp', id: node.id, name });
|
|
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'
|
|
83
|
+
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, builder.layout) ? 'stack' : 'keep', onChange: (e) => e.target.value === 'keep'
|
|
82
84
|
? set(STACK_ON_MOBILE_PROP, false)
|
|
83
85
|
: 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] }) }));
|
|
84
86
|
}
|
|
@@ -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
|