@elytracms/studio 0.0.67 → 0.0.69
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/dist/components/assets/asset-detail.d.ts +4 -2
- package/dist/components/assets/asset-detail.js +4 -2
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +30 -19
- package/dist/components/assets/assets-collection-view-source.js +55 -23
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/builder/canvas-chrome-css.js +6 -1
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/property-inspector.d.ts +0 -21
- package/dist/components/builder/property-inspector.js +5 -22
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/selection-overlay.d.ts +17 -4
- package/dist/components/builder/selection-overlay.js +17 -15
- package/dist/components/builder/selection-overlay.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +26 -1
- package/dist/components/builder/spacing-handles.js +66 -3
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-picker-core.d.ts +52 -0
- package/dist/components/cms/asset-picker-core.js +69 -0
- package/dist/components/cms/asset-picker-core.js.map +1 -0
- package/dist/components/cms/asset-picker.d.ts +16 -4
- package/dist/components/cms/asset-picker.js +34 -50
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-bar.d.ts +11 -1
- package/dist/components/cms/bulk-bar.js +10 -3
- package/dist/components/cms/bulk-bar.js.map +1 -1
- package/dist/components/cms/collection-table.js +19 -2
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +45 -54
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/field-control.js +5 -1
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/prop-fields.js +5 -1
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/prop-visibility.d.ts +3 -0
- package/dist/components/cms/prop-visibility.js +31 -0
- package/dist/components/cms/prop-visibility.js.map +1 -0
- package/dist/components/cms/reference-confirm.js +5 -1
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/rich-text/component-embed.d.ts +8 -6
- package/dist/components/cms/rich-text/component-embed.js +21 -62
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/editor.d.ts +33 -4
- package/dist/components/cms/rich-text/editor.js +52 -22
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.d.ts +60 -27
- package/dist/components/cms/rich-text/embed-props.js +102 -45
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/components/rail-view-source.d.ts +50 -1
- package/dist/components/rail-view-source.js +56 -2
- package/dist/components/rail-view-source.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +3 -2
- package/dist/components/redirects/redirects-collection-view-source.js +4 -3
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/users/users-collection-view-source.js +1 -1
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +12 -0
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/dnd/gap-drag.d.ts +8 -1
- package/dist/lib/dnd/gap-drag.js +4 -0
- package/dist/lib/dnd/gap-drag.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +3 -2
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +1 -1
- package/dist/lib/i18n/messages.en.js +4 -2
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/references/references-store.d.ts +8 -1
- package/dist/lib/references/references-store.js +12 -2
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/ui/expandable-surface.d.ts +35 -25
- package/dist/ui/expandable-surface.js +41 -21
- package/dist/ui/expandable-surface.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/surface-geometry.d.ts +71 -0
- package/dist/ui/surface-geometry.js +64 -0
- package/dist/ui/surface-geometry.js.map +1 -0
- package/package.json +2 -2
- package/src/styles/studio.css +30 -0
- package/dist/components/cms/rich-text/embed-inspector-context.d.ts +0 -50
- package/dist/components/cms/rich-text/embed-inspector-context.js +0 -12
- package/dist/components/cms/rich-text/embed-inspector-context.js.map +0 -1
- package/dist/components/cms/rich-text/embed-inspector.d.ts +0 -25
- package/dist/components/cms/rich-text/embed-inspector.js +0 -48
- package/dist/components/cms/rich-text/embed-inspector.js.map +0 -1
- package/dist/components/cms/use-embed-inspector-host.d.ts +0 -12
- package/dist/components/cms/use-embed-inspector-host.js +0 -17
- package/dist/components/cms/use-embed-inspector-host.js.map +0 -1
|
@@ -37,8 +37,15 @@ export declare function loadReferencePanelData(query: ReferencePanelQuery): Prom
|
|
|
37
37
|
* pass (one index flush, one label-context load) — the pre-flight data of the
|
|
38
38
|
* EC-173 confirmation dialogs, including the bulk flows. Returns entries in
|
|
39
39
|
* `targets` order; pair with `buildReferenceImpact` for break/block semantics.
|
|
40
|
+
*
|
|
41
|
+
* `toTarget` maps each acted-on ref to its reference-index target shape
|
|
42
|
+
* (EC-433): the default is the document shape every `cmsDocuments` collection
|
|
43
|
+
* indexes under; a rail whose records live under a different target type (the
|
|
44
|
+
* assets rail, `{ type: 'asset' }`) supplies its own via
|
|
45
|
+
* `CollectionViewSource.referenceTarget` — otherwise its pre-flight would
|
|
46
|
+
* query the wrong shape and report referenced records as "not referenced".
|
|
40
47
|
*/
|
|
41
|
-
export declare function loadInboundReferenceViews(projectId: string, targets: readonly DocumentRef[]): Promise<{
|
|
48
|
+
export declare function loadInboundReferenceViews(projectId: string, targets: readonly DocumentRef[], toTarget?: (ref: DocumentRef) => ReferenceTarget): Promise<{
|
|
42
49
|
ref: DocumentRef;
|
|
43
50
|
inbound: ReferenceEntryView[];
|
|
44
51
|
}[]>;
|
|
@@ -133,8 +133,18 @@ export async function loadReferencePanelData(query) {
|
|
|
133
133
|
* pass (one index flush, one label-context load) — the pre-flight data of the
|
|
134
134
|
* EC-173 confirmation dialogs, including the bulk flows. Returns entries in
|
|
135
135
|
* `targets` order; pair with `buildReferenceImpact` for break/block semantics.
|
|
136
|
+
*
|
|
137
|
+
* `toTarget` maps each acted-on ref to its reference-index target shape
|
|
138
|
+
* (EC-433): the default is the document shape every `cmsDocuments` collection
|
|
139
|
+
* indexes under; a rail whose records live under a different target type (the
|
|
140
|
+
* assets rail, `{ type: 'asset' }`) supplies its own via
|
|
141
|
+
* `CollectionViewSource.referenceTarget` — otherwise its pre-flight would
|
|
142
|
+
* query the wrong shape and report referenced records as "not referenced".
|
|
136
143
|
*/
|
|
137
|
-
export async function loadInboundReferenceViews(projectId, targets) {
|
|
144
|
+
export async function loadInboundReferenceViews(projectId, targets, toTarget = (ref) => ({
|
|
145
|
+
type: 'document',
|
|
146
|
+
id: documentKey(ref),
|
|
147
|
+
})) {
|
|
138
148
|
const studio = await getStudioOperations();
|
|
139
149
|
await ensureReferenceIndex(studio, projectId);
|
|
140
150
|
// Observe a settled index: maintained-on-write updates run async after events.
|
|
@@ -146,7 +156,7 @@ export async function loadInboundReferenceViews(projectId, targets) {
|
|
|
146
156
|
const recordsPerTarget = await operations.references
|
|
147
157
|
.inboundMany({
|
|
148
158
|
projectId,
|
|
149
|
-
targets: targets.map((ref) => (
|
|
159
|
+
targets: targets.map((ref) => toTarget(ref)),
|
|
150
160
|
})
|
|
151
161
|
.then((result) => {
|
|
152
162
|
if (!result.ok)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"references-store.js","sourceRoot":"","sources":["../../../src/lib/references/references-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { documentKey } from '@elytracms/core/cms-core'\nimport { useOperationQuery } from '@elytracms/studio/lib/query'\nimport type { CmsDocument, DocumentRef } from '@elytracms/core/cms-core'\nimport type {\n ReferenceEntryRecord,\n ReferenceSource,\n ReferenceTarget,\n} from '@elytracms/core/persistence'\nimport { localizedLabel } from '../i18n'\nimport { getStudioOperations } from '../operations'\nimport type { OperationEvent, OperationsClient, StudioOperations } from '../operations'\nimport { getStudioRegistry } from '../registry'\nimport { buildReferenceEntryViews } from './reference-model'\nimport type { ReferenceEntryView, ReferenceLabelContext } from './reference-model'\n\n/**\n * Data layer for the EC-172 references panels. All reads go through\n * `@elytracms/core/operations` queries (`references.outbound` / `references.inbound`\n * plus the labeling queries) — UI code never touches persistence repositories\n * or index tables. The hook keeps panels live: any succeeded command in a\n * content-bearing domain re-queries after the EC-155 maintained-on-write\n * attachment has flushed.\n */\n\nexport interface ReferencePanelQuery {\n projectId: string\n /** Enables the outbound (\"references\") section. */\n source?: ReferenceSource | undefined\n /** Enables the inbound (\"used by\") section. */\n target?: ReferenceTarget | undefined\n}\n\nexport interface ReferencePanelData {\n outbound: ReferenceEntryView[]\n inbound: ReferenceEntryView[]\n /**\n * Per-direction read error (EC-205): a direction whose query returns a\n * structured non-ok degrades to an empty list plus this message, surfaced as a\n * visible per-section error — instead of collapsing the whole panel into its\n * hard-error state. `null` when the direction loaded (or was not requested).\n */\n outboundError: string | null\n inboundError: string | null\n}\n\n// --- Index bootstrap -----------------------------------------------------------\n\n/**\n * The EC-155 index is maintained on write, but seeded/imported projects start\n * with an empty index. {@link ensureReferenceIndex} seeds it once per project per\n * operations client; failures (e.g. a viewer's command denial) degrade to\n * whatever the maintained-on-write index already holds.\n */\nconst backfillsByClient = new WeakMap<StudioOperations, Map<string, Promise<void>>>()\n\n/**\n * Seed the index once if it has never been backfilled, then never again — the\n * EC-269 mutation-free read path. The index is maintained on write, so the\n * (clear + rebuild) `backfill` is only needed to seed a never-built index. A\n * persisted marker (`references.buildMarker`, a read) records completion; when\n * present this returns after a single query with ZERO mutations — so opening the\n * References panel no longer rewrites the whole index every session (the normal\n * Convex case: the EC-233 import already backfilled). Only a genuinely empty/\n * never-built project still pays the one-time backfill (and a viewer who cannot\n * run it degrades to the maintained-on-write index, as before).\n */\nasync function seedReferenceIndex(studio: StudioOperations, projectId: string): Promise<void> {\n try {\n const marker = await studio.operations.references.buildMarker({ projectId })\n if (marker.ok && marker.value !== null) return\n } catch {\n // A marker-read failure falls through to the backfill (best-effort seed).\n }\n await studio.operations.references.backfill({ projectId }).then(\n () => undefined,\n () => undefined,\n )\n}\n\nexport function ensureReferenceIndex(studio: StudioOperations, projectId: string): Promise<void> {\n let byProject = backfillsByClient.get(studio)\n if (!byProject) {\n byProject = new Map()\n backfillsByClient.set(studio, byProject)\n }\n let pending = byProject.get(projectId)\n if (!pending) {\n pending = seedReferenceIndex(studio, projectId)\n byProject.set(projectId, pending)\n }\n return pending\n}\n\n// --- Label context loading -------------------------------------------------------\n\nasync function loadLabelContext(\n operations: OperationsClient,\n projectId: string,\n): Promise<ReferenceLabelContext> {\n const [schemaResult, documentsResult, assetsResult] = await Promise.all([\n operations.schema.get({ projectId }),\n operations.documents.list({ projectId }),\n operations.assets.list({ projectId }),\n ])\n\n const collections = schemaResult.ok ? schemaResult.value.collections : []\n const documents = new Map<string, CmsDocument>()\n if (documentsResult.ok) {\n for (const record of documentsResult.value) {\n documents.set(\n documentKey({ collection: record.document.collection, id: record.document.id }),\n record.document,\n )\n }\n }\n // EC-287: the project graph is retired; reference source labels resolve from\n // CMS documents only. `page`/`layout` graph sources no longer exist.\n const assets = new Map<string, { filename: string; title?: string | undefined }>()\n if (assetsResult.ok) {\n for (const asset of assetsResult.value) {\n assets.set(asset.id, { filename: asset.filename, title: asset.title })\n }\n }\n const registry = getStudioRegistry().registry\n return {\n collections,\n documents,\n assets,\n // EC-407: `title` is a LocalizedString — resolve it to the active UI locale\n // (a plain string is unchanged), never leak a `{ en, de }` object to the label.\n componentTitle: (componentId) => {\n const title = registry.getManifest(componentId)?.title\n return title === undefined ? undefined : localizedLabel(title) || undefined\n },\n }\n}\n\n// --- Panel data loading -----------------------------------------------------------\n\n/** Load both directions + labels for one panel (operations queries only). */\nexport async function loadReferencePanelData(\n query: ReferencePanelQuery,\n): Promise<ReferencePanelData> {\n const studio = await getStudioOperations()\n await ensureReferenceIndex(studio, query.projectId)\n // Observe a settled index: maintained-on-write updates run async after events.\n await studio.references.flush()\n const operations = studio.operations\n\n // EC-205: load each direction INDEPENDENTLY. A structured non-ok from one\n // direction is a degradable operation result (it carries an OperationError —\n // the documented contract), not an exception: degrade that direction to empty\n // and surface its error per-section (references-panel) while the other still\n // renders, instead of throwing the whole panel into its hard-error state. A\n // genuinely broken backend still throws from getStudioOperations/flush above.\n const [outbound, inbound] = await Promise.all([\n query.source\n ? operations.references.outbound({ projectId: query.projectId, source: query.source })\n : null,\n query.target\n ? operations.references.inbound({ projectId: query.projectId, target: query.target })\n : null,\n ])\n\n type DirectionResult = Awaited<ReturnType<typeof operations.references.outbound>>\n const settle = (\n result: DirectionResult | null,\n ): { records: ReferenceEntryRecord[]; error: string | null } =>\n result === null\n ? { records: [], error: null }\n : result.ok\n ? { records: result.value, error: null }\n : { records: [], error: result.error.message }\n const out = settle(outbound)\n const inb = settle(inbound)\n\n // EC-302: nothing referenced in either direction → no sources/targets to label,\n // so skip the label-context load entirely (schema + ALL documents + ALL assets).\n // Common for leaf content; mirrors the asset short-circuit in\n // `loadInboundAssetReferenceViews`.\n if (out.records.length === 0 && inb.records.length === 0) {\n return { outbound: [], inbound: [], outboundError: out.error, inboundError: inb.error }\n }\n\n const context = await loadLabelContext(operations, query.projectId)\n return {\n outbound: buildReferenceEntryViews(out.records, context),\n inbound: buildReferenceEntryViews(inb.records, context),\n outboundError: out.error,\n inboundError: inb.error,\n }\n}\n\n// --- Impact loading (EC-173 safe unpublish/delete) -------------------------------\n\n/**\n * Load the labelled inbound entry views for a set of target documents in ONE\n * pass (one index flush, one label-context load) — the pre-flight data of the\n * EC-173 confirmation dialogs, including the bulk flows. Returns entries in\n * `targets` order; pair with `buildReferenceImpact` for break/block semantics.\n */\nexport async function loadInboundReferenceViews(\n projectId: string,\n targets: readonly DocumentRef[],\n): Promise<{ ref: DocumentRef; inbound: ReferenceEntryView[] }[]> {\n const studio = await getStudioOperations()\n await ensureReferenceIndex(studio, projectId)\n // Observe a settled index: maintained-on-write updates run async after events.\n await studio.references.flush()\n const operations = studio.operations\n\n // EC-308: one batched query for ALL targets (the operation loops the by_target\n // index server-side) instead of a per-target `inbound` fan-out. Returns one\n // record list per target, in `targets` order.\n const recordsPerTarget = await operations.references\n .inboundMany({\n projectId,\n targets: targets.map((ref) => ({ type: 'document', id: documentKey(ref) }) as const),\n })\n .then((result) => {\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n })\n // EC-302: no inbound refs for ANY target → no sources to label, so skip the\n // label-context load (schema + ALL documents + ALL assets). The common case when\n // deleting/unpublishing unreferenced documents (incl. bulk); mirrors the EC-301\n // short-circuit already in `loadInboundAssetReferenceViews`.\n if (recordsPerTarget.every((records) => records.length === 0)) {\n return targets.map((ref) => ({ ref, inbound: [] }))\n }\n const context = await loadLabelContext(operations, projectId)\n return targets.map((ref, index) => ({\n ref,\n inbound: buildReferenceEntryViews(recordsPerTarget[index] ?? [], context),\n }))\n}\n\n/**\n * Inbound reference views for ONE asset (EC-202): the \"what uses this asset\"\n * pre-flight of the asset-delete dialog, index-accurate (the EC-155 reference\n * index, not the retired `find-asset-usages` string scan). Mirrors\n * {@link loadInboundReferenceViews} for a single `{ type: 'asset' }` target —\n * the source labels resolve the referencing documents/graph-nodes identically.\n */\nexport async function loadInboundAssetReferenceViews(\n projectId: string,\n assetId: string,\n): Promise<ReferenceEntryView[]> {\n const studio = await getStudioOperations()\n await ensureReferenceIndex(studio, projectId)\n // Observe a settled index: maintained-on-write updates run async after events.\n await studio.references.flush()\n const operations = studio.operations\n const result = await operations.references.inbound({\n projectId,\n target: { type: 'asset', id: assetId },\n })\n if (!result.ok) throw new Error(result.error.message)\n // No inbound references → no sources to label, so skip the label-context load\n // entirely (schema + ALL documents + ALL assets). This is the common case for a\n // deletable asset, making the delete dialog's pre-flight a single indexed query\n // instead of three full-table lists (EC-301 perf).\n if (result.value.length === 0) return []\n const context = await loadLabelContext(operations, projectId)\n return buildReferenceEntryViews(result.value, context)\n}\n\n// --- React hook ----------------------------------------------------------------\n\n/**\n * Domains whose successful commands can change the reference index or labels and\n * should refresh an open panel. `references` is deliberately ABSENT (EC-269):\n * reacting to reference-index command events (the `backfill`) would make the\n * panel re-trigger on its OWN index maintenance — a self-cascade that, compounded\n * with the per-keystroke `documents` events, produced hundreds of redundant\n * reloads per document. The index is maintained on write; the panel reacts to the\n * CONTENT domains that feed it, never to its own index writes.\n */\nconst REFERENCE_REFRESH_TRIGGERS = [\n 'documents',\n 'graph',\n 'publishing',\n 'assets',\n 'schema',\n] as const\n\nconst REFRESH_DOMAINS: ReadonlySet<string> = new Set(REFERENCE_REFRESH_TRIGGERS)\n\n/**\n * Whether one operation event should refresh an open reference panel. Exported\n * for tests — pins the EC-269 contract that reference-index commands (and any\n * non-succeeded / non-command event) never trigger a reload.\n */\nexport function isReferenceRefreshEvent(event: OperationEvent): boolean {\n return (\n event.kind === 'command' && event.status === 'succeeded' && REFRESH_DOMAINS.has(event.domain)\n )\n}\n\nexport type ReferencePanelStatus = 'loading' | 'ready' | 'error'\n\nexport interface ReferencePanelState {\n status: ReferencePanelStatus\n data: ReferencePanelData | null\n errorMessage: string | null\n refresh: () => void\n}\n\n/**\n * Live panel state for one entity (EC-308/AD-17): loads + caches through the one\n * `useOperationQuery` seam, and re-queries whenever a content-bearing command\n * succeeds anywhere in the studio (the index is maintained on write). Per-direction\n * read errors degrade INSIDE `loadReferencePanelData` (carried on the data); a hard\n * failure (broken backend) surfaces as the query's error → `status: 'error'`.\n */\nexport function useReferencePanel(query: ReferencePanelQuery): ReferencePanelState {\n const { projectId } = query\n const sourceType = query.source?.type\n const sourceId = query.source?.id\n const targetType = query.target?.type\n const targetId = query.target?.id\n\n const stableQuery = useMemo<ReferencePanelQuery>(\n () => ({\n projectId,\n ...(sourceType && sourceId ? { source: { type: sourceType, id: sourceId } } : {}),\n ...(targetType && targetId ? { target: { type: targetType, id: targetId } } : {}),\n }),\n [projectId, sourceType, sourceId, targetType, targetId],\n )\n\n const result = useOperationQuery<ReferencePanelData>({\n queryKey: [\n 'referencePanel',\n projectId,\n sourceType ?? '',\n sourceId ?? '',\n targetType ?? '',\n targetId ?? '',\n ],\n loader: () => loadReferencePanelData(stableQuery),\n invalidateOn: REFERENCE_REFRESH_TRIGGERS,\n })\n\n const status: ReferencePanelStatus = result.isPending\n ? 'loading'\n : result.isError\n ? 'error'\n : 'ready'\n return {\n status,\n data: result.data ?? null,\n errorMessage: result.error instanceof Error ? result.error.message : null,\n refresh: () => void result.refetch(),\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"references-store.js","sourceRoot":"","sources":["../../../src/lib/references/references-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { documentKey } from '@elytracms/core/cms-core'\nimport { useOperationQuery } from '@elytracms/studio/lib/query'\nimport type { CmsDocument, DocumentRef } from '@elytracms/core/cms-core'\nimport type {\n ReferenceEntryRecord,\n ReferenceSource,\n ReferenceTarget,\n} from '@elytracms/core/persistence'\nimport { localizedLabel } from '../i18n'\nimport { getStudioOperations } from '../operations'\nimport type { OperationEvent, OperationsClient, StudioOperations } from '../operations'\nimport { getStudioRegistry } from '../registry'\nimport { buildReferenceEntryViews } from './reference-model'\nimport type { ReferenceEntryView, ReferenceLabelContext } from './reference-model'\n\n/**\n * Data layer for the EC-172 references panels. All reads go through\n * `@elytracms/core/operations` queries (`references.outbound` / `references.inbound`\n * plus the labeling queries) — UI code never touches persistence repositories\n * or index tables. The hook keeps panels live: any succeeded command in a\n * content-bearing domain re-queries after the EC-155 maintained-on-write\n * attachment has flushed.\n */\n\nexport interface ReferencePanelQuery {\n projectId: string\n /** Enables the outbound (\"references\") section. */\n source?: ReferenceSource | undefined\n /** Enables the inbound (\"used by\") section. */\n target?: ReferenceTarget | undefined\n}\n\nexport interface ReferencePanelData {\n outbound: ReferenceEntryView[]\n inbound: ReferenceEntryView[]\n /**\n * Per-direction read error (EC-205): a direction whose query returns a\n * structured non-ok degrades to an empty list plus this message, surfaced as a\n * visible per-section error — instead of collapsing the whole panel into its\n * hard-error state. `null` when the direction loaded (or was not requested).\n */\n outboundError: string | null\n inboundError: string | null\n}\n\n// --- Index bootstrap -----------------------------------------------------------\n\n/**\n * The EC-155 index is maintained on write, but seeded/imported projects start\n * with an empty index. {@link ensureReferenceIndex} seeds it once per project per\n * operations client; failures (e.g. a viewer's command denial) degrade to\n * whatever the maintained-on-write index already holds.\n */\nconst backfillsByClient = new WeakMap<StudioOperations, Map<string, Promise<void>>>()\n\n/**\n * Seed the index once if it has never been backfilled, then never again — the\n * EC-269 mutation-free read path. The index is maintained on write, so the\n * (clear + rebuild) `backfill` is only needed to seed a never-built index. A\n * persisted marker (`references.buildMarker`, a read) records completion; when\n * present this returns after a single query with ZERO mutations — so opening the\n * References panel no longer rewrites the whole index every session (the normal\n * Convex case: the EC-233 import already backfilled). Only a genuinely empty/\n * never-built project still pays the one-time backfill (and a viewer who cannot\n * run it degrades to the maintained-on-write index, as before).\n */\nasync function seedReferenceIndex(studio: StudioOperations, projectId: string): Promise<void> {\n try {\n const marker = await studio.operations.references.buildMarker({ projectId })\n if (marker.ok && marker.value !== null) return\n } catch {\n // A marker-read failure falls through to the backfill (best-effort seed).\n }\n await studio.operations.references.backfill({ projectId }).then(\n () => undefined,\n () => undefined,\n )\n}\n\nexport function ensureReferenceIndex(studio: StudioOperations, projectId: string): Promise<void> {\n let byProject = backfillsByClient.get(studio)\n if (!byProject) {\n byProject = new Map()\n backfillsByClient.set(studio, byProject)\n }\n let pending = byProject.get(projectId)\n if (!pending) {\n pending = seedReferenceIndex(studio, projectId)\n byProject.set(projectId, pending)\n }\n return pending\n}\n\n// --- Label context loading -------------------------------------------------------\n\nasync function loadLabelContext(\n operations: OperationsClient,\n projectId: string,\n): Promise<ReferenceLabelContext> {\n const [schemaResult, documentsResult, assetsResult] = await Promise.all([\n operations.schema.get({ projectId }),\n operations.documents.list({ projectId }),\n operations.assets.list({ projectId }),\n ])\n\n const collections = schemaResult.ok ? schemaResult.value.collections : []\n const documents = new Map<string, CmsDocument>()\n if (documentsResult.ok) {\n for (const record of documentsResult.value) {\n documents.set(\n documentKey({ collection: record.document.collection, id: record.document.id }),\n record.document,\n )\n }\n }\n // EC-287: the project graph is retired; reference source labels resolve from\n // CMS documents only. `page`/`layout` graph sources no longer exist.\n const assets = new Map<string, { filename: string; title?: string | undefined }>()\n if (assetsResult.ok) {\n for (const asset of assetsResult.value) {\n assets.set(asset.id, { filename: asset.filename, title: asset.title })\n }\n }\n const registry = getStudioRegistry().registry\n return {\n collections,\n documents,\n assets,\n // EC-407: `title` is a LocalizedString — resolve it to the active UI locale\n // (a plain string is unchanged), never leak a `{ en, de }` object to the label.\n componentTitle: (componentId) => {\n const title = registry.getManifest(componentId)?.title\n return title === undefined ? undefined : localizedLabel(title) || undefined\n },\n }\n}\n\n// --- Panel data loading -----------------------------------------------------------\n\n/** Load both directions + labels for one panel (operations queries only). */\nexport async function loadReferencePanelData(\n query: ReferencePanelQuery,\n): Promise<ReferencePanelData> {\n const studio = await getStudioOperations()\n await ensureReferenceIndex(studio, query.projectId)\n // Observe a settled index: maintained-on-write updates run async after events.\n await studio.references.flush()\n const operations = studio.operations\n\n // EC-205: load each direction INDEPENDENTLY. A structured non-ok from one\n // direction is a degradable operation result (it carries an OperationError —\n // the documented contract), not an exception: degrade that direction to empty\n // and surface its error per-section (references-panel) while the other still\n // renders, instead of throwing the whole panel into its hard-error state. A\n // genuinely broken backend still throws from getStudioOperations/flush above.\n const [outbound, inbound] = await Promise.all([\n query.source\n ? operations.references.outbound({ projectId: query.projectId, source: query.source })\n : null,\n query.target\n ? operations.references.inbound({ projectId: query.projectId, target: query.target })\n : null,\n ])\n\n type DirectionResult = Awaited<ReturnType<typeof operations.references.outbound>>\n const settle = (\n result: DirectionResult | null,\n ): { records: ReferenceEntryRecord[]; error: string | null } =>\n result === null\n ? { records: [], error: null }\n : result.ok\n ? { records: result.value, error: null }\n : { records: [], error: result.error.message }\n const out = settle(outbound)\n const inb = settle(inbound)\n\n // EC-302: nothing referenced in either direction → no sources/targets to label,\n // so skip the label-context load entirely (schema + ALL documents + ALL assets).\n // Common for leaf content; mirrors the asset short-circuit in\n // `loadInboundAssetReferenceViews`.\n if (out.records.length === 0 && inb.records.length === 0) {\n return { outbound: [], inbound: [], outboundError: out.error, inboundError: inb.error }\n }\n\n const context = await loadLabelContext(operations, query.projectId)\n return {\n outbound: buildReferenceEntryViews(out.records, context),\n inbound: buildReferenceEntryViews(inb.records, context),\n outboundError: out.error,\n inboundError: inb.error,\n }\n}\n\n// --- Impact loading (EC-173 safe unpublish/delete) -------------------------------\n\n/**\n * Load the labelled inbound entry views for a set of target documents in ONE\n * pass (one index flush, one label-context load) — the pre-flight data of the\n * EC-173 confirmation dialogs, including the bulk flows. Returns entries in\n * `targets` order; pair with `buildReferenceImpact` for break/block semantics.\n *\n * `toTarget` maps each acted-on ref to its reference-index target shape\n * (EC-433): the default is the document shape every `cmsDocuments` collection\n * indexes under; a rail whose records live under a different target type (the\n * assets rail, `{ type: 'asset' }`) supplies its own via\n * `CollectionViewSource.referenceTarget` — otherwise its pre-flight would\n * query the wrong shape and report referenced records as \"not referenced\".\n */\nexport async function loadInboundReferenceViews(\n projectId: string,\n targets: readonly DocumentRef[],\n toTarget: (ref: DocumentRef) => ReferenceTarget = (ref) => ({\n type: 'document',\n id: documentKey(ref),\n }),\n): Promise<{ ref: DocumentRef; inbound: ReferenceEntryView[] }[]> {\n const studio = await getStudioOperations()\n await ensureReferenceIndex(studio, projectId)\n // Observe a settled index: maintained-on-write updates run async after events.\n await studio.references.flush()\n const operations = studio.operations\n\n // EC-308: one batched query for ALL targets (the operation loops the by_target\n // index server-side) instead of a per-target `inbound` fan-out. Returns one\n // record list per target, in `targets` order.\n const recordsPerTarget = await operations.references\n .inboundMany({\n projectId,\n targets: targets.map((ref) => toTarget(ref)),\n })\n .then((result) => {\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n })\n // EC-302: no inbound refs for ANY target → no sources to label, so skip the\n // label-context load (schema + ALL documents + ALL assets). The common case when\n // deleting/unpublishing unreferenced documents (incl. bulk); mirrors the EC-301\n // short-circuit already in `loadInboundAssetReferenceViews`.\n if (recordsPerTarget.every((records) => records.length === 0)) {\n return targets.map((ref) => ({ ref, inbound: [] }))\n }\n const context = await loadLabelContext(operations, projectId)\n return targets.map((ref, index) => ({\n ref,\n inbound: buildReferenceEntryViews(recordsPerTarget[index] ?? [], context),\n }))\n}\n\n/**\n * Inbound reference views for ONE asset (EC-202): the \"what uses this asset\"\n * pre-flight of the asset-delete dialog, index-accurate (the EC-155 reference\n * index, not the retired `find-asset-usages` string scan). Mirrors\n * {@link loadInboundReferenceViews} for a single `{ type: 'asset' }` target —\n * the source labels resolve the referencing documents/graph-nodes identically.\n */\nexport async function loadInboundAssetReferenceViews(\n projectId: string,\n assetId: string,\n): Promise<ReferenceEntryView[]> {\n const studio = await getStudioOperations()\n await ensureReferenceIndex(studio, projectId)\n // Observe a settled index: maintained-on-write updates run async after events.\n await studio.references.flush()\n const operations = studio.operations\n const result = await operations.references.inbound({\n projectId,\n target: { type: 'asset', id: assetId },\n })\n if (!result.ok) throw new Error(result.error.message)\n // No inbound references → no sources to label, so skip the label-context load\n // entirely (schema + ALL documents + ALL assets). This is the common case for a\n // deletable asset, making the delete dialog's pre-flight a single indexed query\n // instead of three full-table lists (EC-301 perf).\n if (result.value.length === 0) return []\n const context = await loadLabelContext(operations, projectId)\n return buildReferenceEntryViews(result.value, context)\n}\n\n// --- React hook ----------------------------------------------------------------\n\n/**\n * Domains whose successful commands can change the reference index or labels and\n * should refresh an open panel. `references` is deliberately ABSENT (EC-269):\n * reacting to reference-index command events (the `backfill`) would make the\n * panel re-trigger on its OWN index maintenance — a self-cascade that, compounded\n * with the per-keystroke `documents` events, produced hundreds of redundant\n * reloads per document. The index is maintained on write; the panel reacts to the\n * CONTENT domains that feed it, never to its own index writes.\n */\nconst REFERENCE_REFRESH_TRIGGERS = [\n 'documents',\n 'graph',\n 'publishing',\n 'assets',\n 'schema',\n] as const\n\nconst REFRESH_DOMAINS: ReadonlySet<string> = new Set(REFERENCE_REFRESH_TRIGGERS)\n\n/**\n * Whether one operation event should refresh an open reference panel. Exported\n * for tests — pins the EC-269 contract that reference-index commands (and any\n * non-succeeded / non-command event) never trigger a reload.\n */\nexport function isReferenceRefreshEvent(event: OperationEvent): boolean {\n return (\n event.kind === 'command' && event.status === 'succeeded' && REFRESH_DOMAINS.has(event.domain)\n )\n}\n\nexport type ReferencePanelStatus = 'loading' | 'ready' | 'error'\n\nexport interface ReferencePanelState {\n status: ReferencePanelStatus\n data: ReferencePanelData | null\n errorMessage: string | null\n refresh: () => void\n}\n\n/**\n * Live panel state for one entity (EC-308/AD-17): loads + caches through the one\n * `useOperationQuery` seam, and re-queries whenever a content-bearing command\n * succeeds anywhere in the studio (the index is maintained on write). Per-direction\n * read errors degrade INSIDE `loadReferencePanelData` (carried on the data); a hard\n * failure (broken backend) surfaces as the query's error → `status: 'error'`.\n */\nexport function useReferencePanel(query: ReferencePanelQuery): ReferencePanelState {\n const { projectId } = query\n const sourceType = query.source?.type\n const sourceId = query.source?.id\n const targetType = query.target?.type\n const targetId = query.target?.id\n\n const stableQuery = useMemo<ReferencePanelQuery>(\n () => ({\n projectId,\n ...(sourceType && sourceId ? { source: { type: sourceType, id: sourceId } } : {}),\n ...(targetType && targetId ? { target: { type: targetType, id: targetId } } : {}),\n }),\n [projectId, sourceType, sourceId, targetType, targetId],\n )\n\n const result = useOperationQuery<ReferencePanelData>({\n queryKey: [\n 'referencePanel',\n projectId,\n sourceType ?? '',\n sourceId ?? '',\n targetType ?? '',\n targetId ?? '',\n ],\n loader: () => loadReferencePanelData(stableQuery),\n invalidateOn: REFERENCE_REFRESH_TRIGGERS,\n })\n\n const status: ReferencePanelStatus = result.isPending\n ? 'loading'\n : result.isError\n ? 'error'\n : 'ready'\n return {\n status,\n data: result.data ?? null,\n errorMessage: result.error instanceof Error ? result.error.message : null,\n refresh: () => void result.refetch(),\n }\n}\n"]}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import type { SurfaceGeometryProps } from './surface-geometry.js';
|
|
3
|
+
export type ExpandableSurfaceProps = {
|
|
4
|
+
/** Heading text; doubles as the surface's `aria-label`. */
|
|
4
5
|
title: string;
|
|
5
6
|
/** Accessible label + tooltip for the header close button (caller-translated). */
|
|
6
7
|
closeLabel: string;
|
|
7
|
-
/**
|
|
8
|
-
* CSS width of the sheet panel — callers keep their shipped widths
|
|
9
|
-
* (e.g. `min(820px, 92vw)` rich text, `min(720px, 92vw)` proposal review).
|
|
10
|
-
*/
|
|
11
|
-
width: string;
|
|
12
8
|
/**
|
|
13
9
|
* Dismiss request: Escape (only while this surface is the topmost layer),
|
|
14
10
|
* backdrop click, or the header close button. The caller owns the open state.
|
|
@@ -18,31 +14,45 @@ export interface ExpandableSurfaceProps {
|
|
|
18
14
|
headerLeading?: ReactNode;
|
|
19
15
|
/** Everything below the header. Callers own their scroll areas and footers. */
|
|
20
16
|
children: ReactNode;
|
|
21
|
-
}
|
|
17
|
+
} & SurfaceGeometryProps;
|
|
22
18
|
/**
|
|
23
|
-
* ExpandableSurface (EC-392) — the tier-2 answer to
|
|
24
|
-
* its slot": the SAME surface grown into a roomy
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
19
|
+
* ExpandableSurface (EC-392, geometry variants EC-431) — the tier-2 answer to
|
|
20
|
+
* "an inline surface outgrows its slot": the SAME surface grown into a roomy
|
|
21
|
+
* takeover. It is never a second form — no save/cancel semantics of its own
|
|
22
|
+
* (forms-never-modal) — and stays lightly dismissible: Escape, backdrop click,
|
|
23
|
+
* header close.
|
|
24
|
+
*
|
|
25
|
+
* Two geometries, one mechanic set (ratified AD-A, 2026-07-16):
|
|
26
|
+
*
|
|
27
|
+
* - `variant="stage"` — CENTERED, near-full height, width from a per-surface
|
|
28
|
+
* preset (`prose` rich-text expand EC-431 / `grid` media browser EC-432 /
|
|
29
|
+
* `form` embed inspector EC-430). Primary-activity surfaces live here; the
|
|
30
|
+
* entrance is a subtle motion-safe scale/opacity grow from center, never an
|
|
31
|
+
* edge slide.
|
|
32
|
+
* - `variant="side"` (default) — the right-anchored sheet, reserved for
|
|
33
|
+
* genuinely peripheral review next to a primary activity; today exactly one
|
|
34
|
+
* caller: the AI proposal review card. Shipped shape unchanged, no motion.
|
|
29
35
|
*
|
|
30
36
|
* Rendered through a `createPortal` to `document.body` so it escapes narrow
|
|
31
37
|
* inspectors/sidebars and any transformed ancestor; without a DOM (the
|
|
32
38
|
* `renderToStaticMarkup` smoke path) it renders `null`, so callers keep their
|
|
33
|
-
*
|
|
39
|
+
* surfaces closed by default on SSR paths.
|
|
34
40
|
*
|
|
35
41
|
* Stacking (ratified semantics): all instances register on the shared layer
|
|
36
42
|
* stack; only the TOPMOST layer acts on Escape (backdrop ownership falls out of
|
|
37
|
-
* paint order — the topmost
|
|
38
|
-
* above (the immediate case: the asset
|
|
39
|
-
* expanded rich-text toolbar) are bridged
|
|
40
|
-
* Escape on the document capture phase and
|
|
41
|
-
* this surface treats as "handled above me" —
|
|
42
|
-
* and the
|
|
43
|
+
* paint order — the topmost full-viewport container covers everything below it,
|
|
44
|
+
* for both geometries). Foreign layers above (the immediate case: the asset
|
|
45
|
+
* picker's Radix dialog opened from the expanded rich-text toolbar) are bridged
|
|
46
|
+
* via the event itself: Radix consumes Escape on the document capture phase and
|
|
47
|
+
* marks it `defaultPrevented`, which this surface treats as "handled above me" —
|
|
48
|
+
* so Escape closes the picker first and the surface only on the next press. A
|
|
49
|
+
* second ExpandableSurface above a stage rides the same stack (later mount →
|
|
50
|
+
* later in DOM → paints above; `isTop` hands it Escape) — the EC-432
|
|
51
|
+
* stage-above-stage case needs no new mechanics.
|
|
43
52
|
*
|
|
44
|
-
* Motion: the
|
|
45
|
-
*
|
|
46
|
-
*
|
|
53
|
+
* Motion: only the stage variant has an entrance transition, and it is entirely
|
|
54
|
+
* `motion-safe:`-gated (see `surface-geometry.ts`) — under
|
|
55
|
+
* `prefers-reduced-motion` the stage appears instantly. The side sheet keeps
|
|
56
|
+
* its shipped no-transition behavior.
|
|
47
57
|
*/
|
|
48
|
-
export declare function ExpandableSurface(
|
|
58
|
+
export declare function ExpandableSurface(props: ExpandableSurfaceProps): import("react").ReactPortal | null;
|
|
@@ -6,41 +6,61 @@ import { Button } from './button.js';
|
|
|
6
6
|
import { Heading } from './typography.js';
|
|
7
7
|
import { Row, Spacer } from './layout.js';
|
|
8
8
|
import { createLayerStack } from './layer-stack.js';
|
|
9
|
+
import { surfaceGeometry } from './surface-geometry.js';
|
|
9
10
|
/**
|
|
10
11
|
* The ONE shared open-layer stack all {@link ExpandableSurface} instances
|
|
11
12
|
* coordinate through. The surfaces themselves are instance-per-caller (ratified:
|
|
12
|
-
* no singleton/global
|
|
13
|
-
* legal); only the *ordering* is shared, so that the topmost open
|
|
14
|
-
* Escape while the ones below stay open.
|
|
13
|
+
* no singleton/global surface slot — concurrent surfaces from different features
|
|
14
|
+
* are legal); only the *ordering* is shared, so that the topmost open surface
|
|
15
|
+
* owns Escape while the ones below stay open.
|
|
15
16
|
*/
|
|
16
17
|
const surfaceLayers = createLayerStack();
|
|
17
18
|
/**
|
|
18
|
-
* ExpandableSurface (EC-392) — the tier-2 answer to
|
|
19
|
-
* its slot": the SAME surface grown into a roomy
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* ExpandableSurface (EC-392, geometry variants EC-431) — the tier-2 answer to
|
|
20
|
+
* "an inline surface outgrows its slot": the SAME surface grown into a roomy
|
|
21
|
+
* takeover. It is never a second form — no save/cancel semantics of its own
|
|
22
|
+
* (forms-never-modal) — and stays lightly dismissible: Escape, backdrop click,
|
|
23
|
+
* header close.
|
|
24
|
+
*
|
|
25
|
+
* Two geometries, one mechanic set (ratified AD-A, 2026-07-16):
|
|
26
|
+
*
|
|
27
|
+
* - `variant="stage"` — CENTERED, near-full height, width from a per-surface
|
|
28
|
+
* preset (`prose` rich-text expand EC-431 / `grid` media browser EC-432 /
|
|
29
|
+
* `form` embed inspector EC-430). Primary-activity surfaces live here; the
|
|
30
|
+
* entrance is a subtle motion-safe scale/opacity grow from center, never an
|
|
31
|
+
* edge slide.
|
|
32
|
+
* - `variant="side"` (default) — the right-anchored sheet, reserved for
|
|
33
|
+
* genuinely peripheral review next to a primary activity; today exactly one
|
|
34
|
+
* caller: the AI proposal review card. Shipped shape unchanged, no motion.
|
|
24
35
|
*
|
|
25
36
|
* Rendered through a `createPortal` to `document.body` so it escapes narrow
|
|
26
37
|
* inspectors/sidebars and any transformed ancestor; without a DOM (the
|
|
27
38
|
* `renderToStaticMarkup` smoke path) it renders `null`, so callers keep their
|
|
28
|
-
*
|
|
39
|
+
* surfaces closed by default on SSR paths.
|
|
29
40
|
*
|
|
30
41
|
* Stacking (ratified semantics): all instances register on the shared layer
|
|
31
42
|
* stack; only the TOPMOST layer acts on Escape (backdrop ownership falls out of
|
|
32
|
-
* paint order — the topmost
|
|
33
|
-
* above (the immediate case: the asset
|
|
34
|
-
* expanded rich-text toolbar) are bridged
|
|
35
|
-
* Escape on the document capture phase and
|
|
36
|
-
* this surface treats as "handled above me" —
|
|
37
|
-
* and the
|
|
43
|
+
* paint order — the topmost full-viewport container covers everything below it,
|
|
44
|
+
* for both geometries). Foreign layers above (the immediate case: the asset
|
|
45
|
+
* picker's Radix dialog opened from the expanded rich-text toolbar) are bridged
|
|
46
|
+
* via the event itself: Radix consumes Escape on the document capture phase and
|
|
47
|
+
* marks it `defaultPrevented`, which this surface treats as "handled above me" —
|
|
48
|
+
* so Escape closes the picker first and the surface only on the next press. A
|
|
49
|
+
* second ExpandableSurface above a stage rides the same stack (later mount →
|
|
50
|
+
* later in DOM → paints above; `isTop` hands it Escape) — the EC-432
|
|
51
|
+
* stage-above-stage case needs no new mechanics.
|
|
38
52
|
*
|
|
39
|
-
* Motion: the
|
|
40
|
-
*
|
|
41
|
-
*
|
|
53
|
+
* Motion: only the stage variant has an entrance transition, and it is entirely
|
|
54
|
+
* `motion-safe:`-gated (see `surface-geometry.ts`) — under
|
|
55
|
+
* `prefers-reduced-motion` the stage appears instantly. The side sheet keeps
|
|
56
|
+
* its shipped no-transition behavior.
|
|
42
57
|
*/
|
|
43
|
-
export function ExpandableSurface(
|
|
58
|
+
export function ExpandableSurface(props) {
|
|
59
|
+
const { title, closeLabel, onDismiss, headerLeading, children } = props;
|
|
60
|
+
const geometry = surfaceGeometry(props);
|
|
61
|
+
// The stage IS the primary activity while open — a <section>; the side sheet
|
|
62
|
+
// is complementary content next to it — an <aside>.
|
|
63
|
+
const Panel = props.variant === 'stage' ? 'section' : 'aside';
|
|
44
64
|
// Latest dismiss handler, read inside the once-per-mount listener.
|
|
45
65
|
const onDismissRef = useRef(onDismiss);
|
|
46
66
|
onDismissRef.current = onDismiss;
|
|
@@ -67,6 +87,6 @@ export function ExpandableSurface({ title, closeLabel, width, onDismiss, headerL
|
|
|
67
87
|
}, []);
|
|
68
88
|
if (typeof document === 'undefined')
|
|
69
89
|
return null;
|
|
70
|
-
return createPortal(_jsxs("div", { className:
|
|
90
|
+
return createPortal(_jsxs("div", { className: geometry.container, children: [_jsx("div", { className: geometry.backdrop, "aria-hidden": true, onClick: onDismiss }), _jsxs(Panel, { role: "dialog", "aria-label": title, style: geometry.panelStyle, className: geometry.panel, children: [_jsxs(Row, { gap: "2", align: "center", className: "h-12 shrink-0 border-b border-border px-4", children: [headerLeading, _jsx(Heading, { level: 2, size: "h4", className: "min-w-0 truncate", children: title }), _jsx(Spacer, {}), _jsx(Button, { size: "icon-sm", variant: "ghost", onClick: onDismiss, title: closeLabel, "aria-label": closeLabel, children: _jsx(X, {}) })] }), children] })] }), document.body);
|
|
71
91
|
}
|
|
72
92
|
//# sourceMappingURL=expandable-surface.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expandable-surface.js","sourceRoot":"","sources":["../../src/ui/expandable-surface.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"expandable-surface.js","sourceRoot":"","sources":["../../src/ui/expandable-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { X } from 'lucide-react'\nimport { Button } from './button'\nimport { Heading } from './typography'\nimport { Row, Spacer } from './layout'\nimport { createLayerStack } from './layer-stack'\nimport { surfaceGeometry } from './surface-geometry'\nimport type { SurfaceGeometryProps } from './surface-geometry'\n\n/**\n * The ONE shared open-layer stack all {@link ExpandableSurface} instances\n * coordinate through. The surfaces themselves are instance-per-caller (ratified:\n * no singleton/global surface slot — concurrent surfaces from different features\n * are legal); only the *ordering* is shared, so that the topmost open surface\n * owns Escape while the ones below stay open.\n */\nconst surfaceLayers = createLayerStack()\n\nexport type ExpandableSurfaceProps = {\n /** Heading text; doubles as the surface's `aria-label`. */\n title: string\n /** Accessible label + tooltip for the header close button (caller-translated). */\n closeLabel: string\n /**\n * Dismiss request: Escape (only while this surface is the topmost layer),\n * backdrop click, or the header close button. The caller owns the open state.\n */\n onDismiss: () => void\n /** Optional leading header adornment (an icon) rendered before the title. */\n headerLeading?: ReactNode\n /** Everything below the header. Callers own their scroll areas and footers. */\n children: ReactNode\n} & SurfaceGeometryProps\n\n/**\n * ExpandableSurface (EC-392, geometry variants EC-431) — the tier-2 answer to\n * \"an inline surface outgrows its slot\": the SAME surface grown into a roomy\n * takeover. It is never a second form — no save/cancel semantics of its own\n * (forms-never-modal) — and stays lightly dismissible: Escape, backdrop click,\n * header close.\n *\n * Two geometries, one mechanic set (ratified AD-A, 2026-07-16):\n *\n * - `variant=\"stage\"` — CENTERED, near-full height, width from a per-surface\n * preset (`prose` rich-text expand EC-431 / `grid` media browser EC-432 /\n * `form` embed inspector EC-430). Primary-activity surfaces live here; the\n * entrance is a subtle motion-safe scale/opacity grow from center, never an\n * edge slide.\n * - `variant=\"side\"` (default) — the right-anchored sheet, reserved for\n * genuinely peripheral review next to a primary activity; today exactly one\n * caller: the AI proposal review card. Shipped shape unchanged, no motion.\n *\n * Rendered through a `createPortal` to `document.body` so it escapes narrow\n * inspectors/sidebars and any transformed ancestor; without a DOM (the\n * `renderToStaticMarkup` smoke path) it renders `null`, so callers keep their\n * surfaces closed by default on SSR paths.\n *\n * Stacking (ratified semantics): all instances register on the shared layer\n * stack; only the TOPMOST layer acts on Escape (backdrop ownership falls out of\n * paint order — the topmost full-viewport container covers everything below it,\n * for both geometries). Foreign layers above (the immediate case: the asset\n * picker's Radix dialog opened from the expanded rich-text toolbar) are bridged\n * via the event itself: Radix consumes Escape on the document capture phase and\n * marks it `defaultPrevented`, which this surface treats as \"handled above me\" —\n * so Escape closes the picker first and the surface only on the next press. A\n * second ExpandableSurface above a stage rides the same stack (later mount →\n * later in DOM → paints above; `isTop` hands it Escape) — the EC-432\n * stage-above-stage case needs no new mechanics.\n *\n * Motion: only the stage variant has an entrance transition, and it is entirely\n * `motion-safe:`-gated (see `surface-geometry.ts`) — under\n * `prefers-reduced-motion` the stage appears instantly. The side sheet keeps\n * its shipped no-transition behavior.\n */\nexport function ExpandableSurface(props: ExpandableSurfaceProps) {\n const { title, closeLabel, onDismiss, headerLeading, children } = props\n const geometry = surfaceGeometry(props)\n // The stage IS the primary activity while open — a <section>; the side sheet\n // is complementary content next to it — an <aside>.\n const Panel = props.variant === 'stage' ? 'section' : 'aside'\n\n // Latest dismiss handler, read inside the once-per-mount listener.\n const onDismissRef = useRef(onDismiss)\n onDismissRef.current = onDismiss\n\n useEffect(() => {\n const layer = surfaceLayers.push()\n const onKey = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n // A layer above already consumed this Escape — a Radix dialog (asset\n // picker) preventDefaults on dismiss, and a sibling surface that handled\n // it marks it the same way. Layers below stay open.\n if (event.defaultPrevented) return\n if (!surfaceLayers.isTop(layer)) return\n event.preventDefault()\n onDismissRef.current()\n }\n window.addEventListener('keydown', onKey)\n return () => {\n window.removeEventListener('keydown', onKey)\n surfaceLayers.remove(layer)\n }\n }, [])\n\n if (typeof document === 'undefined') return null\n\n return createPortal(\n <div className={geometry.container}>\n <div className={geometry.backdrop} aria-hidden onClick={onDismiss} />\n <Panel\n role=\"dialog\"\n aria-label={title}\n style={geometry.panelStyle}\n className={geometry.panel}\n >\n <Row gap=\"2\" align=\"center\" className=\"h-12 shrink-0 border-b border-border px-4\">\n {headerLeading}\n <Heading level={2} size=\"h4\" className=\"min-w-0 truncate\">\n {title}\n </Heading>\n <Spacer />\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={onDismiss}\n title={closeLabel}\n aria-label={closeLabel}\n >\n <X />\n </Button>\n </Row>\n {children}\n </Panel>\n </div>,\n document.body,\n )\n}\n"]}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './separator.js';
|
|
|
12
12
|
export * from './toolbar.js';
|
|
13
13
|
export * from './inspector.js';
|
|
14
14
|
export * from './expandable-surface.js';
|
|
15
|
+
export * from './surface-geometry.js';
|
|
15
16
|
export * from './layer-stack.js';
|
|
16
17
|
export * from './dialog.js';
|
|
17
18
|
export * from './dropdown-menu.js';
|
package/dist/ui/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './separator.js';
|
|
|
12
12
|
export * from './toolbar.js';
|
|
13
13
|
export * from './inspector.js';
|
|
14
14
|
export * from './expandable-surface.js';
|
|
15
|
+
export * from './surface-geometry.js';
|
|
15
16
|
export * from './layer-stack.js';
|
|
16
17
|
export * from './dialog.js';
|
|
17
18
|
export * from './dropdown-menu.js';
|
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAC1B,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,qBAAqB,CAAA","sourcesContent":["export * from './typography'\nexport * from './layout'\nexport * from './avatar'\nexport * from './button'\nexport * from './input'\nexport * from './badge'\nexport * from './card'\nexport * from './panel'\nexport * from './row-item'\nexport * from './collapsible-section'\nexport * from './separator'\nexport * from './toolbar'\nexport * from './inspector'\nexport * from './expandable-surface'\nexport * from './layer-stack'\nexport * from './dialog'\nexport * from './dropdown-menu'\nexport * from './popover'\nexport * from './field'\nexport * from './toast'\nexport * from './segmented-control'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAC1B,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,qBAAqB,CAAA","sourcesContent":["export * from './typography'\nexport * from './layout'\nexport * from './avatar'\nexport * from './button'\nexport * from './input'\nexport * from './badge'\nexport * from './card'\nexport * from './panel'\nexport * from './row-item'\nexport * from './collapsible-section'\nexport * from './separator'\nexport * from './toolbar'\nexport * from './inspector'\nexport * from './expandable-surface'\nexport * from './surface-geometry'\nexport * from './layer-stack'\nexport * from './dialog'\nexport * from './dropdown-menu'\nexport * from './popover'\nexport * from './field'\nexport * from './toast'\nexport * from './segmented-control'\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* EC-431 (AD-A) — the pure geometry core for {@link ExpandableSurface}: which
|
|
4
|
+
* classes/width a surface mounts with, per variant. Extracted (the EC-391
|
|
5
|
+
* class-contract pattern) so the geometry is pinnable in the node test env,
|
|
6
|
+
* where the portal itself renders `null`.
|
|
7
|
+
*
|
|
8
|
+
* Two geometries, one mechanic set (portal-to-body, backdrop, header close,
|
|
9
|
+
* layer-stacked Escape):
|
|
10
|
+
*
|
|
11
|
+
* - `side` — the right-anchored sheet. Reserved for genuinely PERIPHERAL review
|
|
12
|
+
* surfaces (a side panel semantically means "detail next to the primary
|
|
13
|
+
* activity"); today exactly one: the AI proposal review card. Byte-for-byte
|
|
14
|
+
* the EC-392 shipped shape — no visual change for that caller.
|
|
15
|
+
* - `stage` — the centered takeover for PRIMARY-ACTIVITY surfaces (ratified
|
|
16
|
+
* AD-A, 2026-07-16): centered, near-full height, growing from center with a
|
|
17
|
+
* subtle scale/opacity entrance — never an edge slide. Width comes from a
|
|
18
|
+
* per-surface preset ({@link STAGE_WIDTHS}).
|
|
19
|
+
*
|
|
20
|
+
* Motion: only the stage has an entrance transition, and every animated class
|
|
21
|
+
* is `motion-safe:`-gated — under `prefers-reduced-motion` the stage appears
|
|
22
|
+
* instantly (pinned by `surface-geometry.test.ts`). The side sheet keeps its
|
|
23
|
+
* shipped no-transition behavior.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The stage width presets — one per surface family (ratified AD-A). Consumers
|
|
27
|
+
* pick a preset, never a raw width, so stage surfaces stay mutually consistent:
|
|
28
|
+
*
|
|
29
|
+
* - `prose` — distraction-free writing (the rich-text expand, EC-431/AD-B).
|
|
30
|
+
* EC-431 ratified `min(920px, 92vw)`; owner feedback on 0.0.68 (EC-436,
|
|
31
|
+
* 2026-07-16 — "the dialog + text looks a bit narrow on a big screen")
|
|
32
|
+
* SUPERSEDED that number: the panel widened to `min(1160px, 94vw)` and the
|
|
33
|
+
* caller's CONTENT column grew in step (~78ch,
|
|
34
|
+
* `RICH_TEXT_STAGE_CONTENT_CLASS`). The column stays constrained — the stage
|
|
35
|
+
* being wider than the text is the point, not a defect.
|
|
36
|
+
* - `grid` — large centered browsing grids (the EC-432 media browser).
|
|
37
|
+
* - `form` — a single-column form measure (the EC-430 embed inspector).
|
|
38
|
+
*/
|
|
39
|
+
export declare const STAGE_WIDTHS: {
|
|
40
|
+
readonly prose: "min(1160px, 94vw)";
|
|
41
|
+
readonly grid: "min(1240px, 94vw)";
|
|
42
|
+
readonly form: "min(720px, 92vw)";
|
|
43
|
+
};
|
|
44
|
+
export type StageWidthPreset = keyof typeof STAGE_WIDTHS;
|
|
45
|
+
/**
|
|
46
|
+
* The geometry-discriminating props ({@link ExpandableSurface} spreads these
|
|
47
|
+
* into its own): `side` callers keep passing their shipped CSS width; `stage`
|
|
48
|
+
* callers pick a preset.
|
|
49
|
+
*/
|
|
50
|
+
export type SurfaceGeometryProps = {
|
|
51
|
+
/** The right-anchored sheet (default) — peripheral review surfaces only. */
|
|
52
|
+
variant?: 'side';
|
|
53
|
+
/** CSS width of the sheet panel (callers keep their shipped widths). */
|
|
54
|
+
width: string;
|
|
55
|
+
} | {
|
|
56
|
+
/** The centered stage — primary-activity surfaces (ratified AD-A). */
|
|
57
|
+
variant: 'stage';
|
|
58
|
+
/** Per-surface stage width preset. */
|
|
59
|
+
preset: StageWidthPreset;
|
|
60
|
+
};
|
|
61
|
+
export interface SurfaceGeometry {
|
|
62
|
+
/** The fixed full-viewport container. */
|
|
63
|
+
container: string;
|
|
64
|
+
/** The dismiss backdrop (paint order keeps it under the panel). */
|
|
65
|
+
backdrop: string;
|
|
66
|
+
/** The `role="dialog"` panel. */
|
|
67
|
+
panel: string;
|
|
68
|
+
/** Inline style carrying the resolved panel width (deterministic, no class scanning). */
|
|
69
|
+
panelStyle: CSSProperties;
|
|
70
|
+
}
|
|
71
|
+
export declare function surfaceGeometry(props: SurfaceGeometryProps): SurfaceGeometry;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EC-431 (AD-A) — the pure geometry core for {@link ExpandableSurface}: which
|
|
3
|
+
* classes/width a surface mounts with, per variant. Extracted (the EC-391
|
|
4
|
+
* class-contract pattern) so the geometry is pinnable in the node test env,
|
|
5
|
+
* where the portal itself renders `null`.
|
|
6
|
+
*
|
|
7
|
+
* Two geometries, one mechanic set (portal-to-body, backdrop, header close,
|
|
8
|
+
* layer-stacked Escape):
|
|
9
|
+
*
|
|
10
|
+
* - `side` — the right-anchored sheet. Reserved for genuinely PERIPHERAL review
|
|
11
|
+
* surfaces (a side panel semantically means "detail next to the primary
|
|
12
|
+
* activity"); today exactly one: the AI proposal review card. Byte-for-byte
|
|
13
|
+
* the EC-392 shipped shape — no visual change for that caller.
|
|
14
|
+
* - `stage` — the centered takeover for PRIMARY-ACTIVITY surfaces (ratified
|
|
15
|
+
* AD-A, 2026-07-16): centered, near-full height, growing from center with a
|
|
16
|
+
* subtle scale/opacity entrance — never an edge slide. Width comes from a
|
|
17
|
+
* per-surface preset ({@link STAGE_WIDTHS}).
|
|
18
|
+
*
|
|
19
|
+
* Motion: only the stage has an entrance transition, and every animated class
|
|
20
|
+
* is `motion-safe:`-gated — under `prefers-reduced-motion` the stage appears
|
|
21
|
+
* instantly (pinned by `surface-geometry.test.ts`). The side sheet keeps its
|
|
22
|
+
* shipped no-transition behavior.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The stage width presets — one per surface family (ratified AD-A). Consumers
|
|
26
|
+
* pick a preset, never a raw width, so stage surfaces stay mutually consistent:
|
|
27
|
+
*
|
|
28
|
+
* - `prose` — distraction-free writing (the rich-text expand, EC-431/AD-B).
|
|
29
|
+
* EC-431 ratified `min(920px, 92vw)`; owner feedback on 0.0.68 (EC-436,
|
|
30
|
+
* 2026-07-16 — "the dialog + text looks a bit narrow on a big screen")
|
|
31
|
+
* SUPERSEDED that number: the panel widened to `min(1160px, 94vw)` and the
|
|
32
|
+
* caller's CONTENT column grew in step (~78ch,
|
|
33
|
+
* `RICH_TEXT_STAGE_CONTENT_CLASS`). The column stays constrained — the stage
|
|
34
|
+
* being wider than the text is the point, not a defect.
|
|
35
|
+
* - `grid` — large centered browsing grids (the EC-432 media browser).
|
|
36
|
+
* - `form` — a single-column form measure (the EC-430 embed inspector).
|
|
37
|
+
*/
|
|
38
|
+
export const STAGE_WIDTHS = {
|
|
39
|
+
prose: 'min(1160px, 94vw)',
|
|
40
|
+
grid: 'min(1240px, 94vw)',
|
|
41
|
+
form: 'min(720px, 92vw)',
|
|
42
|
+
};
|
|
43
|
+
export function surfaceGeometry(props) {
|
|
44
|
+
if (props.variant === 'stage') {
|
|
45
|
+
return {
|
|
46
|
+
// Near-full height falls out of the container padding: the panel is
|
|
47
|
+
// `h-full` inside the padded fixed container, so it always keeps a
|
|
48
|
+
// breathing gutter to every viewport edge.
|
|
49
|
+
container: 'fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6',
|
|
50
|
+
backdrop: 'absolute inset-0 bg-background/70 backdrop-blur-sm motion-safe:animate-ec-stage-fade',
|
|
51
|
+
panel: 'relative flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl motion-safe:animate-ec-stage-in',
|
|
52
|
+
panelStyle: { width: STAGE_WIDTHS[props.preset] },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// The EC-392 shipped sheet shape, byte-for-byte — the proposal card must not
|
|
56
|
+
// change visually (ratified: side survives for peripheral review).
|
|
57
|
+
return {
|
|
58
|
+
container: 'fixed inset-0 z-50 flex',
|
|
59
|
+
backdrop: 'flex-1 bg-background/70 backdrop-blur-sm',
|
|
60
|
+
panel: 'flex h-full flex-col border-l border-border bg-card shadow-2xl',
|
|
61
|
+
panelStyle: { width: props.width },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=surface-geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-geometry.js","sourceRoot":"","sources":["../../src/ui/surface-geometry.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,mBAAmB;IAC1B,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,kBAAkB;CAChB,CAAA;AAkCV,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QAC9B,OAAO;YACL,oEAAoE;YACpE,mEAAmE;YACnE,2CAA2C;YAC3C,SAAS,EAAE,gEAAgE;YAC3E,QAAQ,EACN,sFAAsF;YACxF,KAAK,EACH,kIAAkI;YACpI,UAAU,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;SAClD,CAAA;IACH,CAAC;IACD,6EAA6E;IAC7E,mEAAmE;IACnE,OAAO;QACL,SAAS,EAAE,yBAAyB;QACpC,QAAQ,EAAE,0CAA0C;QACpD,KAAK,EAAE,gEAAgE;QACvE,UAAU,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;KACnC,CAAA;AACH,CAAC","sourcesContent":["import type { CSSProperties } from 'react'\n\n/**\n * EC-431 (AD-A) — the pure geometry core for {@link ExpandableSurface}: which\n * classes/width a surface mounts with, per variant. Extracted (the EC-391\n * class-contract pattern) so the geometry is pinnable in the node test env,\n * where the portal itself renders `null`.\n *\n * Two geometries, one mechanic set (portal-to-body, backdrop, header close,\n * layer-stacked Escape):\n *\n * - `side` — the right-anchored sheet. Reserved for genuinely PERIPHERAL review\n * surfaces (a side panel semantically means \"detail next to the primary\n * activity\"); today exactly one: the AI proposal review card. Byte-for-byte\n * the EC-392 shipped shape — no visual change for that caller.\n * - `stage` — the centered takeover for PRIMARY-ACTIVITY surfaces (ratified\n * AD-A, 2026-07-16): centered, near-full height, growing from center with a\n * subtle scale/opacity entrance — never an edge slide. Width comes from a\n * per-surface preset ({@link STAGE_WIDTHS}).\n *\n * Motion: only the stage has an entrance transition, and every animated class\n * is `motion-safe:`-gated — under `prefers-reduced-motion` the stage appears\n * instantly (pinned by `surface-geometry.test.ts`). The side sheet keeps its\n * shipped no-transition behavior.\n */\n\n/**\n * The stage width presets — one per surface family (ratified AD-A). Consumers\n * pick a preset, never a raw width, so stage surfaces stay mutually consistent:\n *\n * - `prose` — distraction-free writing (the rich-text expand, EC-431/AD-B).\n * EC-431 ratified `min(920px, 92vw)`; owner feedback on 0.0.68 (EC-436,\n * 2026-07-16 — \"the dialog + text looks a bit narrow on a big screen\")\n * SUPERSEDED that number: the panel widened to `min(1160px, 94vw)` and the\n * caller's CONTENT column grew in step (~78ch,\n * `RICH_TEXT_STAGE_CONTENT_CLASS`). The column stays constrained — the stage\n * being wider than the text is the point, not a defect.\n * - `grid` — large centered browsing grids (the EC-432 media browser).\n * - `form` — a single-column form measure (the EC-430 embed inspector).\n */\nexport const STAGE_WIDTHS = {\n prose: 'min(1160px, 94vw)',\n grid: 'min(1240px, 94vw)',\n form: 'min(720px, 92vw)',\n} as const\n\nexport type StageWidthPreset = keyof typeof STAGE_WIDTHS\n\n/**\n * The geometry-discriminating props ({@link ExpandableSurface} spreads these\n * into its own): `side` callers keep passing their shipped CSS width; `stage`\n * callers pick a preset.\n */\nexport type SurfaceGeometryProps =\n | {\n /** The right-anchored sheet (default) — peripheral review surfaces only. */\n variant?: 'side'\n /** CSS width of the sheet panel (callers keep their shipped widths). */\n width: string\n }\n | {\n /** The centered stage — primary-activity surfaces (ratified AD-A). */\n variant: 'stage'\n /** Per-surface stage width preset. */\n preset: StageWidthPreset\n }\n\nexport interface SurfaceGeometry {\n /** The fixed full-viewport container. */\n container: string\n /** The dismiss backdrop (paint order keeps it under the panel). */\n backdrop: string\n /** The `role=\"dialog\"` panel. */\n panel: string\n /** Inline style carrying the resolved panel width (deterministic, no class scanning). */\n panelStyle: CSSProperties\n}\n\nexport function surfaceGeometry(props: SurfaceGeometryProps): SurfaceGeometry {\n if (props.variant === 'stage') {\n return {\n // Near-full height falls out of the container padding: the panel is\n // `h-full` inside the padded fixed container, so it always keeps a\n // breathing gutter to every viewport edge.\n container: 'fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6',\n backdrop:\n 'absolute inset-0 bg-background/70 backdrop-blur-sm motion-safe:animate-ec-stage-fade',\n panel:\n 'relative flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl motion-safe:animate-ec-stage-in',\n panelStyle: { width: STAGE_WIDTHS[props.preset] },\n }\n }\n // The EC-392 shipped sheet shape, byte-for-byte — the proposal card must not\n // change visually (ratified: side survives for peripheral review).\n return {\n container: 'fixed inset-0 z-50 flex',\n backdrop: 'flex-1 bg-background/70 backdrop-blur-sm',\n panel: 'flex h-full flex-col border-l border-border bg-card shadow-2xl',\n panelStyle: { width: props.width },\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elytracms/studio",
|
|
3
3
|
"author": "finaldream <hi@finaldream.de>",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.69",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "The Elytra studio engine (UI + lib + routes + data-only Convex schema) as an importable package. Extraction staged in EXTRACTION.md (EC-223); Stages 1-2 = UI primitives + router-free lib leaves.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"lucide-react": "^1.23.0",
|
|
147
147
|
"tailwind-merge": "^3.6.0",
|
|
148
148
|
"zod": "^4.4.3",
|
|
149
|
-
"@elytracms/core": "^0.0.
|
|
149
|
+
"@elytracms/core": "^0.0.69"
|
|
150
150
|
},
|
|
151
151
|
"peerDependencies": {
|
|
152
152
|
"@tanstack/react-router": "^1.170.0",
|