@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,8 +1,21 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { convexTest } from 'convex-test'
|
|
3
3
|
import { makeFunctionReference } from 'convex/server'
|
|
4
4
|
import schema from './schema'
|
|
5
5
|
|
|
6
|
+
// convex-test fires a `runAfter(0)` schedule on a REAL `setTimeout`. These tests assert
|
|
7
|
+
// only on the `_scheduled_functions` rows, so the real `webhooks:deliver` action (a fetch
|
|
8
|
+
// to host.invalid) must never run: under a loaded full run it failed after this file's
|
|
9
|
+
// environment closed, and convex-test's `console.error` for it then failed the whole run
|
|
10
|
+
// (`EnvironmentTeardownError … onUserConsoleLog`, measured in the wave-0092 gate). Fake
|
|
11
|
+
// timers keep the delivery queued and never fired; the rows are still written.
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.useFakeTimers()
|
|
14
|
+
})
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.useRealTimers()
|
|
17
|
+
})
|
|
18
|
+
|
|
6
19
|
/**
|
|
7
20
|
* EC-579 — an asset edit must reach the live site (the wave-0089 carve-out; since
|
|
8
21
|
* the ruling the trigger sends the typed EVENT `{ kind: 'asset', id }`, and the host
|
package/convex/cms.ts
CHANGED
|
@@ -64,9 +64,9 @@ export const upsertDocument = mutation({
|
|
|
64
64
|
// pinned published snapshot — the mutation has both sides, so the compare runs
|
|
65
65
|
// ONCE here, in-transaction (never per list render). A no-op save compares
|
|
66
66
|
// equal → `false` (never flips to true); a revert back to the published
|
|
67
|
-
// content clears a previously-true flag the same way. Schema-
|
|
68
|
-
//
|
|
69
|
-
//
|
|
67
|
+
// content clears a previously-true flag the same way. Schema-free (AD-11 — the
|
|
68
|
+
// deployment stores documents only); the compare is the editor's exact
|
|
69
|
+
// `documentsSemanticEqual`, the ONE def-less verdict over canonical forms (EC-586). Unpublished doc / missing pinned row →
|
|
70
70
|
// absent-key patch preserves the row as-is (the flag doesn't apply).
|
|
71
71
|
let changedFlag = {}
|
|
72
72
|
if (existing.publishedVersion !== undefined) {
|
|
@@ -82,7 +82,6 @@ export const upsertDocument = mutation({
|
|
|
82
82
|
if (pinned) {
|
|
83
83
|
changedFlag = {
|
|
84
84
|
hasUnpublishedChanges: !documentsSemanticEqual(
|
|
85
|
-
undefined,
|
|
86
85
|
unfoldDocument(pinned.snapshot as CmsDocument),
|
|
87
86
|
args.document as CmsDocument,
|
|
88
87
|
),
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { convexTest } from 'convex-test'
|
|
3
3
|
import { makeFunctionReference } from 'convex/server'
|
|
4
4
|
import schema from './schema'
|
|
5
5
|
|
|
6
|
+
// convex-test fires a `runAfter(0)` schedule on a REAL `setTimeout`. These tests assert
|
|
7
|
+
// only on the `_scheduled_functions` rows, so the real `webhooks:deliver` action (a fetch
|
|
8
|
+
// to host.invalid) must never run: under a loaded full run it failed after this file's
|
|
9
|
+
// environment closed, and convex-test's `console.error` for it then failed the whole run
|
|
10
|
+
// (`EnvironmentTeardownError … onUserConsoleLog`, measured in the wave-0092 gate). Fake
|
|
11
|
+
// timers keep the delivery queued and never fired; the rows are still written.
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.useFakeTimers()
|
|
14
|
+
})
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.useRealTimers()
|
|
17
|
+
})
|
|
18
|
+
|
|
6
19
|
/**
|
|
7
20
|
* EC-579 — the write chokepoint is the ONE revalidation emitter, and it sends the
|
|
8
21
|
* EVENT, not tags.
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { convexTest } from 'convex-test'
|
|
3
3
|
import { makeFunctionReference } from 'convex/server'
|
|
4
4
|
import schema from './schema'
|
|
5
5
|
|
|
6
|
+
// convex-test fires a `runAfter(0)` schedule on a REAL `setTimeout`. These tests assert
|
|
7
|
+
// only on the `_scheduled_functions` rows, so the real `webhooks:deliver` action (a fetch
|
|
8
|
+
// to host.invalid) must never run: under a loaded full run it failed after this file's
|
|
9
|
+
// environment closed, and convex-test's `console.error` for it then failed the whole run
|
|
10
|
+
// (`EnvironmentTeardownError … onUserConsoleLog`, measured in the wave-0092 gate). Fake
|
|
11
|
+
// timers keep the delivery queued and never fired; the rows are still written.
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.useFakeTimers()
|
|
14
|
+
})
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.useRealTimers()
|
|
17
|
+
})
|
|
18
|
+
|
|
6
19
|
/**
|
|
7
20
|
* EC-510 — "a redirect change must reach the live site".
|
|
8
21
|
*
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import type { AssetFieldDef } from '@elytracms/core/cms-core';
|
|
2
|
-
import type { NewPlacementForm } from '@elytracms/studio/lib/assets/asset-placements';
|
|
3
1
|
/** The id list a `many` asset field currently holds (unreadable entries dropped). */
|
|
4
2
|
export declare function assetIdsOf(value: unknown): string[];
|
|
5
3
|
/** The single id a `one` asset field currently holds, if any. */
|
|
6
4
|
export declare function singleAssetIdOf(value: unknown): string | null;
|
|
7
5
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* Remove the `index`-th placement of a `many` field — `index` counts the readable
|
|
7
|
+
* entries, the list {@link assetIdsOf} shows — keeping every other placement VERBATIM,
|
|
8
|
+
* per-use sub-fields included (EC-586). It used to rebuild the list from the bare ids
|
|
9
|
+
* (`ids.filter(…)`), which wrote the picker's bare form AND dropped every remaining
|
|
10
|
+
* placement's caption. Removing the last placement leaves the field absent, never `[]`.
|
|
12
11
|
*/
|
|
13
|
-
export declare function
|
|
12
|
+
export declare function removeAssetPlacementAt(current: unknown, index: number): unknown;
|
|
14
13
|
/**
|
|
15
14
|
* Apply a picker selection to the stored field value, keeping every placement's
|
|
16
15
|
* per-use sub-fields (EC-558).
|
|
@@ -21,10 +20,10 @@ export declare function assetPickForm(field: Pick<AssetFieldDef, 'fields'>): New
|
|
|
21
20
|
*
|
|
22
21
|
* A no-op returns `current` itself, so a caller can skip the write.
|
|
23
22
|
*/
|
|
24
|
-
export declare function applyAssetSelection(current: unknown, picked: string | null, multiple: boolean
|
|
23
|
+
export declare function applyAssetSelection(current: unknown, picked: string | null, multiple: boolean): unknown;
|
|
25
24
|
/**
|
|
26
25
|
* Attach several picked ids to a `many` field at once: an in-picker or drop upload
|
|
27
26
|
* batch (EC-460). Each id not already held is appended once, in arrival order, and
|
|
28
27
|
* held entries stay as they are. Returns `current` itself when nothing is new.
|
|
29
28
|
*/
|
|
30
|
-
export declare function appendAssetSelections(current: unknown, picked: readonly string[]
|
|
29
|
+
export declare function appendAssetSelections(current: unknown, picked: readonly string[]): unknown;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure value logic for CMS `asset` fields (EC-075 → EC-152): a field stores
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* legacy bare
|
|
2
|
+
* Pure value logic for CMS `asset` fields (EC-075 → EC-152): a field stores one
|
|
3
|
+
* placement (cardinality `one`) or a list of them (`many`). These helpers read stored
|
|
4
|
+
* values and apply a picker selection without the UI owning the rules. EC-586: every
|
|
5
|
+
* placement a pick writes is the canonical `{ type:'asset', id, … }` envelope; reads
|
|
6
|
+
* still tolerate a legacy bare id (the tolerant reader goes after the reseed window).
|
|
7
7
|
*
|
|
8
8
|
* EC-558: the pick operations below are the ones the table cell and the detail
|
|
9
9
|
* editor BOTH commit. They work out which ids the field holds next, and hand the
|
|
@@ -28,13 +28,23 @@ export function singleAssetIdOf(value) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* Remove the `index`-th placement of a `many` field — `index` counts the readable
|
|
32
|
+
* entries, the list {@link assetIdsOf} shows — keeping every other placement VERBATIM,
|
|
33
|
+
* per-use sub-fields included (EC-586). It used to rebuild the list from the bare ids
|
|
34
|
+
* (`ids.filter(…)`), which wrote the picker's bare form AND dropped every remaining
|
|
35
|
+
* placement's caption. Removing the last placement leaves the field absent, never `[]`.
|
|
35
36
|
*/
|
|
36
|
-
export function
|
|
37
|
-
|
|
37
|
+
export function removeAssetPlacementAt(current, index) {
|
|
38
|
+
if (!Array.isArray(current))
|
|
39
|
+
return current;
|
|
40
|
+
let seen = -1;
|
|
41
|
+
const next = current.filter((entry) => {
|
|
42
|
+
if (readAssetId(entry) === undefined)
|
|
43
|
+
return true;
|
|
44
|
+
seen += 1;
|
|
45
|
+
return seen !== index;
|
|
46
|
+
});
|
|
47
|
+
return next.length === 0 ? undefined : next;
|
|
38
48
|
}
|
|
39
49
|
/**
|
|
40
50
|
* Apply a picker selection to the stored field value, keeping every placement's
|
|
@@ -46,23 +56,23 @@ export function assetPickForm(field) {
|
|
|
46
56
|
*
|
|
47
57
|
* A no-op returns `current` itself, so a caller can skip the write.
|
|
48
58
|
*/
|
|
49
|
-
export function applyAssetSelection(current, picked, multiple
|
|
59
|
+
export function applyAssetSelection(current, picked, multiple) {
|
|
50
60
|
if (!multiple)
|
|
51
|
-
return carryAssetPlacements(current, { multiple: false, id: picked }
|
|
61
|
+
return carryAssetPlacements(current, { multiple: false, id: picked });
|
|
52
62
|
if (picked === null)
|
|
53
63
|
return current;
|
|
54
|
-
return appendAssetSelections(current, [picked]
|
|
64
|
+
return appendAssetSelections(current, [picked]);
|
|
55
65
|
}
|
|
56
66
|
/**
|
|
57
67
|
* Attach several picked ids to a `many` field at once: an in-picker or drop upload
|
|
58
68
|
* batch (EC-460). Each id not already held is appended once, in arrival order, and
|
|
59
69
|
* held entries stay as they are. Returns `current` itself when nothing is new.
|
|
60
70
|
*/
|
|
61
|
-
export function appendAssetSelections(current, picked
|
|
71
|
+
export function appendAssetSelections(current, picked) {
|
|
62
72
|
const ids = assetIdsOf(current);
|
|
63
73
|
const fresh = [...new Set(picked)].filter((id) => !ids.includes(id));
|
|
64
74
|
if (fresh.length === 0)
|
|
65
75
|
return current;
|
|
66
|
-
return carryAssetPlacements(current, { multiple: true, ids: [...ids, ...fresh] }
|
|
76
|
+
return carryAssetPlacements(current, { multiple: true, ids: [...ids, ...fresh] });
|
|
67
77
|
}
|
|
68
78
|
//# sourceMappingURL=asset-selection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-selection.js","sourceRoot":"","sources":["../../../src/components/assets/asset-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"asset-selection.js","sourceRoot":"","sources":["../../../src/components/assets/asset-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+CAA+C,CAAA;AAEpF,qFAAqF;AACrF,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACpC,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,EAAE,KAAK,SAAS,CAAC,CAAA;AAC9E,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;IACjC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IACvC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;IAC9D,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAgB,EAAE,KAAa;IACpE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IAC3C,IAAI,IAAI,GAAG,CAAC,CAAC,CAAA;IACb,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QACpC,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QACjD,IAAI,IAAI,CAAC,CAAA;QACT,OAAO,IAAI,KAAK,KAAK,CAAA;IACvB,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;AAC7C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAgB,EAChB,MAAqB,EACrB,QAAiB;IAEjB,IAAI,CAAC,QAAQ;QAAE,OAAO,oBAAoB,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IACpF,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,OAAO,CAAA;IACnC,OAAO,qBAAqB,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB,EAAE,MAAyB;IAC/E,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;IAC/B,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;IACpE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAA;IACtC,OAAO,oBAAoB,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAA;AACnF,CAAC","sourcesContent":["/**\n * Pure value logic for CMS `asset` fields (EC-075 → EC-152): a field stores one\n * placement (cardinality `one`) or a list of them (`many`). These helpers read stored\n * values and apply a picker selection without the UI owning the rules. EC-586: every\n * placement a pick writes is the canonical `{ type:'asset', id, … }` envelope; reads\n * still tolerate a legacy bare id (the tolerant reader goes after the reseed window).\n *\n * EC-558: the pick operations below are the ones the table cell and the detail\n * editor BOTH commit. They work out which ids the field holds next, and hand the\n * per-use sub-field carry to the one rule, `carryAssetPlacements` (in `lib/`,\n * because AI `updateFields` uses it too). Neither writer rebuilds the value itself.\n */\nimport { readAssetId } from '@elytracms/core/cms-core'\nimport { carryAssetPlacements } from '@elytracms/studio/lib/assets/asset-placements'\n\n/** The id list a `many` asset field currently holds (unreadable entries dropped). */\nexport function assetIdsOf(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return value.map(readAssetId).filter((id): id is string => id !== undefined)\n}\n\n/** The single id a `one` asset field currently holds, if any. */\nexport function singleAssetIdOf(value: unknown): string | null {\n const direct = readAssetId(value)\n if (direct !== undefined) return direct\n if (Array.isArray(value)) return readAssetId(value[0]) ?? null\n return null\n}\n\n/**\n * Remove the `index`-th placement of a `many` field — `index` counts the readable\n * entries, the list {@link assetIdsOf} shows — keeping every other placement VERBATIM,\n * per-use sub-fields included (EC-586). It used to rebuild the list from the bare ids\n * (`ids.filter(…)`), which wrote the picker's bare form AND dropped every remaining\n * placement's caption. Removing the last placement leaves the field absent, never `[]`.\n */\nexport function removeAssetPlacementAt(current: unknown, index: number): unknown {\n if (!Array.isArray(current)) return current\n let seen = -1\n const next = current.filter((entry) => {\n if (readAssetId(entry) === undefined) return true\n seen += 1\n return seen !== index\n })\n return next.length === 0 ? undefined : next\n}\n\n/**\n * Apply a picker selection to the stored field value, keeping every placement's\n * per-use sub-fields (EC-558).\n * - `one`: the picked id replaces the image and keeps the placement's sub-fields.\n * Picking the id it already shows is a no-op, and `null` clears it (`undefined`).\n * - `many`: the picked id is appended once, and held entries stay as they are.\n * An already-present id and `null` are no-ops.\n *\n * A no-op returns `current` itself, so a caller can skip the write.\n */\nexport function applyAssetSelection(\n current: unknown,\n picked: string | null,\n multiple: boolean,\n): unknown {\n if (!multiple) return carryAssetPlacements(current, { multiple: false, id: picked })\n if (picked === null) return current\n return appendAssetSelections(current, [picked])\n}\n\n/**\n * Attach several picked ids to a `many` field at once: an in-picker or drop upload\n * batch (EC-460). Each id not already held is appended once, in arrival order, and\n * held entries stay as they are. Returns `current` itself when nothing is new.\n */\nexport function appendAssetSelections(current: unknown, picked: readonly string[]): unknown {\n const ids = assetIdsOf(current)\n const fresh = [...new Set(picked)].filter((id) => !ids.includes(id))\n if (fresh.length === 0) return current\n return carryAssetPlacements(current, { multiple: true, ids: [...ids, ...fresh] })\n}\n"]}
|
|
@@ -107,9 +107,9 @@ export interface BlockFilter {
|
|
|
107
107
|
*/
|
|
108
108
|
export declare function intersectAllow(slotAllow: readonly string[] | undefined, fieldAllow: readonly string[] | undefined): string[] | undefined;
|
|
109
109
|
/**
|
|
110
|
-
* Build the configured subtree a fixture describes:
|
|
111
|
-
*
|
|
112
|
-
* `slotChildren` component id. Never throws — unknown components, undeclared
|
|
110
|
+
* Build the configured subtree a fixture describes: the fixture's props (EC-591/2f:
|
|
111
|
+
* never the manifest defaults — an unset prop stays absent), plus one bare child
|
|
112
|
+
* node per `slotChildren` component id. Never throws — unknown components, undeclared
|
|
113
113
|
* slots, and invalid prop values become entries in `issues` while the node is
|
|
114
114
|
* still produced (the renderer shows its own visible fallbacks for them).
|
|
115
115
|
*/
|
|
@@ -156,6 +156,8 @@ export declare function hasInsertableComponent(studio: StudioRegistry, slotAllow
|
|
|
156
156
|
/**
|
|
157
157
|
* Layout helper presets (EC-067) as panel tiles — already one tile per preset.
|
|
158
158
|
* A preset has a single implicit variant and never a preview image, so its
|
|
159
|
-
* tile renders the placeholder treatment.
|
|
159
|
+
* tile renders the placeholder treatment. EC-591: the presets the component LIST
|
|
160
|
+
* supports ({@link layoutPresets} — built from the layout and text roles), never
|
|
161
|
+
* hardcoded engine ids.
|
|
160
162
|
*/
|
|
161
|
-
export declare function presetTiles(slotAllow?: string[]): BlockTile[];
|
|
163
|
+
export declare function presetTiles(studio: StudioRegistry, slotAllow?: string[]): BlockTile[];
|
|
@@ -4,7 +4,7 @@ import { componentBlockNode } from '@elytracms/core/rich-text';
|
|
|
4
4
|
import { localizedLabel } from '@elytracms/studio/lib/i18n';
|
|
5
5
|
import { componentStatus } from '@elytracms/studio/lib/registry';
|
|
6
6
|
import { nodeFromProps } from '@elytracms/studio/lib/registry/preview';
|
|
7
|
-
import {
|
|
7
|
+
import { layoutPresets, nodeFromManifest, seededNodeFromManifest } from './node-factory.js';
|
|
8
8
|
/**
|
|
9
9
|
* The implicit variant name of a fixture-less manifest (and the fixture name
|
|
10
10
|
* a {@link GeneratedPreviewLookup} is queried with for it).
|
|
@@ -47,9 +47,9 @@ function manifestIssues(studio, manifest) {
|
|
|
47
47
|
return [];
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Build the configured subtree a fixture describes:
|
|
51
|
-
*
|
|
52
|
-
* `slotChildren` component id. Never throws — unknown components, undeclared
|
|
50
|
+
* Build the configured subtree a fixture describes: the fixture's props (EC-591/2f:
|
|
51
|
+
* never the manifest defaults — an unset prop stays absent), plus one bare child
|
|
52
|
+
* node per `slotChildren` component id. Never throws — unknown components, undeclared
|
|
53
53
|
* slots, and invalid prop values become entries in `issues` while the node is
|
|
54
54
|
* still produced (the renderer shows its own visible fallbacks for them).
|
|
55
55
|
*/
|
|
@@ -125,7 +125,7 @@ export function tileForManifest(studio, manifest, generatedPreview) {
|
|
|
125
125
|
name: DEFAULT_VARIANT,
|
|
126
126
|
issues: baseIssues,
|
|
127
127
|
...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, DEFAULT_VARIANT)),
|
|
128
|
-
build: () => seededNodeFromManifest(manifest),
|
|
128
|
+
build: () => seededNodeFromManifest(manifest, studio.registry),
|
|
129
129
|
},
|
|
130
130
|
];
|
|
131
131
|
const defaultVariant = variants[0];
|
|
@@ -223,15 +223,17 @@ export function hasInsertableComponent(studio, slotAllow) {
|
|
|
223
223
|
continue;
|
|
224
224
|
return true;
|
|
225
225
|
}
|
|
226
|
-
return presetTiles(slotAllow).length > 0;
|
|
226
|
+
return presetTiles(studio, slotAllow).length > 0;
|
|
227
227
|
}
|
|
228
228
|
/**
|
|
229
229
|
* Layout helper presets (EC-067) as panel tiles — already one tile per preset.
|
|
230
230
|
* A preset has a single implicit variant and never a preview image, so its
|
|
231
|
-
* tile renders the placeholder treatment.
|
|
231
|
+
* tile renders the placeholder treatment. EC-591: the presets the component LIST
|
|
232
|
+
* supports ({@link layoutPresets} — built from the layout and text roles), never
|
|
233
|
+
* hardcoded engine ids.
|
|
232
234
|
*/
|
|
233
|
-
export function presetTiles(slotAllow) {
|
|
234
|
-
return
|
|
235
|
+
export function presetTiles(studio, slotAllow) {
|
|
236
|
+
return layoutPresets(studio.registry).filter((preset) => !slotAllow || slotAllow.includes(preset.build().componentId)).map((preset) => ({
|
|
235
237
|
key: `preset::${preset.key}`,
|
|
236
238
|
componentId: preset.build().componentId,
|
|
237
239
|
title: preset.label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks-model.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-model.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { isCanvasEligible, manifestSlots, propValueValidator } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { componentBlockNode } from '@elytracms/core/rich-text'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { componentStatus } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { nodeFromProps } from '@elytracms/studio/lib/registry/preview'\nimport { LAYOUT_PRESETS, nodeFromManifest, seededNodeFromManifest } from './node-factory'\n\n/**\n * Blocks panel model (EC-164, reshaped by EC-439) — the pure, React-free\n * derivation behind the visual insert surface. Every canvas-eligible registry\n * manifest yields **one tile** carrying the component identity plus a\n * `variants` list (one per declared fixture, or a single implicit `Default`\n * variant built from the seeded manifest defaults). Tiles group by manifest\n * `category` and filter by a free-text query over name / description /\n * category / **fixture names** — no hand-maintained panel config. Broken\n * fixtures degrade to variants carrying explicit `issues`, never to a missing\n * entry or a crash.\n *\n * Previews are ARTIFACTS, never runtime renders (EC-439 AD-2). Per tile and\n * per variant, `previewUrl` resolves: curated `manifest.previewImage` (EC-409,\n * always wins) → the {@link GeneratedPreviewLookup} seam (EC-440's build-time\n * fixture renders) → `undefined` (the panel shows an icon+name placeholder).\n */\n\n/**\n * The generated-preview seam (EC-439 AD-2, filled by EC-440): a pure lookup\n * from component id + fixture name to an image URL. The EC-440 CLI renders\n * every canvas-eligible component's fixtures headlessly against the consumer\n * theme and writes a previews manifest; the consumer's `studio.config.tsx`\n * adapts that manifest into this function via `defineStudio({ previews:\n * { generated } })`. A fixture-less manifest's single implicit variant is\n * looked up under the name {@link DEFAULT_VARIANT}. Empty/undefined everywhere\n * until EC-440 lands — the panel then falls back to placeholders.\n */\nexport type GeneratedPreviewLookup = (componentId: string, fixtureName: string) => string | undefined\n\n/**\n * The implicit variant name of a fixture-less manifest (and the fixture name\n * a {@link GeneratedPreviewLookup} is queried with for it).\n */\nexport const DEFAULT_VARIANT = 'Default'\n\n/**\n * One insertable variant of a component tile: a declared manifest fixture, or\n * the single implicit `Default` for a fixture-less manifest. `variants[0]` is\n * the tile's default — what tile click / Enter / drag inserts.\n */\nexport interface BlockVariant {\n /** The fixture name, or {@link DEFAULT_VARIANT} for the implicit variant. */\n name: string\n /**\n * Validation problems for this variant (manifest-level statuses plus the\n * fixture's own issues). A non-empty list marks the variant as degraded —\n * it still lists and still inserts.\n */\n issues: string[]\n /**\n * Resolved preview image URL: curated `manifest.previewImage` (wins) else\n * the generated-preview lookup's URL for this fixture. `undefined` → the\n * panel renders the icon+name placeholder.\n */\n previewUrl?: string\n /** Build the subtree this variant inserts (ids are reassigned on insert). */\n build: () => ComponentNode\n}\n\n/** One component (or layout preset) in the blocks panel — ONE tile per component (EC-439 AD-1). */\nexport interface BlockTile {\n /** Stable identity: the component id, or `preset::<key>` for layout helpers. */\n key: string\n /** Component id of the node the tile inserts. */\n componentId: string\n title: string\n description?: string\n /**\n * The DEFAULT variant's validation problems — what clicking the tile\n * inserts. Per-variant issues live on {@link BlockTile.variants}.\n */\n issues: string[]\n /** The default variant's resolved preview URL (see {@link BlockVariant.previewUrl}). */\n previewUrl?: string\n /** All insertable variants; `variants[0]` is the default the tile inserts. */\n variants: BlockVariant[]\n /**\n * Fixture names the active query matched (stamped by {@link blockGroups}\n * when a query is present) — the panel highlights the variant affordance and\n * the matching flyout rows so \"found by fixture name\" is visible.\n */\n matchedVariants?: string[]\n /** Build the DEFAULT subtree to insert: the first declared fixture's, else the seeded manifest default. */\n build: () => ComponentNode\n}\n\n/** Tiles of one manifest `category`. */\nexport interface BlockGroup {\n category: string\n tiles: BlockTile[]\n}\n\nexport interface BlockFilter {\n /** Case-insensitive match over id, title, description, category, and fixture names. */\n query?: string\n /** When set, only these component ids may be inserted (slot.allow). */\n slotAllow?: string[]\n}\n\n/**\n * Intersect two component-id allow-lists. `undefined` means \"no constraint from\n * this source\", so the result is the other list (copied); two present lists yield\n * their set intersection, preserving the first list's order. This layers a\n * composition field's top-level `allow` vocabulary over a slot's positional\n * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be\n * offered. A present list is a closed set (EC-544): the intersection of two\n * disjoint lists is `[]`, which every reader treats as \"allow nothing\" — there is\n * no coalescing hatch anywhere, and a *declared* `[]` is rejected at the\n * declaration site (config resolve / the `empty-allow` registry issue), so an\n * empty result here is always computed, never authored.\n */\nexport function intersectAllow(\n slotAllow: readonly string[] | undefined,\n fieldAllow: readonly string[] | undefined,\n): string[] | undefined {\n if (!slotAllow) return fieldAllow ? [...fieldAllow] : undefined\n if (!fieldAllow) return [...slotAllow]\n const inField = new Set(fieldAllow)\n return slotAllow.filter((id) => inField.has(id))\n}\n\nconst UNCATEGORIZED = 'other'\n\n/** A node built from a manifest's defaults with an explicit id. */\nfunction defaultNode(manifest: ComponentManifest, id: string): ComponentNode {\n return { ...nodeFromManifest(manifest), id }\n}\n\n/** Structured issues for a component that cannot render (EC-055 statuses). */\nfunction manifestIssues(studio: StudioRegistry, manifest: ComponentManifest): string[] {\n const status = componentStatus(studio, manifest)\n if (status === 'incompatible') {\n return [`No implementation is registered for \"${manifest.id}\".`]\n }\n if (status === 'invalid') {\n return studio.registry.issues.filter((issue) => issue.componentId === manifest.id).map((issue) => issue.message)\n }\n return []\n}\n\n/**\n * Build the configured subtree a fixture describes: manifest defaults overlaid\n * with the fixture's props, plus one default-props child node per\n * `slotChildren` component id. Never throws — unknown components, undeclared\n * slots, and invalid prop values become entries in `issues` while the node is\n * still produced (the renderer shows its own visible fallbacks for them).\n */\nexport function fixtureSubtree(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n fixture: ComponentFixture,\n baseId: string,\n): { node: ComponentNode; issues: string[] } {\n const issues: string[] = []\n const fixtureProps = fixture.props ?? {}\n\n for (const [name, value] of Object.entries(fixtureProps)) {\n const spec = manifest.props[name]\n if (!spec) {\n issues.push(`Fixture \"${fixture.name}\" sets unknown prop \"${name}\".`)\n continue\n }\n // EC-190: validate the fixture value with the prop's field-def validator.\n if (!(propValueValidator(manifest, name)?.(value) ?? true)) {\n issues.push(`Fixture \"${fixture.name}\" sets an invalid value for prop \"${name}\".`)\n }\n }\n\n // EC-547: fixture-built inserts are v2 block-sequence citizens like every\n // other factory node — the root carries the `elytra/component` discriminator.\n const node = componentBlockNode(nodeFromProps(manifest, fixtureProps, baseId))\n\n if (fixture.slotChildren) {\n // EC-386: blocks-prop regions are declared slots too (`manifestSlots`), so a\n // fixture may seed a Section-style body without a false \"undeclared slot\" issue.\n const declared = new Set(manifestSlots(manifest).map((slot) => slot.name))\n const slots: NonNullable<ComponentNode['slots']> = {}\n for (const [slotName, childIds] of Object.entries(fixture.slotChildren)) {\n if (!declared.has(slotName)) {\n issues.push(`Fixture \"${fixture.name}\" fills undeclared slot \"${slotName}\".`)\n }\n slots[slotName] = childIds.map((childId, index) => {\n const id = `${baseId}-${slotName}-${index}`\n const childManifest = studio.registry.getManifest(childId)\n if (!childManifest) {\n issues.push(`Fixture \"${fixture.name}\" references unknown component \"${childId}\" in slot \"${slotName}\".`)\n // Keep the reference: the renderer shows a visible missing-component fallback.\n return componentBlockNode({ id, componentId: childId })\n }\n return defaultNode(childManifest, id)\n })\n }\n if (Object.keys(slots).length > 0) node.slots = slots\n }\n\n return { node, issues }\n}\n\n/**\n * The ONE tile for a manifest (EC-439 AD-1): component identity plus one\n * variant per declared fixture — or a single implicit `Default` variant using\n * the seeded manifest default (a bare layout container is seeded so it\n * survives EC-161 normalization instead of vanishing on commit, EC-383). The\n * tile's own `issues`/`previewUrl`/`build` mirror the default variant\n * (`variants[0]`), so tile click/Enter/drag inserts exactly what the first\n * fixture tile inserted before the reshape.\n */\nexport function tileForManifest(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n generatedPreview?: GeneratedPreviewLookup,\n): BlockTile {\n const title = localizedLabel(manifest.title, manifest.id)\n const baseIssues = manifestIssues(studio, manifest)\n // EC-409: a curated preview image previews the COMPONENT, not one fixture —\n // when declared it wins on the tile and on every variant row (AD-2 tier 1).\n const curated = manifest.previewImage\n\n const variants: BlockVariant[] =\n manifest.fixtures && manifest.fixtures.length > 0\n ? manifest.fixtures.map((fixture, index) => {\n const { issues } = fixtureSubtree(studio, manifest, fixture, `probe-${manifest.id}-${index}`)\n return {\n name: fixture.name,\n issues: [...baseIssues, ...issues],\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, fixture.name)),\n build: () => fixtureSubtree(studio, manifest, fixture, 'tmp').node,\n }\n })\n : [\n {\n name: DEFAULT_VARIANT,\n issues: baseIssues,\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, DEFAULT_VARIANT)),\n build: () => seededNodeFromManifest(manifest),\n },\n ]\n\n const defaultVariant = variants[0]!\n return {\n key: manifest.id,\n componentId: manifest.id,\n title,\n description: manifest.description,\n issues: defaultVariant.issues,\n ...previewUrlEntry(defaultVariant.previewUrl),\n variants,\n build: defaultVariant.build,\n }\n}\n\n/** Spreadable `{ previewUrl }` entry — omitted (not `undefined`-valued) when absent. */\nfunction previewUrlEntry(url: string | undefined): { previewUrl?: string } {\n return url === undefined ? {} : { previewUrl: url }\n}\n\n/**\n * A two-letter monogram from a tile title — the deliberate icon+name\n * placeholder glyph (EC-439 AD-2 tier 3) for tiles with neither a curated nor\n * a generated preview image: first letters of the first two words, else the\n * first two letters of a single word. Deterministic and pure.\n */\nexport function tileMonogram(title: string): string {\n const words = title.trim().split(/\\s+/).filter(Boolean)\n if (words.length === 0) return '?'\n const glyph = words.length === 1 ? words[0]!.slice(0, 2) : `${words[0]!.charAt(0)}${words[1]!.charAt(0)}`\n return glyph.toUpperCase()\n}\n\nfunction matchedFixtureNames(manifest: ComponentManifest, query: string): string[] {\n return (manifest.fixtures ?? [])\n .map((fixture) => fixture.name)\n .filter((name) => name.toLowerCase().includes(query))\n}\n\nfunction matches(manifest: ComponentManifest, query: string): boolean {\n const hay = [manifest.id, ...localizedStringVariants(manifest.title), manifest.description, manifest.category]\n .filter(Boolean)\n .join(' ')\n .toLowerCase()\n // EC-439: fixture names are searchable too — a content-seeded fixture name\n // (\"Startseite\") must find its component even when no manifest field matches.\n return hay.includes(query) || matchedFixtureNames(manifest, query).length > 0\n}\n\n/**\n * Derive the panel's category groups from the registry: filter by slot allow\n * list and query, derive ONE tile per manifest, group by `category`, and sort\n * categories alphabetically (registry order within a group). Deterministic.\n * When a query is active, each tile is stamped with the fixture names it\n * matched (see {@link BlockTile.matchedVariants}).\n */\nexport function blockGroups(\n studio: StudioRegistry,\n filter: BlockFilter = {},\n generatedPreview?: GeneratedPreviewLookup,\n): BlockGroup[] {\n const query = filter.query?.trim().toLowerCase()\n const allow = filter.slotAllow\n\n const groups = new Map<string, BlockTile[]>()\n for (const manifest of studio.registry.list()) {\n // EC-191: the canvas is client-rendered, so only canvas-eligible\n // (client-renderable) components are offered for insertion; server-only / RSC\n // components are usable in dev frames, not placed on the canvas.\n if (!isCanvasEligible(manifest)) continue\n if (allow && !allow.includes(manifest.id)) continue\n if (query && !matches(manifest, query)) continue\n const tile = tileForManifest(studio, manifest, generatedPreview)\n if (query) {\n const matched = matchedFixtureNames(manifest, query)\n if (matched.length > 0) tile.matchedVariants = matched\n }\n const category = manifest.category ?? UNCATEGORIZED\n const tiles = groups.get(category) ?? []\n tiles.push(tile)\n groups.set(category, tiles)\n }\n\n return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, tiles]) => ({ category, tiles }))\n}\n\n/**\n * Whether ANY component could be inserted under a slot allow-list (EC-405) — the\n * cheap existence check behind the canvas insertion affordances: a \"+\" only shows\n * at a position where the blocks panel would offer at least one tile. Mirrors the\n * exact filter {@link blockGroups} applies (canvas-eligible AND, when an allow-list\n * is present, on that list), short-circuiting on the first match instead of\n * building tiles; falls back to the layout-helper presets (which are also\n * insertable there). `undefined`/absent `slotAllow` means \"allow any\" → true when\n * the registry has any canvas-eligible component.\n */\nexport function hasInsertableComponent(studio: StudioRegistry, slotAllow?: string[]): boolean {\n for (const manifest of studio.registry.list()) {\n if (!isCanvasEligible(manifest)) continue\n if (slotAllow && !slotAllow.includes(manifest.id)) continue\n return true\n }\n return presetTiles(slotAllow).length > 0\n}\n\n/**\n * Layout helper presets (EC-067) as panel tiles — already one tile per preset.\n * A preset has a single implicit variant and never a preview image, so its\n * tile renders the placeholder treatment.\n */\nexport function presetTiles(slotAllow?: string[]): BlockTile[] {\n return LAYOUT_PRESETS.filter((preset) => !slotAllow || slotAllow.includes(preset.build().componentId)).map(\n (preset) => ({\n key: `preset::${preset.key}`,\n componentId: preset.build().componentId,\n title: preset.label,\n description: preset.description,\n issues: [],\n variants: [{ name: DEFAULT_VARIANT, issues: [], build: () => preset.build() }],\n build: () => preset.build(),\n }),\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"blocks-model.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-model.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { isCanvasEligible, manifestSlots, propValueValidator } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { componentBlockNode } from '@elytracms/core/rich-text'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { componentStatus } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { nodeFromProps } from '@elytracms/studio/lib/registry/preview'\nimport { layoutPresets, nodeFromManifest, seededNodeFromManifest } from './node-factory'\n\n/**\n * Blocks panel model (EC-164, reshaped by EC-439) — the pure, React-free\n * derivation behind the visual insert surface. Every canvas-eligible registry\n * manifest yields **one tile** carrying the component identity plus a\n * `variants` list (one per declared fixture, or a single implicit `Default`\n * variant built from the seeded manifest defaults). Tiles group by manifest\n * `category` and filter by a free-text query over name / description /\n * category / **fixture names** — no hand-maintained panel config. Broken\n * fixtures degrade to variants carrying explicit `issues`, never to a missing\n * entry or a crash.\n *\n * Previews are ARTIFACTS, never runtime renders (EC-439 AD-2). Per tile and\n * per variant, `previewUrl` resolves: curated `manifest.previewImage` (EC-409,\n * always wins) → the {@link GeneratedPreviewLookup} seam (EC-440's build-time\n * fixture renders) → `undefined` (the panel shows an icon+name placeholder).\n */\n\n/**\n * The generated-preview seam (EC-439 AD-2, filled by EC-440): a pure lookup\n * from component id + fixture name to an image URL. The EC-440 CLI renders\n * every canvas-eligible component's fixtures headlessly against the consumer\n * theme and writes a previews manifest; the consumer's `studio.config.tsx`\n * adapts that manifest into this function via `defineStudio({ previews:\n * { generated } })`. A fixture-less manifest's single implicit variant is\n * looked up under the name {@link DEFAULT_VARIANT}. Empty/undefined everywhere\n * until EC-440 lands — the panel then falls back to placeholders.\n */\nexport type GeneratedPreviewLookup = (componentId: string, fixtureName: string) => string | undefined\n\n/**\n * The implicit variant name of a fixture-less manifest (and the fixture name\n * a {@link GeneratedPreviewLookup} is queried with for it).\n */\nexport const DEFAULT_VARIANT = 'Default'\n\n/**\n * One insertable variant of a component tile: a declared manifest fixture, or\n * the single implicit `Default` for a fixture-less manifest. `variants[0]` is\n * the tile's default — what tile click / Enter / drag inserts.\n */\nexport interface BlockVariant {\n /** The fixture name, or {@link DEFAULT_VARIANT} for the implicit variant. */\n name: string\n /**\n * Validation problems for this variant (manifest-level statuses plus the\n * fixture's own issues). A non-empty list marks the variant as degraded —\n * it still lists and still inserts.\n */\n issues: string[]\n /**\n * Resolved preview image URL: curated `manifest.previewImage` (wins) else\n * the generated-preview lookup's URL for this fixture. `undefined` → the\n * panel renders the icon+name placeholder.\n */\n previewUrl?: string\n /** Build the subtree this variant inserts (ids are reassigned on insert). */\n build: () => ComponentNode\n}\n\n/** One component (or layout preset) in the blocks panel — ONE tile per component (EC-439 AD-1). */\nexport interface BlockTile {\n /** Stable identity: the component id, or `preset::<key>` for layout helpers. */\n key: string\n /** Component id of the node the tile inserts. */\n componentId: string\n title: string\n description?: string\n /**\n * The DEFAULT variant's validation problems — what clicking the tile\n * inserts. Per-variant issues live on {@link BlockTile.variants}.\n */\n issues: string[]\n /** The default variant's resolved preview URL (see {@link BlockVariant.previewUrl}). */\n previewUrl?: string\n /** All insertable variants; `variants[0]` is the default the tile inserts. */\n variants: BlockVariant[]\n /**\n * Fixture names the active query matched (stamped by {@link blockGroups}\n * when a query is present) — the panel highlights the variant affordance and\n * the matching flyout rows so \"found by fixture name\" is visible.\n */\n matchedVariants?: string[]\n /** Build the DEFAULT subtree to insert: the first declared fixture's, else the seeded manifest default. */\n build: () => ComponentNode\n}\n\n/** Tiles of one manifest `category`. */\nexport interface BlockGroup {\n category: string\n tiles: BlockTile[]\n}\n\nexport interface BlockFilter {\n /** Case-insensitive match over id, title, description, category, and fixture names. */\n query?: string\n /** When set, only these component ids may be inserted (slot.allow). */\n slotAllow?: string[]\n}\n\n/**\n * Intersect two component-id allow-lists. `undefined` means \"no constraint from\n * this source\", so the result is the other list (copied); two present lists yield\n * their set intersection, preserving the first list's order. This layers a\n * composition field's top-level `allow` vocabulary over a slot's positional\n * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be\n * offered. A present list is a closed set (EC-544): the intersection of two\n * disjoint lists is `[]`, which every reader treats as \"allow nothing\" — there is\n * no coalescing hatch anywhere, and a *declared* `[]` is rejected at the\n * declaration site (config resolve / the `empty-allow` registry issue), so an\n * empty result here is always computed, never authored.\n */\nexport function intersectAllow(\n slotAllow: readonly string[] | undefined,\n fieldAllow: readonly string[] | undefined,\n): string[] | undefined {\n if (!slotAllow) return fieldAllow ? [...fieldAllow] : undefined\n if (!fieldAllow) return [...slotAllow]\n const inField = new Set(fieldAllow)\n return slotAllow.filter((id) => inField.has(id))\n}\n\nconst UNCATEGORIZED = 'other'\n\n/** A node built from a manifest's defaults with an explicit id. */\nfunction defaultNode(manifest: ComponentManifest, id: string): ComponentNode {\n return { ...nodeFromManifest(manifest), id }\n}\n\n/** Structured issues for a component that cannot render (EC-055 statuses). */\nfunction manifestIssues(studio: StudioRegistry, manifest: ComponentManifest): string[] {\n const status = componentStatus(studio, manifest)\n if (status === 'incompatible') {\n return [`No implementation is registered for \"${manifest.id}\".`]\n }\n if (status === 'invalid') {\n return studio.registry.issues.filter((issue) => issue.componentId === manifest.id).map((issue) => issue.message)\n }\n return []\n}\n\n/**\n * Build the configured subtree a fixture describes: the fixture's props (EC-591/2f:\n * never the manifest defaults — an unset prop stays absent), plus one bare child\n * node per `slotChildren` component id. Never throws — unknown components, undeclared\n * slots, and invalid prop values become entries in `issues` while the node is\n * still produced (the renderer shows its own visible fallbacks for them).\n */\nexport function fixtureSubtree(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n fixture: ComponentFixture,\n baseId: string,\n): { node: ComponentNode; issues: string[] } {\n const issues: string[] = []\n const fixtureProps = fixture.props ?? {}\n\n for (const [name, value] of Object.entries(fixtureProps)) {\n const spec = manifest.props[name]\n if (!spec) {\n issues.push(`Fixture \"${fixture.name}\" sets unknown prop \"${name}\".`)\n continue\n }\n // EC-190: validate the fixture value with the prop's field-def validator.\n if (!(propValueValidator(manifest, name)?.(value) ?? true)) {\n issues.push(`Fixture \"${fixture.name}\" sets an invalid value for prop \"${name}\".`)\n }\n }\n\n // EC-547: fixture-built inserts are v2 block-sequence citizens like every\n // other factory node — the root carries the `elytra/component` discriminator.\n const node = componentBlockNode(nodeFromProps(manifest, fixtureProps, baseId))\n\n if (fixture.slotChildren) {\n // EC-386: blocks-prop regions are declared slots too (`manifestSlots`), so a\n // fixture may seed a Section-style body without a false \"undeclared slot\" issue.\n const declared = new Set(manifestSlots(manifest).map((slot) => slot.name))\n const slots: NonNullable<ComponentNode['slots']> = {}\n for (const [slotName, childIds] of Object.entries(fixture.slotChildren)) {\n if (!declared.has(slotName)) {\n issues.push(`Fixture \"${fixture.name}\" fills undeclared slot \"${slotName}\".`)\n }\n slots[slotName] = childIds.map((childId, index) => {\n const id = `${baseId}-${slotName}-${index}`\n const childManifest = studio.registry.getManifest(childId)\n if (!childManifest) {\n issues.push(`Fixture \"${fixture.name}\" references unknown component \"${childId}\" in slot \"${slotName}\".`)\n // Keep the reference: the renderer shows a visible missing-component fallback.\n return componentBlockNode({ id, componentId: childId })\n }\n return defaultNode(childManifest, id)\n })\n }\n if (Object.keys(slots).length > 0) node.slots = slots\n }\n\n return { node, issues }\n}\n\n/**\n * The ONE tile for a manifest (EC-439 AD-1): component identity plus one\n * variant per declared fixture — or a single implicit `Default` variant using\n * the seeded manifest default (a bare layout container is seeded so it\n * survives EC-161 normalization instead of vanishing on commit, EC-383). The\n * tile's own `issues`/`previewUrl`/`build` mirror the default variant\n * (`variants[0]`), so tile click/Enter/drag inserts exactly what the first\n * fixture tile inserted before the reshape.\n */\nexport function tileForManifest(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n generatedPreview?: GeneratedPreviewLookup,\n): BlockTile {\n const title = localizedLabel(manifest.title, manifest.id)\n const baseIssues = manifestIssues(studio, manifest)\n // EC-409: a curated preview image previews the COMPONENT, not one fixture —\n // when declared it wins on the tile and on every variant row (AD-2 tier 1).\n const curated = manifest.previewImage\n\n const variants: BlockVariant[] =\n manifest.fixtures && manifest.fixtures.length > 0\n ? manifest.fixtures.map((fixture, index) => {\n const { issues } = fixtureSubtree(studio, manifest, fixture, `probe-${manifest.id}-${index}`)\n return {\n name: fixture.name,\n issues: [...baseIssues, ...issues],\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, fixture.name)),\n build: () => fixtureSubtree(studio, manifest, fixture, 'tmp').node,\n }\n })\n : [\n {\n name: DEFAULT_VARIANT,\n issues: baseIssues,\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, DEFAULT_VARIANT)),\n build: () => seededNodeFromManifest(manifest, studio.registry),\n },\n ]\n\n const defaultVariant = variants[0]!\n return {\n key: manifest.id,\n componentId: manifest.id,\n title,\n description: manifest.description,\n issues: defaultVariant.issues,\n ...previewUrlEntry(defaultVariant.previewUrl),\n variants,\n build: defaultVariant.build,\n }\n}\n\n/** Spreadable `{ previewUrl }` entry — omitted (not `undefined`-valued) when absent. */\nfunction previewUrlEntry(url: string | undefined): { previewUrl?: string } {\n return url === undefined ? {} : { previewUrl: url }\n}\n\n/**\n * A two-letter monogram from a tile title — the deliberate icon+name\n * placeholder glyph (EC-439 AD-2 tier 3) for tiles with neither a curated nor\n * a generated preview image: first letters of the first two words, else the\n * first two letters of a single word. Deterministic and pure.\n */\nexport function tileMonogram(title: string): string {\n const words = title.trim().split(/\\s+/).filter(Boolean)\n if (words.length === 0) return '?'\n const glyph = words.length === 1 ? words[0]!.slice(0, 2) : `${words[0]!.charAt(0)}${words[1]!.charAt(0)}`\n return glyph.toUpperCase()\n}\n\nfunction matchedFixtureNames(manifest: ComponentManifest, query: string): string[] {\n return (manifest.fixtures ?? [])\n .map((fixture) => fixture.name)\n .filter((name) => name.toLowerCase().includes(query))\n}\n\nfunction matches(manifest: ComponentManifest, query: string): boolean {\n const hay = [manifest.id, ...localizedStringVariants(manifest.title), manifest.description, manifest.category]\n .filter(Boolean)\n .join(' ')\n .toLowerCase()\n // EC-439: fixture names are searchable too — a content-seeded fixture name\n // (\"Startseite\") must find its component even when no manifest field matches.\n return hay.includes(query) || matchedFixtureNames(manifest, query).length > 0\n}\n\n/**\n * Derive the panel's category groups from the registry: filter by slot allow\n * list and query, derive ONE tile per manifest, group by `category`, and sort\n * categories alphabetically (registry order within a group). Deterministic.\n * When a query is active, each tile is stamped with the fixture names it\n * matched (see {@link BlockTile.matchedVariants}).\n */\nexport function blockGroups(\n studio: StudioRegistry,\n filter: BlockFilter = {},\n generatedPreview?: GeneratedPreviewLookup,\n): BlockGroup[] {\n const query = filter.query?.trim().toLowerCase()\n const allow = filter.slotAllow\n\n const groups = new Map<string, BlockTile[]>()\n for (const manifest of studio.registry.list()) {\n // EC-191: the canvas is client-rendered, so only canvas-eligible\n // (client-renderable) components are offered for insertion; server-only / RSC\n // components are usable in dev frames, not placed on the canvas.\n if (!isCanvasEligible(manifest)) continue\n if (allow && !allow.includes(manifest.id)) continue\n if (query && !matches(manifest, query)) continue\n const tile = tileForManifest(studio, manifest, generatedPreview)\n if (query) {\n const matched = matchedFixtureNames(manifest, query)\n if (matched.length > 0) tile.matchedVariants = matched\n }\n const category = manifest.category ?? UNCATEGORIZED\n const tiles = groups.get(category) ?? []\n tiles.push(tile)\n groups.set(category, tiles)\n }\n\n return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, tiles]) => ({ category, tiles }))\n}\n\n/**\n * Whether ANY component could be inserted under a slot allow-list (EC-405) — the\n * cheap existence check behind the canvas insertion affordances: a \"+\" only shows\n * at a position where the blocks panel would offer at least one tile. Mirrors the\n * exact filter {@link blockGroups} applies (canvas-eligible AND, when an allow-list\n * is present, on that list), short-circuiting on the first match instead of\n * building tiles; falls back to the layout-helper presets (which are also\n * insertable there). `undefined`/absent `slotAllow` means \"allow any\" → true when\n * the registry has any canvas-eligible component.\n */\nexport function hasInsertableComponent(studio: StudioRegistry, slotAllow?: string[]): boolean {\n for (const manifest of studio.registry.list()) {\n if (!isCanvasEligible(manifest)) continue\n if (slotAllow && !slotAllow.includes(manifest.id)) continue\n return true\n }\n return presetTiles(studio, slotAllow).length > 0\n}\n\n/**\n * Layout helper presets (EC-067) as panel tiles — already one tile per preset.\n * A preset has a single implicit variant and never a preview image, so its\n * tile renders the placeholder treatment. EC-591: the presets the component LIST\n * supports ({@link layoutPresets} — built from the layout and text roles), never\n * hardcoded engine ids.\n */\nexport function presetTiles(studio: StudioRegistry, slotAllow?: string[]): BlockTile[] {\n return layoutPresets(studio.registry).filter((preset) => !slotAllow || slotAllow.includes(preset.build().componentId)).map(\n (preset) => ({\n key: `preset::${preset.key}`,\n componentId: preset.build().componentId,\n title: preset.label,\n description: preset.description,\n issues: [],\n variants: [{ name: DEFAULT_VARIANT, issues: [], build: () => preset.build() }],\n build: () => preset.build(),\n }),\n )\n}\n"]}
|
|
@@ -63,7 +63,7 @@ export function BlocksPanel({ context, open, onOpenChange, }) {
|
|
|
63
63
|
const groups = useMemo(() => blockGroups(studio, { query: trimmed || undefined, slotAllow }, generatedPreview), [studio, trimmed, slotAllow, generatedPreview]);
|
|
64
64
|
// Layout helpers keep their dedicated group while browsing (hidden once a
|
|
65
65
|
// query narrows the panel to registry matches), filtered by slot.allow.
|
|
66
|
-
const presets = useMemo(() => (trimmed ? [] : presetTiles(slotAllow)), [trimmed, slotAllow]);
|
|
66
|
+
const presets = useMemo(() => (trimmed ? [] : presetTiles(studio, slotAllow)), [studio, trimmed, slotAllow]);
|
|
67
67
|
// Drag-to-insert (EC-160): a tile is also a drag source. While the drag is
|
|
68
68
|
// live the modal (content + overlay + Radix body pointer lock) gets out of
|
|
69
69
|
// the way so the canvas and the page tree can receive the drop; the session
|