@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.
Files changed (89) hide show
  1. package/dist/components/assets/asset-detail.d.ts +4 -2
  2. package/dist/components/assets/asset-detail.js +4 -2
  3. package/dist/components/assets/asset-detail.js.map +1 -1
  4. package/dist/components/assets/assets-collection-view-source.d.ts +30 -19
  5. package/dist/components/assets/assets-collection-view-source.js +55 -23
  6. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  7. package/dist/components/builder/canvas-chrome-css.js +6 -1
  8. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  9. package/dist/components/builder/property-inspector.d.ts +0 -21
  10. package/dist/components/builder/property-inspector.js +5 -22
  11. package/dist/components/builder/property-inspector.js.map +1 -1
  12. package/dist/components/builder/selection-overlay.d.ts +17 -4
  13. package/dist/components/builder/selection-overlay.js +17 -15
  14. package/dist/components/builder/selection-overlay.js.map +1 -1
  15. package/dist/components/builder/spacing-handles.d.ts +26 -1
  16. package/dist/components/builder/spacing-handles.js +66 -3
  17. package/dist/components/builder/spacing-handles.js.map +1 -1
  18. package/dist/components/cms/asset-picker-core.d.ts +52 -0
  19. package/dist/components/cms/asset-picker-core.js +69 -0
  20. package/dist/components/cms/asset-picker-core.js.map +1 -0
  21. package/dist/components/cms/asset-picker.d.ts +16 -4
  22. package/dist/components/cms/asset-picker.js +34 -50
  23. package/dist/components/cms/asset-picker.js.map +1 -1
  24. package/dist/components/cms/bulk-bar.d.ts +11 -1
  25. package/dist/components/cms/bulk-bar.js +10 -3
  26. package/dist/components/cms/bulk-bar.js.map +1 -1
  27. package/dist/components/cms/collection-table.js +19 -2
  28. package/dist/components/cms/collection-table.js.map +1 -1
  29. package/dist/components/cms/detail-editor.js +45 -54
  30. package/dist/components/cms/detail-editor.js.map +1 -1
  31. package/dist/components/cms/field-control.js +5 -1
  32. package/dist/components/cms/field-control.js.map +1 -1
  33. package/dist/components/cms/prop-fields.js +5 -1
  34. package/dist/components/cms/prop-fields.js.map +1 -1
  35. package/dist/components/cms/prop-visibility.d.ts +3 -0
  36. package/dist/components/cms/prop-visibility.js +31 -0
  37. package/dist/components/cms/prop-visibility.js.map +1 -0
  38. package/dist/components/cms/reference-confirm.js +5 -1
  39. package/dist/components/cms/reference-confirm.js.map +1 -1
  40. package/dist/components/cms/rich-text/component-embed.d.ts +8 -6
  41. package/dist/components/cms/rich-text/component-embed.js +21 -62
  42. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  43. package/dist/components/cms/rich-text/editor.d.ts +33 -4
  44. package/dist/components/cms/rich-text/editor.js +52 -22
  45. package/dist/components/cms/rich-text/editor.js.map +1 -1
  46. package/dist/components/cms/rich-text/embed-props.d.ts +60 -27
  47. package/dist/components/cms/rich-text/embed-props.js +102 -45
  48. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  49. package/dist/components/rail-view-source.d.ts +50 -1
  50. package/dist/components/rail-view-source.js +56 -2
  51. package/dist/components/rail-view-source.js.map +1 -1
  52. package/dist/components/redirects/redirects-collection-view-source.d.ts +3 -2
  53. package/dist/components/redirects/redirects-collection-view-source.js +4 -3
  54. package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
  55. package/dist/components/users/users-collection-view-source.js +1 -1
  56. package/dist/components/users/users-collection-view-source.js.map +1 -1
  57. package/dist/lib/collection-view/collection-view-source.d.ts +12 -0
  58. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  59. package/dist/lib/dnd/gap-drag.d.ts +8 -1
  60. package/dist/lib/dnd/gap-drag.js +4 -0
  61. package/dist/lib/dnd/gap-drag.js.map +1 -1
  62. package/dist/lib/i18n/messages.de.js +3 -2
  63. package/dist/lib/i18n/messages.de.js.map +1 -1
  64. package/dist/lib/i18n/messages.en.d.ts +1 -1
  65. package/dist/lib/i18n/messages.en.js +4 -2
  66. package/dist/lib/i18n/messages.en.js.map +1 -1
  67. package/dist/lib/references/references-store.d.ts +8 -1
  68. package/dist/lib/references/references-store.js +12 -2
  69. package/dist/lib/references/references-store.js.map +1 -1
  70. package/dist/ui/expandable-surface.d.ts +35 -25
  71. package/dist/ui/expandable-surface.js +41 -21
  72. package/dist/ui/expandable-surface.js.map +1 -1
  73. package/dist/ui/index.d.ts +1 -0
  74. package/dist/ui/index.js +1 -0
  75. package/dist/ui/index.js.map +1 -1
  76. package/dist/ui/surface-geometry.d.ts +71 -0
  77. package/dist/ui/surface-geometry.js +64 -0
  78. package/dist/ui/surface-geometry.js.map +1 -0
  79. package/package.json +2 -2
  80. package/src/styles/studio.css +30 -0
  81. package/dist/components/cms/rich-text/embed-inspector-context.d.ts +0 -50
  82. package/dist/components/cms/rich-text/embed-inspector-context.js +0 -12
  83. package/dist/components/cms/rich-text/embed-inspector-context.js.map +0 -1
  84. package/dist/components/cms/rich-text/embed-inspector.d.ts +0 -25
  85. package/dist/components/cms/rich-text/embed-inspector.js +0 -48
  86. package/dist/components/cms/rich-text/embed-inspector.js.map +0 -1
  87. package/dist/components/cms/use-embed-inspector-host.d.ts +0 -12
  88. package/dist/components/cms/use-embed-inspector-host.js +0 -17
  89. package/dist/components/cms/use-embed-inspector-host.js.map +0 -1
@@ -15,8 +15,10 @@
15
15
  * editing an asset here is never a downgrade from editing it via a field.
16
16
  *
17
17
  * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`
18
- * → `deleteAsset`, keyed on `{ type: 'asset' }` — never the generic document
19
- * reference-confirm the universal table's row/bulk delete would wrongly apply).
18
+ * → `deleteAsset`, keyed on `{ type: 'asset' }`). Since EC-433 the universal
19
+ * table's row/bulk delete runs the SAME asset-shaped reference check (the
20
+ * source's `referenceTarget`) and the same confirmed store delete — the two
21
+ * surfaces share one delete semantics, not two divergent ones.
20
22
  */
21
23
  export declare function AssetDetail({ projectId, assetId }: {
22
24
  projectId: string;
@@ -31,8 +31,10 @@ import { useAssets } from './assets-store.js';
31
31
  * editing an asset here is never a downgrade from editing it via a field.
32
32
  *
33
33
  * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`
34
- * → `deleteAsset`, keyed on `{ type: 'asset' }` — never the generic document
35
- * reference-confirm the universal table's row/bulk delete would wrongly apply).
34
+ * → `deleteAsset`, keyed on `{ type: 'asset' }`). Since EC-433 the universal
35
+ * table's row/bulk delete runs the SAME asset-shaped reference check (the
36
+ * source's `referenceTarget`) and the same confirmed store delete — the two
37
+ * surfaces share one delete semantics, not two divergent ones.
36
38
  */
37
39
  export function AssetDetail({ projectId, assetId }) {
38
40
  const source = useCollectionView();
@@ -1 +1 @@
1
- {"version":3,"file":"asset-detail.js","sourceRoot":"","sources":["../../../src/components/assets/asset-detail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Heading,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { FieldControl } from '../cms/field-control'\nimport { fieldLabel } from '../cms/cms-labels'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thumb'\nimport type { FocalPoint } from './focal-point'\nimport { assetMetaFieldsOf, formatBytes, pruneMeta } from './asset-metadata'\nimport { useAssets } from './assets-store'\n\n/**\n * The assets rail's DETAIL surface (EC-319). Pragmatic call: assets don't fit\n * the portable `PortableDetailEditor` shell cleanly — there's no dirty/Save/\n * Publish document model here (an asset record isn't a `cmsDocument`), and the\n * focal-point picker is a bespoke drag interaction over the image, not a\n * scalar field value a generic form row can host without contortions. So this\n * is a bespoke surface (mirrors the CMS document editor's shape: a header +\n * panels), reached via the SAME rail-owned `detailPath`/`listPath` seam every\n * other rail uses (AD-19) — only the body differs.\n *\n * Unifies what were two divergent asset-edit UIs before this task: the plain\n * alt/title dialog on the old asset library card, and the richer schema-driven\n * + focal-point \"image options\" dialog opened from an `asset` field elsewhere\n * (`field-control.tsx`). This page reuses the LATTER (richer) content, so\n * editing an asset here is never a downgrade from editing it via a field.\n *\n * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`\n * → `deleteAsset`, keyed on `{ type: 'asset' }` — never the generic document\n * reference-confirm the universal table's row/bulk delete would wrongly apply).\n */\nexport function AssetDetail({ projectId, assetId }: { projectId: string; assetId: string }) {\n const source = useCollectionView()\n const store = useAssets()\n const record = useAssetRecord(assetId)\n const collection = source.schema\n\n const collectionLabel = localizedLabel(collection.form?.label, collection.id)\n useSetBreadcrumb(\n useMemo(() => {\n if (source.listPath === undefined) return [{ label: collectionLabel }]\n const title = record?.title ?? record?.filename ?? assetId\n return [\n { label: collectionLabel, to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } },\n { label: title },\n ]\n }, [collectionLabel, source.listPath, record, projectId, assetId]),\n )\n\n if (record === undefined) {\n if (store.status === 'loading') {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.loading')}\n </Text>\n </Stack>\n )\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"text-center\">\n <Heading level={2} size=\"h2\">\n {translate('cms.editor.notFoundTitle')}\n </Heading>\n {source.listPath !== undefined ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: source.listPath }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('cms.editor.backToList', { label: collectionLabel })}\n </Link>\n ) : null}\n </Stack>\n </div>\n )\n }\n\n // Remount per asset (the surface's own key, set by the caller) resets every\n // local buffer below cleanly when navigating from one asset's detail to\n // another — no stale-state carryover to guard against by hand.\n return <AssetDetailBody projectId={projectId} record={record} collection={source.schema} />\n}\n\nfunction AssetDetailBody({\n projectId,\n record,\n collection,\n}: {\n projectId: string\n record: AssetRecord\n collection: CollectionDef\n}) {\n const store = useAssets()\n const metaFields = useMemo(() => assetMetaFieldsOf(collection), [collection])\n\n // Metadata buffers locally and commits explicitly (Save) — an asset record has\n // no dirty/Save document model of its own to piggyback on. The focal point\n // commits immediately (it drives a live visual), matching the field's options\n // dialog (`field-control.tsx`).\n const [meta, setMeta] = useState<Record<string, unknown>>(() => ({\n ...(record.alt !== undefined ? { alt: record.alt } : {}),\n ...(record.meta ?? {}),\n }))\n const [focal, setFocal] = useState<FocalPoint | undefined>(record.focalPoint)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n\n function setField(name: string, value: unknown) {\n setDirty(true)\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n async function save() {\n setSaving(true)\n setSaveError(null)\n const result = await store.updateMetadata(record.id, { meta: pruneMeta(meta) })\n setSaving(false)\n if (result.ok) setDirty(false)\n else setSaveError(result.message)\n }\n\n function commitFocal(point: FocalPoint | null) {\n setFocal(point ?? undefined)\n void store.updateMetadata(record.id, { focalPoint: point })\n }\n\n return (\n <Stack gap=\"4\" className=\"mx-auto max-w-3xl p-4 pb-10 sm:p-6\">\n <AssetDetailHeader projectId={projectId} record={record} />\n\n <Panel>\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"2\">\n <FocalPointPicker record={record} point={focal} onChange={commitFocal} onPreview={setFocal} />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n </PanelBody>\n </Panel>\n\n <Panel data-testid=\"asset-detail-meta\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.detail.metadataHeading')}\n </Text>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"3\">\n {metaFields.map((f: FieldDef) => (\n <Field\n key={f.name}\n label={fieldLabel(f)}\n hint={localizedLabel(f.form?.description) || undefined}\n >\n <FieldControl\n id={`asset-detail-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={false}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n {translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' })}\n </Text>\n ) : null}\n {saveError ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-detail-save-error\">\n {saveError}\n </Text>\n ) : null}\n {metaFields.length > 0 ? (\n <Row gap=\"2\">\n <Spacer />\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={!dirty || saving}\n data-testid=\"asset-detail-save\"\n >\n {saving ? translate('common.saving') : translate('common.save')}\n </Button>\n </Row>\n ) : null}\n </Stack>\n </PanelBody>\n </Panel>\n </Stack>\n )\n}\n\n/** Filename/title heading + system info (content type, dimensions, size) + actions. */\nfunction AssetDetailHeader({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const dims = record.width && record.height ? `${record.width}×${record.height}` : null\n const info = [record.contentType, dims, formatBytes(record.byteSize)].filter(Boolean).join(' · ')\n return (\n <Row gap=\"3\" align=\"start\" wrap>\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Heading level={2} size=\"h2\" className=\"truncate\" title={record.title ?? record.filename}>\n {record.title ?? record.filename}\n </Heading>\n {record.title ? (\n <Text size=\"sm\" tone=\"muted\" className=\"truncate\">\n {record.filename}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {info}\n </Text>\n </Stack>\n <Row gap=\"2\">\n <AssetReferencesButton projectId={projectId} record={record} />\n <AssetDeleteButton projectId={projectId} record={record} />\n </Row>\n </Row>\n )\n}\n\n/** \"Where is this asset used?\" (EC-172): the shared references panel, EC-155-backed. */\nfunction AssetReferencesButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n data-testid=\"asset-detail-references\"\n >\n <Link2 /> {translate('assets.references.buttonLabel')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>\n {translate('assets.references.title', { name: record.title ?? record.filename })}\n </DialogTitle>\n <DialogDescription>{translate('assets.references.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: record.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): enumerates the exact inbound\n * references — index-accurate, with deep links to the field/node — before\n * removing. Deleting a referenced asset is weak-by-default (allowed with an\n * explicit \"delete anyway\"); every reference dangles into a visible validation\n * issue rather than silently breaking. On success, returns to the list.\n */\nfunction AssetDeleteButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const source = useCollectionView()\n const store = useAssets()\n const navigate = useNavigate()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await loadInboundAssetReferenceViews(projectId, record.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n // The dialog has already enumerated usages (or shown there are none) and the\n // user is confirming either way — confirm unconditionally, skipping the\n // redundant inbound re-check (EC-301 perf). Weak-by-default: a still-\n // referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(record.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n if (source.listPath !== undefined) {\n void navigate({ to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } })\n }\n } else if (result.outcome === 'blocked') {\n setError(\n translate(\n result.usageCount === 1 ? 'assets.delete.raceError.one' : 'assets.delete.raceError.other',\n { count: result.usageCount },\n ),\n )\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void openDialog()}\n data-testid=\"asset-detail-delete\"\n >\n <Trash2 /> {translate('common.delete')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{translate('assets.delete.title', { filename: record.filename })}</DialogTitle>\n <DialogDescription>{translate('assets.delete.description')}</DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.checking')}\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate(usageCount === 1 ? 'assets.delete.inUse.one' : 'assets.delete.inUse.other', {\n count: usageCount,\n })}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n {translate('assets.delete.brokenWarning')}\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.noReferences')}\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n {translate('common.cancel')}\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? translate('assets.delete.confirmAnyway') : translate('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
1
+ {"version":3,"file":"asset-detail.js","sourceRoot":"","sources":["../../../src/components/assets/asset-detail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Heading,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { FieldControl } from '../cms/field-control'\nimport { fieldLabel } from '../cms/cms-labels'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thumb'\nimport type { FocalPoint } from './focal-point'\nimport { assetMetaFieldsOf, formatBytes, pruneMeta } from './asset-metadata'\nimport { useAssets } from './assets-store'\n\n/**\n * The assets rail's DETAIL surface (EC-319). Pragmatic call: assets don't fit\n * the portable `PortableDetailEditor` shell cleanly — there's no dirty/Save/\n * Publish document model here (an asset record isn't a `cmsDocument`), and the\n * focal-point picker is a bespoke drag interaction over the image, not a\n * scalar field value a generic form row can host without contortions. So this\n * is a bespoke surface (mirrors the CMS document editor's shape: a header +\n * panels), reached via the SAME rail-owned `detailPath`/`listPath` seam every\n * other rail uses (AD-19) — only the body differs.\n *\n * Unifies what were two divergent asset-edit UIs before this task: the plain\n * alt/title dialog on the old asset library card, and the richer schema-driven\n * + focal-point \"image options\" dialog opened from an `asset` field elsewhere\n * (`field-control.tsx`). This page reuses the LATTER (richer) content, so\n * editing an asset here is never a downgrade from editing it via a field.\n *\n * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`\n * → `deleteAsset`, keyed on `{ type: 'asset' }`). Since EC-433 the universal\n * table's row/bulk delete runs the SAME asset-shaped reference check (the\n * source's `referenceTarget`) and the same confirmed store delete — the two\n * surfaces share one delete semantics, not two divergent ones.\n */\nexport function AssetDetail({ projectId, assetId }: { projectId: string; assetId: string }) {\n const source = useCollectionView()\n const store = useAssets()\n const record = useAssetRecord(assetId)\n const collection = source.schema\n\n const collectionLabel = localizedLabel(collection.form?.label, collection.id)\n useSetBreadcrumb(\n useMemo(() => {\n if (source.listPath === undefined) return [{ label: collectionLabel }]\n const title = record?.title ?? record?.filename ?? assetId\n return [\n { label: collectionLabel, to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } },\n { label: title },\n ]\n }, [collectionLabel, source.listPath, record, projectId, assetId]),\n )\n\n if (record === undefined) {\n if (store.status === 'loading') {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.loading')}\n </Text>\n </Stack>\n )\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"text-center\">\n <Heading level={2} size=\"h2\">\n {translate('cms.editor.notFoundTitle')}\n </Heading>\n {source.listPath !== undefined ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: source.listPath }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('cms.editor.backToList', { label: collectionLabel })}\n </Link>\n ) : null}\n </Stack>\n </div>\n )\n }\n\n // Remount per asset (the surface's own key, set by the caller) resets every\n // local buffer below cleanly when navigating from one asset's detail to\n // another — no stale-state carryover to guard against by hand.\n return <AssetDetailBody projectId={projectId} record={record} collection={source.schema} />\n}\n\nfunction AssetDetailBody({\n projectId,\n record,\n collection,\n}: {\n projectId: string\n record: AssetRecord\n collection: CollectionDef\n}) {\n const store = useAssets()\n const metaFields = useMemo(() => assetMetaFieldsOf(collection), [collection])\n\n // Metadata buffers locally and commits explicitly (Save) — an asset record has\n // no dirty/Save document model of its own to piggyback on. The focal point\n // commits immediately (it drives a live visual), matching the field's options\n // dialog (`field-control.tsx`).\n const [meta, setMeta] = useState<Record<string, unknown>>(() => ({\n ...(record.alt !== undefined ? { alt: record.alt } : {}),\n ...(record.meta ?? {}),\n }))\n const [focal, setFocal] = useState<FocalPoint | undefined>(record.focalPoint)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n\n function setField(name: string, value: unknown) {\n setDirty(true)\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n async function save() {\n setSaving(true)\n setSaveError(null)\n const result = await store.updateMetadata(record.id, { meta: pruneMeta(meta) })\n setSaving(false)\n if (result.ok) setDirty(false)\n else setSaveError(result.message)\n }\n\n function commitFocal(point: FocalPoint | null) {\n setFocal(point ?? undefined)\n void store.updateMetadata(record.id, { focalPoint: point })\n }\n\n return (\n <Stack gap=\"4\" className=\"mx-auto max-w-3xl p-4 pb-10 sm:p-6\">\n <AssetDetailHeader projectId={projectId} record={record} />\n\n <Panel>\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"2\">\n <FocalPointPicker record={record} point={focal} onChange={commitFocal} onPreview={setFocal} />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n </PanelBody>\n </Panel>\n\n <Panel data-testid=\"asset-detail-meta\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.detail.metadataHeading')}\n </Text>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"3\">\n {metaFields.map((f: FieldDef) => (\n <Field\n key={f.name}\n label={fieldLabel(f)}\n hint={localizedLabel(f.form?.description) || undefined}\n >\n <FieldControl\n id={`asset-detail-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={false}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n {translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' })}\n </Text>\n ) : null}\n {saveError ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-detail-save-error\">\n {saveError}\n </Text>\n ) : null}\n {metaFields.length > 0 ? (\n <Row gap=\"2\">\n <Spacer />\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={!dirty || saving}\n data-testid=\"asset-detail-save\"\n >\n {saving ? translate('common.saving') : translate('common.save')}\n </Button>\n </Row>\n ) : null}\n </Stack>\n </PanelBody>\n </Panel>\n </Stack>\n )\n}\n\n/** Filename/title heading + system info (content type, dimensions, size) + actions. */\nfunction AssetDetailHeader({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const dims = record.width && record.height ? `${record.width}×${record.height}` : null\n const info = [record.contentType, dims, formatBytes(record.byteSize)].filter(Boolean).join(' · ')\n return (\n <Row gap=\"3\" align=\"start\" wrap>\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Heading level={2} size=\"h2\" className=\"truncate\" title={record.title ?? record.filename}>\n {record.title ?? record.filename}\n </Heading>\n {record.title ? (\n <Text size=\"sm\" tone=\"muted\" className=\"truncate\">\n {record.filename}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {info}\n </Text>\n </Stack>\n <Row gap=\"2\">\n <AssetReferencesButton projectId={projectId} record={record} />\n <AssetDeleteButton projectId={projectId} record={record} />\n </Row>\n </Row>\n )\n}\n\n/** \"Where is this asset used?\" (EC-172): the shared references panel, EC-155-backed. */\nfunction AssetReferencesButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n data-testid=\"asset-detail-references\"\n >\n <Link2 /> {translate('assets.references.buttonLabel')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>\n {translate('assets.references.title', { name: record.title ?? record.filename })}\n </DialogTitle>\n <DialogDescription>{translate('assets.references.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: record.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): enumerates the exact inbound\n * references — index-accurate, with deep links to the field/node — before\n * removing. Deleting a referenced asset is weak-by-default (allowed with an\n * explicit \"delete anyway\"); every reference dangles into a visible validation\n * issue rather than silently breaking. On success, returns to the list.\n */\nfunction AssetDeleteButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const source = useCollectionView()\n const store = useAssets()\n const navigate = useNavigate()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await loadInboundAssetReferenceViews(projectId, record.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n // The dialog has already enumerated usages (or shown there are none) and the\n // user is confirming either way — confirm unconditionally, skipping the\n // redundant inbound re-check (EC-301 perf). Weak-by-default: a still-\n // referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(record.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n if (source.listPath !== undefined) {\n void navigate({ to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } })\n }\n } else if (result.outcome === 'blocked') {\n setError(\n translate(\n result.usageCount === 1 ? 'assets.delete.raceError.one' : 'assets.delete.raceError.other',\n { count: result.usageCount },\n ),\n )\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void openDialog()}\n data-testid=\"asset-detail-delete\"\n >\n <Trash2 /> {translate('common.delete')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{translate('assets.delete.title', { filename: record.filename })}</DialogTitle>\n <DialogDescription>{translate('assets.delete.description')}</DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.checking')}\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate(usageCount === 1 ? 'assets.delete.inUse.one' : 'assets.delete.inUse.other', {\n count: usageCount,\n })}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n {translate('assets.delete.brokenWarning')}\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.noReferences')}\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n {translate('common.cancel')}\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? translate('assets.delete.confirmAnyway') : translate('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
@@ -1,24 +1,25 @@
1
- import type { CmsDocument, CollectionDef } from '@elytracms/core/cms-core';
2
- import type { AssetRecord } from '@elytracms/core/persistence';
1
+ import type { CmsDocument, CollectionDef, DocumentRef } from '@elytracms/core/cms-core';
2
+ import type { AssetRecord, ReferenceTarget } from '@elytracms/core/persistence';
3
3
  import type { OperationError } from '@elytracms/core/operations';
4
4
  import type { DeleteAssetResult } from '@elytracms/studio/lib/assets';
5
5
  import type { CollectionViewSource, CollectionViewCapabilities } from '@elytracms/studio/lib/collection-view/collection-view-source';
6
6
  /**
7
- * The capability profile of the assets rail (EC-319). Hoisted to a module-level
8
- * constant (no prop/state dependency) so it's both the value the hook returns
9
- * AND independently unit-testable.
7
+ * The capability profile of the assets rail (EC-319, EC-433). Hoisted to a
8
+ * module-level constant (no prop/state dependency) so it's both the value the
9
+ * hook returns AND independently unit-testable.
10
10
  *
11
11
  * `create` is off — a new asset comes from an upload, never a blank draft.
12
- * `remove` is off DELIBERATELY, even though the store can safely delete one
13
- * (EC-202): the universal table's generic row/bulk delete runs through
14
- * `ReferenceConfirmDialog`, which enumerates inbound references as
15
- * `{ type: 'document' }` targets — the wrong index shape for an asset (indexed
16
- * as `{ type: 'asset' }`, see `loadInboundAssetReferenceViews`). Wiring
17
- * `remove: true` here would silently show "not referenced" for an asset that
18
- * IS referenced. Deletion instead stays on the asset detail surface
19
- * (`asset-detail.tsx`), which keeps calling the asset-specific reference check.
20
- * Nothing else applies to assets: no publish/versions/locales/inline-edit/bulk/
21
- * live-URL/hierarchy/import-export.
12
+ * `remove` + `bulk` are ON since EC-433: the list's row/bulk delete used to be
13
+ * off deliberately because the generic `ReferenceConfirmDialog` enumerated
14
+ * inbound references as `{ type: 'document' }` targets — the wrong index shape
15
+ * for an asset — which would have shown "not referenced" for an asset that IS
16
+ * referenced. That pre-flight now queries the shape THIS source declares via
17
+ * {@link assetReferenceTarget} (`{ type: 'asset' }`, the same lookup the
18
+ * detail surface runs), and the confirmed delete dispatches the SAME checked
19
+ * store delete the detail surface uses (`deleteAsset`: original + EC-400 thumb
20
+ * blobs, then the record — EC-420 lqips ride on the record). Nothing else
21
+ * applies to assets: no publish/versions/locales/inline-edit/live-URL/
22
+ * hierarchy/import-export.
22
23
  */
23
24
  export declare const ASSET_COLLECTION_CAPABILITIES: CollectionViewCapabilities;
24
25
  /**
@@ -41,11 +42,21 @@ export declare function assetToDocument(collection: CollectionDef, record: Asset
41
42
  */
42
43
  export declare function documentToAssetMetaPatch(collection: CollectionDef, doc: CmsDocument): Record<string, unknown>;
43
44
  /** Assets' `remove()` returns an `outcome`-discriminated {@link DeleteAssetResult}
44
- * (not the `{ ok }` shape users/redirects use, EC-327): only `'error'` is a
45
- * failure the shell surfaces via `lastError`. `'blocked'`/`'not-found'` are
46
- * unreached from this rail's UI (`capabilities.remove === false`) but handled
47
- * soundly regardless, matching the pre-EC-327 behaviour exactly. */
45
+ * (not the `{ ok }` shape users/redirects use, EC-327). `'error'` surfaces as-is;
46
+ * `'blocked'` (the unconfirmed usage gate fired — nothing was removed) surfaces
47
+ * as an explicit failure too (EC-433): a delete that left the asset in place
48
+ * must NEVER look like success. It is unreachable from this rail's UI — every
49
+ * delete path confirms through the reference dialog and dispatches with
50
+ * `confirmInUse` — but stays honest for any direct caller. `'not-found'` maps
51
+ * to success (idempotent delete: the desired end state — gone — holds). */
48
52
  export declare function normalizeAssetRemoveResult(result: DeleteAssetResult): OperationError | null;
53
+ /**
54
+ * How an asset row is addressed in the EC-155 reference index (EC-433):
55
+ * `{ type: 'asset' }`, never the document shape — so the delete confirmation's
56
+ * inbound pre-flight counts the SAME references `loadInboundAssetReferenceViews`
57
+ * (the detail surface's check) does.
58
+ */
59
+ export declare function assetReferenceTarget(ref: DocumentRef): ReferenceTarget;
49
60
  /**
50
61
  * Assemble a {@link CollectionViewSource} for the **assets rail** (EC-319) — the
51
62
  * portable list surface over the existing asset library store instead of a
@@ -11,21 +11,22 @@ const ASSETS_LOCALE_CONFIG = {
11
11
  locales: [ASSETS_LOCALE],
12
12
  };
13
13
  /**
14
- * The capability profile of the assets rail (EC-319). Hoisted to a module-level
15
- * constant (no prop/state dependency) so it's both the value the hook returns
16
- * AND independently unit-testable.
14
+ * The capability profile of the assets rail (EC-319, EC-433). Hoisted to a
15
+ * module-level constant (no prop/state dependency) so it's both the value the
16
+ * hook returns AND independently unit-testable.
17
17
  *
18
18
  * `create` is off — a new asset comes from an upload, never a blank draft.
19
- * `remove` is off DELIBERATELY, even though the store can safely delete one
20
- * (EC-202): the universal table's generic row/bulk delete runs through
21
- * `ReferenceConfirmDialog`, which enumerates inbound references as
22
- * `{ type: 'document' }` targets — the wrong index shape for an asset (indexed
23
- * as `{ type: 'asset' }`, see `loadInboundAssetReferenceViews`). Wiring
24
- * `remove: true` here would silently show "not referenced" for an asset that
25
- * IS referenced. Deletion instead stays on the asset detail surface
26
- * (`asset-detail.tsx`), which keeps calling the asset-specific reference check.
27
- * Nothing else applies to assets: no publish/versions/locales/inline-edit/bulk/
28
- * live-URL/hierarchy/import-export.
19
+ * `remove` + `bulk` are ON since EC-433: the list's row/bulk delete used to be
20
+ * off deliberately because the generic `ReferenceConfirmDialog` enumerated
21
+ * inbound references as `{ type: 'document' }` targets — the wrong index shape
22
+ * for an asset — which would have shown "not referenced" for an asset that IS
23
+ * referenced. That pre-flight now queries the shape THIS source declares via
24
+ * {@link assetReferenceTarget} (`{ type: 'asset' }`, the same lookup the
25
+ * detail surface runs), and the confirmed delete dispatches the SAME checked
26
+ * store delete the detail surface uses (`deleteAsset`: original + EC-400 thumb
27
+ * blobs, then the record — EC-420 lqips ride on the record). Nothing else
28
+ * applies to assets: no publish/versions/locales/inline-edit/live-URL/
29
+ * hierarchy/import-export.
29
30
  */
30
31
  export const ASSET_COLLECTION_CAPABILITIES = {
31
32
  publish: false,
@@ -33,8 +34,8 @@ export const ASSET_COLLECTION_CAPABILITIES = {
33
34
  locales: false,
34
35
  inlineEdit: false,
35
36
  create: false,
36
- remove: false,
37
- bulk: false,
37
+ remove: true,
38
+ bulk: true,
38
39
  liveUrl: false,
39
40
  hierarchy: false,
40
41
  importExport: false,
@@ -111,12 +112,32 @@ function createUnsupported() {
111
112
  throw new Error('assets are created by upload, not the assets rail');
112
113
  }
113
114
  /** Assets' `remove()` returns an `outcome`-discriminated {@link DeleteAssetResult}
114
- * (not the `{ ok }` shape users/redirects use, EC-327): only `'error'` is a
115
- * failure the shell surfaces via `lastError`. `'blocked'`/`'not-found'` are
116
- * unreached from this rail's UI (`capabilities.remove === false`) but handled
117
- * soundly regardless, matching the pre-EC-327 behaviour exactly. */
115
+ * (not the `{ ok }` shape users/redirects use, EC-327). `'error'` surfaces as-is;
116
+ * `'blocked'` (the unconfirmed usage gate fired — nothing was removed) surfaces
117
+ * as an explicit failure too (EC-433): a delete that left the asset in place
118
+ * must NEVER look like success. It is unreachable from this rail's UI — every
119
+ * delete path confirms through the reference dialog and dispatches with
120
+ * `confirmInUse` — but stays honest for any direct caller. `'not-found'` maps
121
+ * to success (idempotent delete: the desired end state — gone — holds). */
118
122
  export function normalizeAssetRemoveResult(result) {
119
- return result.outcome === 'error' ? { code: 'unknown', message: result.message } : null;
123
+ if (result.outcome === 'error')
124
+ return { code: 'unknown', message: result.message };
125
+ if (result.outcome === 'blocked') {
126
+ return {
127
+ code: 'unknown',
128
+ message: `Asset is referenced in ${result.usageCount} ${result.usageCount === 1 ? 'place' : 'places'} and was not deleted.`,
129
+ };
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * How an asset row is addressed in the EC-155 reference index (EC-433):
135
+ * `{ type: 'asset' }`, never the document shape — so the delete confirmation's
136
+ * inbound pre-flight counts the SAME references `loadInboundAssetReferenceViews`
137
+ * (the detail surface's check) does.
138
+ */
139
+ export function assetReferenceTarget(ref) {
140
+ return { type: 'asset', id: ref.id };
120
141
  }
121
142
  /**
122
143
  * Assemble a {@link CollectionViewSource} for the **assets rail** (EC-319) — the
@@ -149,10 +170,16 @@ export function useAssetsCollectionViewSource(projectId, schema) {
149
170
  }, [store.assets]);
150
171
  const validate = useCallback((doc) => validateDocument(registry, doc, { localeConfig: ASSETS_LOCALE_CONFIG }), [registry]);
151
172
  const save = useCallback((doc) => store.updateMetadata(doc.id, { meta: documentToAssetMetaPatch(schema, doc) }), [schema, store]);
152
- // Unreached from the UI (`capabilities.remove === false`); implemented soundly
153
- // (the same checked delete the store always runs) in case anything ever calls
154
- // it directly, rather than throwing.
173
+ // The unconfirmed delete (the store's usage gate still applies — a referenced
174
+ // asset comes back `blocked`, surfaced via `lastError`). The UI's delete paths
175
+ // never land here: row/bulk deletes confirm through the reference dialog and
176
+ // dispatch `bulkRemove` below.
155
177
  const remove = useCallback((ref) => store.remove(ref.id), [store]);
178
+ // The CONFIRMED delete the bulk/row path dispatches (EC-433): the reference
179
+ // dialog has already enumerated inbound usages (asset-shaped, see
180
+ // `assetReferenceTarget`), so this passes `confirmInUse` exactly like the
181
+ // detail surface's confirmed delete — same blob + thumb + cache handling.
182
+ const bulkRemove = useCallback((ref) => store.remove(ref.id, { confirmInUse: true }), [store]);
156
183
  return useRailViewSource({
157
184
  projectId,
158
185
  schema,
@@ -166,10 +193,15 @@ export function useAssetsCollectionViewSource(projectId, schema) {
166
193
  // resolve — and read-only follows the factory's default `form.readOnly` check.
167
194
  detailPath: (id) => studioPath.asset(id),
168
195
  listPath: studioPath.assets(),
196
+ // EC-433: the delete confirmation must enumerate ASSET-shaped inbound
197
+ // references — the document-shaped default would report "not referenced"
198
+ // for a referenced asset.
199
+ referenceTarget: assetReferenceTarget,
169
200
  validate,
170
201
  create: createUnsupported,
171
202
  save,
172
203
  remove,
204
+ bulkRemove,
173
205
  normalizeSaveResult: normalizeOkResult,
174
206
  normalizeRemoveResult: normalizeAssetRemoveResult,
175
207
  });
@@ -1 +1 @@
1
- {"version":3,"file":"assets-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/assets/assets-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { OperationError } from '@elytracms/core/operations'\nimport { studioPath } from '@elytracms/studio/router'\nimport type { DeleteAssetResult } from '@elytracms/studio/lib/assets'\nimport type { CollectionViewSource, CollectionViewCapabilities } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useAssets } from './assets-store'\nimport { SYSTEM_ASSET_FIELDS } from './asset-metadata'\nimport { normalizeOkResult, useRailViewSource } from '../rail-view-source'\n\n/** The assets rail is not localized in v1 (EC-301: asset metadata is non-localized). */\nconst ASSETS_LOCALE: Locale = 'en'\nconst ASSETS_LOCALE_CONFIG: LocaleConfig = {\n default: ASSETS_LOCALE,\n locales: [ASSETS_LOCALE],\n}\n\n/**\n * The capability profile of the assets rail (EC-319). Hoisted to a module-level\n * constant (no prop/state dependency) so it's both the value the hook returns\n * AND independently unit-testable.\n *\n * `create` is off — a new asset comes from an upload, never a blank draft.\n * `remove` is off DELIBERATELY, even though the store can safely delete one\n * (EC-202): the universal table's generic row/bulk delete runs through\n * `ReferenceConfirmDialog`, which enumerates inbound references as\n * `{ type: 'document' }` targets — the wrong index shape for an asset (indexed\n * as `{ type: 'asset' }`, see `loadInboundAssetReferenceViews`). Wiring\n * `remove: true` here would silently show \"not referenced\" for an asset that\n * IS referenced. Deletion instead stays on the asset detail surface\n * (`asset-detail.tsx`), which keeps calling the asset-specific reference check.\n * Nothing else applies to assets: no publish/versions/locales/inline-edit/bulk/\n * live-URL/hierarchy/import-export.\n */\nexport const ASSET_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false,\n remove: false,\n bulk: false,\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false,\n}\n\n/** Read one of the record's fixed, upload-managed columns by field name. */\nfunction systemFieldValue(record: AssetRecord, name: string): unknown {\n switch (name) {\n case 'filename':\n return record.filename\n case 'url':\n return record.url\n case 'contentType':\n return record.contentType\n case 'byteSize':\n return record.byteSize\n case 'width':\n return record.width\n case 'height':\n return record.height\n case 'storageKey':\n return record.storageKey\n case 'title':\n return record.title\n default:\n return undefined\n }\n}\n\n/**\n * Project a stored asset record into the lens's `values` shape (EC-319), reading\n * only the schema's DECLARED fields — the same \"schema is a lens\" projection the\n * redirects/users rails use. A system column (`filename`, `url`, dimensions, …)\n * reads straight off the record; everything else — the built-in `alt`, a\n * project's own `copyright`/`credit`, or an EC-311 additive field — round-trips\n * through the record's open `meta` bag (EC-301). `alt` is schema-driven but ALSO\n * has a legacy record column: the meta value wins, falling back to the column\n * (matches delivery's `meta.alt ?? record.alt` read and the image field's\n * options dialog).\n */\nexport function assetToDocument(collection: CollectionDef, record: AssetRecord): CmsDocument {\n const values: Record<string, unknown> = {}\n for (const field of collection.fields) {\n if (field.name === 'alt') {\n const metaAlt = typeof record.meta?.alt === 'string' ? record.meta.alt : undefined\n values.alt = metaAlt ?? record.alt\n continue\n }\n values[field.name] = SYSTEM_ASSET_FIELDS.has(field.name)\n ? systemFieldValue(record, field.name)\n : record.meta?.[field.name]\n }\n return { collection: collection.id, id: record.id, values }\n}\n\n/**\n * The inverse of {@link assetToDocument}: the schema-driven meta bag a save\n * writes back — every declared field OUTSIDE the system set (EC-301/EC-311),\n * keyed exactly as the record stores it. System columns are upload-managed and\n * never written here.\n */\nexport function documentToAssetMetaPatch(\n collection: CollectionDef,\n doc: CmsDocument,\n): Record<string, unknown> {\n const values = doc.values ?? {}\n const meta: Record<string, unknown> = {}\n for (const field of collection.fields) {\n if (SYSTEM_ASSET_FIELDS.has(field.name)) continue\n if (field.name in values) meta[field.name] = values[field.name]\n }\n return meta\n}\n\n/** `create` when `capabilities.create === false` — never reached from the UI\n * (list/detail never call it); an explicit throw rather than a silent no-op. */\nfunction createUnsupported(): DocumentRef {\n throw new Error('assets are created by upload, not the assets rail')\n}\n\n/** Assets' `remove()` returns an `outcome`-discriminated {@link DeleteAssetResult}\n * (not the `{ ok }` shape users/redirects use, EC-327): only `'error'` is a\n * failure the shell surfaces via `lastError`. `'blocked'`/`'not-found'` are\n * unreached from this rail's UI (`capabilities.remove === false`) but handled\n * soundly regardless, matching the pre-EC-327 behaviour exactly. */\nexport function normalizeAssetRemoveResult(result: DeleteAssetResult): OperationError | null {\n return result.outcome === 'error' ? { code: 'unknown', message: result.message } : null\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **assets rail** (EC-319) — the\n * portable list surface over the existing asset library store instead of a\n * bespoke listing. `schema` is the DEPLOYMENT-resolved `kind:'asset'` collection\n * (EC-311: a project's own declared fields, or a mount's `nav.assets({ fields })`\n * override merged over it — see `resolveAssetsSchema` in `app-views/default-modules.tsx`),\n * never a hardcoded engine default — so a project's `copyright`/`credit`/… fields\n * (and any EC-311 override) drive the list's columns/filters exactly as they\n * drive the image field's options dialog.\n *\n * Must be mounted under an `<AssetsProvider>` (for {@link useAssets}) — already\n * ambient at the project level (EC-195), so callers rarely mount their own.\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the asset-store\n * reads/writes, the capability profile, and the projections above.\n */\nexport function useAssetsCollectionViewSource(\n projectId: string,\n schema: CollectionDef,\n): CollectionViewSource {\n const store = useAssets()\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n\n const documents = useCallback(\n () => store.assets.map((record) => assetToDocument(schema, record)),\n [store.assets, schema],\n )\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record ? assetToDocument(schema, record) : undefined\n },\n [store.assets, schema],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record?.title ?? record?.filename ?? ref.id\n },\n [store.assets],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: ASSETS_LOCALE_CONFIG }),\n [registry],\n )\n const save = useCallback(\n (doc: CmsDocument) => store.updateMetadata(doc.id, { meta: documentToAssetMetaPatch(schema, doc) }),\n [schema, store],\n )\n // Unreached from the UI (`capabilities.remove === false`); implemented soundly\n // (the same checked delete the store always runs) in case anything ever calls\n // it directly, rather than throwing.\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities: ASSET_COLLECTION_CAPABILITIES,\n localeConfig: ASSETS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections`/`isFieldReadOnly` override: assets carry\n // no relation/asset fields of their own (v1) — nothing cross-collection to\n // resolve — and read-only follows the factory's default `form.readOnly` check.\n detailPath: (id: string) => studioPath.asset(id),\n listPath: studioPath.assets(),\n validate,\n create: createUnsupported,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeAssetRemoveResult,\n })\n}\n"]}
1
+ {"version":3,"file":"assets-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/assets/assets-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core'\nimport type { AssetRecord, ReferenceTarget } from '@elytracms/core/persistence'\nimport type { OperationError } from '@elytracms/core/operations'\nimport { studioPath } from '@elytracms/studio/router'\nimport type { DeleteAssetResult } from '@elytracms/studio/lib/assets'\nimport type { CollectionViewSource, CollectionViewCapabilities } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useAssets } from './assets-store'\nimport { SYSTEM_ASSET_FIELDS } from './asset-metadata'\nimport { normalizeOkResult, useRailViewSource } from '../rail-view-source'\n\n/** The assets rail is not localized in v1 (EC-301: asset metadata is non-localized). */\nconst ASSETS_LOCALE: Locale = 'en'\nconst ASSETS_LOCALE_CONFIG: LocaleConfig = {\n default: ASSETS_LOCALE,\n locales: [ASSETS_LOCALE],\n}\n\n/**\n * The capability profile of the assets rail (EC-319, EC-433). Hoisted to a\n * module-level constant (no prop/state dependency) so it's both the value the\n * hook returns AND independently unit-testable.\n *\n * `create` is off — a new asset comes from an upload, never a blank draft.\n * `remove` + `bulk` are ON since EC-433: the list's row/bulk delete used to be\n * off deliberately because the generic `ReferenceConfirmDialog` enumerated\n * inbound references as `{ type: 'document' }` targets — the wrong index shape\n * for an asset — which would have shown \"not referenced\" for an asset that IS\n * referenced. That pre-flight now queries the shape THIS source declares via\n * {@link assetReferenceTarget} (`{ type: 'asset' }`, the same lookup the\n * detail surface runs), and the confirmed delete dispatches the SAME checked\n * store delete the detail surface uses (`deleteAsset`: original + EC-400 thumb\n * blobs, then the record — EC-420 lqips ride on the record). Nothing else\n * applies to assets: no publish/versions/locales/inline-edit/live-URL/\n * hierarchy/import-export.\n */\nexport const ASSET_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false,\n remove: true,\n bulk: true,\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false,\n}\n\n/** Read one of the record's fixed, upload-managed columns by field name. */\nfunction systemFieldValue(record: AssetRecord, name: string): unknown {\n switch (name) {\n case 'filename':\n return record.filename\n case 'url':\n return record.url\n case 'contentType':\n return record.contentType\n case 'byteSize':\n return record.byteSize\n case 'width':\n return record.width\n case 'height':\n return record.height\n case 'storageKey':\n return record.storageKey\n case 'title':\n return record.title\n default:\n return undefined\n }\n}\n\n/**\n * Project a stored asset record into the lens's `values` shape (EC-319), reading\n * only the schema's DECLARED fields — the same \"schema is a lens\" projection the\n * redirects/users rails use. A system column (`filename`, `url`, dimensions, …)\n * reads straight off the record; everything else — the built-in `alt`, a\n * project's own `copyright`/`credit`, or an EC-311 additive field — round-trips\n * through the record's open `meta` bag (EC-301). `alt` is schema-driven but ALSO\n * has a legacy record column: the meta value wins, falling back to the column\n * (matches delivery's `meta.alt ?? record.alt` read and the image field's\n * options dialog).\n */\nexport function assetToDocument(collection: CollectionDef, record: AssetRecord): CmsDocument {\n const values: Record<string, unknown> = {}\n for (const field of collection.fields) {\n if (field.name === 'alt') {\n const metaAlt = typeof record.meta?.alt === 'string' ? record.meta.alt : undefined\n values.alt = metaAlt ?? record.alt\n continue\n }\n values[field.name] = SYSTEM_ASSET_FIELDS.has(field.name)\n ? systemFieldValue(record, field.name)\n : record.meta?.[field.name]\n }\n return { collection: collection.id, id: record.id, values }\n}\n\n/**\n * The inverse of {@link assetToDocument}: the schema-driven meta bag a save\n * writes back — every declared field OUTSIDE the system set (EC-301/EC-311),\n * keyed exactly as the record stores it. System columns are upload-managed and\n * never written here.\n */\nexport function documentToAssetMetaPatch(\n collection: CollectionDef,\n doc: CmsDocument,\n): Record<string, unknown> {\n const values = doc.values ?? {}\n const meta: Record<string, unknown> = {}\n for (const field of collection.fields) {\n if (SYSTEM_ASSET_FIELDS.has(field.name)) continue\n if (field.name in values) meta[field.name] = values[field.name]\n }\n return meta\n}\n\n/** `create` when `capabilities.create === false` — never reached from the UI\n * (list/detail never call it); an explicit throw rather than a silent no-op. */\nfunction createUnsupported(): DocumentRef {\n throw new Error('assets are created by upload, not the assets rail')\n}\n\n/** Assets' `remove()` returns an `outcome`-discriminated {@link DeleteAssetResult}\n * (not the `{ ok }` shape users/redirects use, EC-327). `'error'` surfaces as-is;\n * `'blocked'` (the unconfirmed usage gate fired — nothing was removed) surfaces\n * as an explicit failure too (EC-433): a delete that left the asset in place\n * must NEVER look like success. It is unreachable from this rail's UI — every\n * delete path confirms through the reference dialog and dispatches with\n * `confirmInUse` — but stays honest for any direct caller. `'not-found'` maps\n * to success (idempotent delete: the desired end state — gone — holds). */\nexport function normalizeAssetRemoveResult(result: DeleteAssetResult): OperationError | null {\n if (result.outcome === 'error') return { code: 'unknown', message: result.message }\n if (result.outcome === 'blocked') {\n return {\n code: 'unknown',\n message: `Asset is referenced in ${result.usageCount} ${result.usageCount === 1 ? 'place' : 'places'} and was not deleted.`,\n }\n }\n return null\n}\n\n/**\n * How an asset row is addressed in the EC-155 reference index (EC-433):\n * `{ type: 'asset' }`, never the document shape — so the delete confirmation's\n * inbound pre-flight counts the SAME references `loadInboundAssetReferenceViews`\n * (the detail surface's check) does.\n */\nexport function assetReferenceTarget(ref: DocumentRef): ReferenceTarget {\n return { type: 'asset', id: ref.id }\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **assets rail** (EC-319) — the\n * portable list surface over the existing asset library store instead of a\n * bespoke listing. `schema` is the DEPLOYMENT-resolved `kind:'asset'` collection\n * (EC-311: a project's own declared fields, or a mount's `nav.assets({ fields })`\n * override merged over it — see `resolveAssetsSchema` in `app-views/default-modules.tsx`),\n * never a hardcoded engine default — so a project's `copyright`/`credit`/… fields\n * (and any EC-311 override) drive the list's columns/filters exactly as they\n * drive the image field's options dialog.\n *\n * Must be mounted under an `<AssetsProvider>` (for {@link useAssets}) — already\n * ambient at the project level (EC-195), so callers rarely mount their own.\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the asset-store\n * reads/writes, the capability profile, and the projections above.\n */\nexport function useAssetsCollectionViewSource(\n projectId: string,\n schema: CollectionDef,\n): CollectionViewSource {\n const store = useAssets()\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n\n const documents = useCallback(\n () => store.assets.map((record) => assetToDocument(schema, record)),\n [store.assets, schema],\n )\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record ? assetToDocument(schema, record) : undefined\n },\n [store.assets, schema],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record?.title ?? record?.filename ?? ref.id\n },\n [store.assets],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: ASSETS_LOCALE_CONFIG }),\n [registry],\n )\n const save = useCallback(\n (doc: CmsDocument) => store.updateMetadata(doc.id, { meta: documentToAssetMetaPatch(schema, doc) }),\n [schema, store],\n )\n // The unconfirmed delete (the store's usage gate still applies — a referenced\n // asset comes back `blocked`, surfaced via `lastError`). The UI's delete paths\n // never land here: row/bulk deletes confirm through the reference dialog and\n // dispatch `bulkRemove` below.\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n // The CONFIRMED delete the bulk/row path dispatches (EC-433): the reference\n // dialog has already enumerated inbound usages (asset-shaped, see\n // `assetReferenceTarget`), so this passes `confirmInUse` exactly like the\n // detail surface's confirmed delete — same blob + thumb + cache handling.\n const bulkRemove = useCallback(\n (ref: DocumentRef) => store.remove(ref.id, { confirmInUse: true }),\n [store],\n )\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities: ASSET_COLLECTION_CAPABILITIES,\n localeConfig: ASSETS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections`/`isFieldReadOnly` override: assets carry\n // no relation/asset fields of their own (v1) — nothing cross-collection to\n // resolve — and read-only follows the factory's default `form.readOnly` check.\n detailPath: (id: string) => studioPath.asset(id),\n listPath: studioPath.assets(),\n // EC-433: the delete confirmation must enumerate ASSET-shaped inbound\n // references — the document-shaped default would report \"not referenced\"\n // for a referenced asset.\n referenceTarget: assetReferenceTarget,\n validate,\n create: createUnsupported,\n save,\n remove,\n bulkRemove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeAssetRemoveResult,\n })\n}\n"]}
@@ -107,7 +107,12 @@ const CHROME_UI_CSS = `
107
107
  position: absolute !important; box-sizing: border-box !important;
108
108
  pointer-events: none !important; border-radius: 2px !important;
109
109
  }
110
- .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 1px dashed rgba(130,150,190,0.85) !important; }
110
+ /* Hover frame (EC-435 AD-3): accent-leaning and heavier than the old 1px
111
+ grey-blue dash (easy to overlook over imagery; live-tuned on light AND
112
+ busy sections — 1.5px collapses to 1px at 1x DPR, so 2px is the smallest
113
+ deterministic bump). Still clearly secondary to the SELECTED frame: the
114
+ same weight but dashed and translucent vs. solid opaque. */
115
+ .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 2px dashed rgba(91,140,255,0.8) !important; }
111
116
  .ec-overlay-frame[data-ec-selected] { z-index: 2 !important; border: 2px solid #5b8cff !important; }
112
117
  /* ── Prop highlight (EC-408 reverse: field focus → sub-part) ─────────────
113
118
  A quiet FILLED tint (no hard border) on the focused prop's element,
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-chrome-css.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-chrome-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAyC9D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAA;AAE3E;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG;;;;;CAK9B,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG;;CAE3B,CAAA;AAED;;;;;GAKG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAuIkB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDzD,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,aAAa,CAAA;AAC5F,CAAC","sourcesContent":["import { PRESERVE_NODE_ATTR } from '@elytracms/core/rich-text'\n\n/**\n * The canvas chrome STYLESHEET (EC-414 / AD-B) — the studio's complete in-frame\n * styling, hand-written. This is deliberately NOT a second Tailwind world: the\n * chrome inside the canvas iframe styles itself with `ec-*` classes, `data-ec-*`\n * attributes, and inline styles only, so the consumer's theme is the ONLY design\n * system in the canvas document and the canvas resolves exactly what delivery\n * resolves (AD-A).\n *\n * The chrome contract (binding on ALL in-frame studio UI — EC-404/405/408 build\n * on this):\n *\n * 1. Only `ec-*` class names / `data-ec-*` attributes / inline styles — never\n * studio utility classes, never studio React UI primitives.\n * 2. Chrome roots carry the containment recipe (`.ec-chrome`): `all: initial`\n * plus explicit font/color/line-height/color-scheme re-establishment, so\n * consumer element rules and inherited typography cannot restyle chrome.\n * NOTE (verified, MDN): `all: initial` does NOT stop custom-property\n * inheritance — the real protection is that chrome never CONSUMES a var()\n * name a consumer could define (this file uses no var() at all; the\n * contract test enforces it).\n * 3. Where a consumer ELEMENT selector could still match chrome descendants\n * (`div`, `span`, `button`, `input` rules in a site theme), chrome rules\n * use `!important` deliberately — a small, documented surface: the\n * selection/DnD indicators, the rich-text toolbar, and the locale chip.\n * The `.ec-chrome` recipe itself stays normal-strength on purpose: inline\n * styles are the top styling layer for chrome-owned looks (placeholders,\n * spacing handles) and stylesheet-`!important` would beat them.\n * 4. Escape hatch (documented only, NOT built): if chrome ever outgrows\n * hand-written CSS, the verified prefixed-bundle recipe is the upgrade\n * path — granular Tailwind imports `theme.css layer(theme) prefix(ec)` +\n * `utilities.css layer(utilities) prefix(ec) important source(none)`\n * compiled into an engine-provided entry (compiles clean on 4.3.2).\n */\n\n/** The two canvas stylesheet worlds (AD-C): `legacy` = the pre-EC-414 merged\n * canvas.css (studio design system + consumer theme in one build); `new` = the\n * consumer's standalone full-Tailwind theme entry, studio-free. */\nexport type CanvasWorld = 'legacy' | 'new'\n\n/**\n * Injected FIRST into the canvas head (before the `ec-app-css` link): a bare\n * layer STATEMENT that pins `ec-canvas-backdrop` to the very bottom of the\n * document's layer order (layer order is first-occurrence order). The new-world\n * backdrop rules live in this layer, so they lose to EVERYTHING the consumer\n * ships — `@layer base` body rules, unlayered rules, later layers — and only\n * paint when the consumer paints nothing. (An unlayered backdrop would ALWAYS\n * beat the consumer's layered `@layer base body { background }` — the exact\n * NK-004 mechanism this task retires.) Inert in the legacy world: the legacy\n * chrome CSS puts nothing in the layer.\n */\nexport const CANVAS_BACKDROP_LAYER_STATEMENT = '@layer ec-canvas-backdrop;'\n\n/**\n * NEW-world backdrop (AD-B item 4): the studio-utility body-class default is\n * retired; these neutral hand-written rules give the canvas a light, site-like\n * page when the consumer theme paints nothing, in the weakest possible cascade\n * position (see {@link CANVAS_BACKDROP_LAYER_STATEMENT}). Background sits on\n * BODY (not html) so a consumer `body { background }` keeps its normal\n * canvas-propagation behavior — short pages paint edge-to-edge exactly as the\n * delivery build does.\n */\nconst NEW_WORLD_BACKDROP_CSS = `\n @layer ec-canvas-backdrop {\n html { color-scheme: light; }\n body { background-color: #ffffff; }\n }\n`\n\n/**\n * LEGACY-world backdrop — today's rule, verbatim and unlayered (EC-389-era\n * behavior applies EXACTLY in the legacy world, per AD-C): the canvas backdrop\n * is deliberately always light/site-like, so the native form-control/scrollbar\n * theme is forced to match and a page with real inputs never shows dark native\n * chrome floating on a light page. The light page itself comes from the studio\n * body-class default (`DEFAULT_CANVAS_BODY_CLASS`) resolving against the merged\n * stylesheet's studio tokens.\n */\nconst LEGACY_BACKDROP_CSS = `\n html { color-scheme: light; }\n`\n\n/**\n * The chrome UI rules — identical in both worlds. Sections:\n * page frame, containment recipe, editable wrappers, selection/hover overlay\n * (EC-404), inline editing, rich-text toolbar, locale-fallback chips, drag &\n * drop indicators.\n */\nconst CHROME_UI_CSS = `\n /* ── Page frame ─────────────────────────────────────────────────────────\n The canvas gutter — unlayered on purpose (identical in both worlds, and\n it must beat a consumer body margin: the 16px frame is editor ergonomics,\n not page styling). */\n body { margin: 0; padding: 16px; }\n\n /* ── Containment recipe (chrome contract item 2) ────────────────────────\n Every chrome ROOT (toolbar, spacing handles, in-canvas placeholders)\n carries .ec-chrome: reset everything, then re-establish the chrome's own\n typography and color-scheme explicitly. Inherited custom properties pierce\n 'all: initial' (and even shadow DOM) — that is why chrome never consumes\n var() names (contract-tested), not something this recipe could fix.\n The :where(div, span, p) guard re-links descendant text to the chrome\n root's typography with class-level specificity, so a consumer's bare\n 'div, span { color: … }' element rules cannot restyle chrome text; chrome\n elements styled inline stay inline-styled (inline always wins here). */\n .ec-chrome {\n all: initial;\n display: block;\n box-sizing: border-box;\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: 13px;\n line-height: 1.5;\n color: rgb(30, 35, 45);\n color-scheme: light;\n }\n .ec-chrome *,\n .ec-chrome *::before,\n .ec-chrome *::after { box-sizing: border-box; }\n .ec-chrome :where(div, span, p) { color: inherit; font: inherit; }\n\n /* ── Editable wrappers ──────────────────────────────────────────────────\n Scoped to the per-node editable wrappers. The wrappers WRAP consumer\n content, so they are deliberately NOT contained (no .ec-chrome — they must\n stay layout-transparent). EC-404 RETIRED the in-document selection/hover\n outlines (data-selected / data-hovered): an outline paints in the\n element's own stacking order and any full-bleed background or higher\n z-index sibling paints over it — a structural occlusion, not a z-index bug.\n Selection + hover are now drawn by the topmost .ec-overlay layer below.\n The outline-offset stays for the editing outline (and DnD 'into' overrides\n it); its !important (contract item 3) keeps a site theme's element rules\n from hiding those states. */\n .ec-editable { outline-offset: -1px !important; cursor: default; }\n\n /* ── Selection/hover overlay (EC-404) ───────────────────────────────────\n The topmost editing layer, portaled into the iframe body as a sibling of\n the page composition and pinned to the maximum stacking level, so NOTHING\n the page paints can occlude the selection frame. The root carries the\n .ec-chrome containment recipe; geometry (left/top/width/height) is inline,\n everything visual lives here. Element-matching declarations carry\n !important (contract item 3): the frame boxes are <div>s and the controls\n are <button>s a site theme's element rules would otherwise restyle. Frames\n are click-through (pointer-events:none); only the bar re-enables events. */\n .ec-overlay {\n position: absolute !important; top: 0 !important; left: 0 !important;\n z-index: 2147483647 !important; pointer-events: none !important;\n width: 0 !important; height: 0 !important;\n }\n .ec-overlay-frame {\n position: absolute !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 2px !important;\n }\n .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 1px dashed rgba(130,150,190,0.85) !important; }\n .ec-overlay-frame[data-ec-selected] { z-index: 2 !important; border: 2px solid #5b8cff !important; }\n /* ── Prop highlight (EC-408 reverse: field focus → sub-part) ─────────────\n A quiet FILLED tint (no hard border) on the focused prop's element,\n deliberately distinct from the selection frame (solid border) and the\n hover frame (dashed border). Sits below both frames so their borders stay\n crisp; click-through like the frames. */\n .ec-overlay-prop {\n position: absolute !important; z-index: 1 !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 3px !important;\n background: rgba(91,140,255,0.16) !important;\n box-shadow: inset 0 0 0 1px rgba(91,140,255,0.45) !important;\n }\n .ec-overlay-bar {\n position: absolute !important; z-index: 3 !important; pointer-events: auto !important;\n display: flex !important; align-items: center !important; gap: 1px !important;\n height: 22px !important; max-width: 320px !important; padding: 0 2px 0 6px !important;\n border-radius: 6px !important; background: #5b8cff !important;\n box-shadow: 0 2px 8px rgba(8,12,24,0.35) !important;\n font: 600 11px/1 ui-sans-serif, system-ui, sans-serif !important;\n color: #ffffff !important; white-space: nowrap !important;\n }\n .ec-overlay-label {\n overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important;\n max-width: 180px !important; padding-right: 2px !important;\n font: inherit !important; color: #ffffff !important;\n }\n .ec-overlay-sep {\n width: 1px !important; height: 12px !important; margin: 0 3px !important;\n background: rgba(255,255,255,0.4) !important;\n }\n .ec-overlay-btn {\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 18px !important; padding: 0 !important; margin: 0 !important;\n border: 0 !important; border-radius: 4px !important; background: transparent !important;\n color: #ffffff !important; cursor: pointer !important; -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-btn:hover { background: rgba(255,255,255,0.22) !important; }\n .ec-overlay-btn:disabled { opacity: 0.4 !important; cursor: default !important; }\n\n /* ── Insertion affordances (EC-405) ─────────────────────────────────────\n A \"+\" button centered ON a block boundary (before/after edge, or the center\n of an empty container) — the geometry is inline (the anchor point), the\n translate here centers the button on it. Appears only on hover/selection\n (never during a drag or inline edit); pointer-events re-enabled so it is\n clickable while the overlay root stays click-through. Element-matching\n declarations !important (contract item 3): it is a <button> a site theme's\n element rules would otherwise restyle. */\n .ec-overlay-add {\n position: absolute !important; z-index: 4 !important; pointer-events: auto !important;\n transform: translate(-50%, -50%) !important;\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 20px !important; padding: 0 !important; margin: 0 !important;\n border: 1.5px solid #ffffff !important; border-radius: 999px !important;\n background: #5b8cff !important; color: #ffffff !important; cursor: pointer !important;\n box-shadow: 0 1px 5px rgba(8,12,24,0.4) !important;\n -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-add:hover { background: #3f74f5 !important; }\n\n /* ── Inline editing (EC-158 text, EC-159 rich text) ─────────────────────\n The rendered element itself becomes the editor — no overlay, no\n replacement input — so typography, alignment, and layout are\n pixel-identical between edit and render states. The wrapper keeps the\n selection outline; the host only gets a caret. pre-wrap applies to PLAIN\n text hosts only — rich-text markup must keep its rendered whitespace\n handling (no layout shift). */\n .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff !important; }\n .ec-editable [data-ec-edit] { outline: none; cursor: text; }\n .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }\n /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,\n images, embeds): visible and rendered, but not text-editable. */\n .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }\n\n /* ── Floating mark toolbar (EC-159), portaled into the iframe ───────────\n All visual declarations !important (contract item 3): the toolbar is\n stylesheet-styled chrome whose button/input elements a site theme's\n element rules would otherwise match. */\n .ec-rt-toolbar { color-scheme: dark !important; }\n .ec-rt-toolbar-row { display: flex !important; align-items: center !important; gap: 2px !important;\n padding: 3px !important; margin: 0 !important;\n border: 1px solid rgba(130,150,190,0.4) !important; border-radius: 8px !important;\n background: rgb(24,26,32) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; }\n .ec-rt-toolbar-btn { min-width: 26px !important; height: 24px !important; padding: 0 7px !important;\n border: 0 !important; border-radius: 5px !important;\n background: transparent !important; color: rgb(225,228,236) !important;\n font: 600 12px/1 ui-sans-serif, system-ui, sans-serif !important;\n cursor: pointer !important; }\n .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25) !important; }\n .ec-rt-toolbar-btn[data-active] { background: #5b8cff !important; color: white !important; }\n .ec-rt-toolbar-input { width: 200px !important; height: 24px !important; padding: 0 7px !important;\n border-radius: 5px !important;\n border: 1px solid rgba(130,150,190,0.4) !important; background: rgb(16,18,22) !important;\n color: rgb(225,228,236) !important;\n font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }\n\n /* ── Locale-fallback chips (EC-176) ─────────────────────────────────────\n A node whose bound content is currently served via locale fallback wears a\n small corner chip naming the locale it actually came from — the canvas\n never silently mixes locales. A pseudo-element cannot carry .ec-chrome, so\n every declaration is !important (contract item 3). */\n .ec-editable[data-ec-locale-fallback] { position: relative; }\n .ec-editable[data-ec-locale-fallback]::after {\n content: attr(data-ec-locale-fallback) !important;\n position: absolute !important; top: 2px !important; right: 2px !important;\n z-index: 5 !important; pointer-events: none !important;\n padding: 0 4px !important; border-radius: 4px !important;\n font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif !important;\n letter-spacing: 0.05em !important; text-transform: uppercase !important;\n color: #fbbf24 !important; background: rgba(180,83,9,0.25) !important;\n border: 1px solid rgba(245,158,11,0.45) !important;\n }\n\n /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────\n Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these\n classes only *show* the resolution. Rejection is explicit and visible\n during the drag — never a silent failed drop. Before/after bars follow the\n parent's flow axis (data-ec-drop-axis); left/right are the\n wrap-into-columns gesture (violet, paired highlight). Indicator\n declarations !important (contract item 3). */\n .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399 !important; outline-offset: -2px !important; background-color: rgba(52, 211, 153, 0.08) !important; }\n .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444 !important; outline-offset: -2px !important; background-color: rgba(239, 68, 68, 0.08) !important; cursor: not-allowed !important; }\n`\n\n/**\n * The complete chrome stylesheet for a world — the `ec-outline-css` element's\n * content (id kept from EC-389; the cascade position is pinned by test).\n */\nexport function canvasChromeCss(world: CanvasWorld): string {\n return (world === 'legacy' ? LEGACY_BACKDROP_CSS : NEW_WORLD_BACKDROP_CSS) + CHROME_UI_CSS\n}\n"]}
1
+ {"version":3,"file":"canvas-chrome-css.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-chrome-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAyC9D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAA;AAE3E;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG;;;;;CAK9B,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG;;CAE3B,CAAA;AAED;;;;;GAKG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCA4IkB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDzD,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,aAAa,CAAA;AAC5F,CAAC","sourcesContent":["import { PRESERVE_NODE_ATTR } from '@elytracms/core/rich-text'\n\n/**\n * The canvas chrome STYLESHEET (EC-414 / AD-B) — the studio's complete in-frame\n * styling, hand-written. This is deliberately NOT a second Tailwind world: the\n * chrome inside the canvas iframe styles itself with `ec-*` classes, `data-ec-*`\n * attributes, and inline styles only, so the consumer's theme is the ONLY design\n * system in the canvas document and the canvas resolves exactly what delivery\n * resolves (AD-A).\n *\n * The chrome contract (binding on ALL in-frame studio UI — EC-404/405/408 build\n * on this):\n *\n * 1. Only `ec-*` class names / `data-ec-*` attributes / inline styles — never\n * studio utility classes, never studio React UI primitives.\n * 2. Chrome roots carry the containment recipe (`.ec-chrome`): `all: initial`\n * plus explicit font/color/line-height/color-scheme re-establishment, so\n * consumer element rules and inherited typography cannot restyle chrome.\n * NOTE (verified, MDN): `all: initial` does NOT stop custom-property\n * inheritance — the real protection is that chrome never CONSUMES a var()\n * name a consumer could define (this file uses no var() at all; the\n * contract test enforces it).\n * 3. Where a consumer ELEMENT selector could still match chrome descendants\n * (`div`, `span`, `button`, `input` rules in a site theme), chrome rules\n * use `!important` deliberately — a small, documented surface: the\n * selection/DnD indicators, the rich-text toolbar, and the locale chip.\n * The `.ec-chrome` recipe itself stays normal-strength on purpose: inline\n * styles are the top styling layer for chrome-owned looks (placeholders,\n * spacing handles) and stylesheet-`!important` would beat them.\n * 4. Escape hatch (documented only, NOT built): if chrome ever outgrows\n * hand-written CSS, the verified prefixed-bundle recipe is the upgrade\n * path — granular Tailwind imports `theme.css layer(theme) prefix(ec)` +\n * `utilities.css layer(utilities) prefix(ec) important source(none)`\n * compiled into an engine-provided entry (compiles clean on 4.3.2).\n */\n\n/** The two canvas stylesheet worlds (AD-C): `legacy` = the pre-EC-414 merged\n * canvas.css (studio design system + consumer theme in one build); `new` = the\n * consumer's standalone full-Tailwind theme entry, studio-free. */\nexport type CanvasWorld = 'legacy' | 'new'\n\n/**\n * Injected FIRST into the canvas head (before the `ec-app-css` link): a bare\n * layer STATEMENT that pins `ec-canvas-backdrop` to the very bottom of the\n * document's layer order (layer order is first-occurrence order). The new-world\n * backdrop rules live in this layer, so they lose to EVERYTHING the consumer\n * ships — `@layer base` body rules, unlayered rules, later layers — and only\n * paint when the consumer paints nothing. (An unlayered backdrop would ALWAYS\n * beat the consumer's layered `@layer base body { background }` — the exact\n * NK-004 mechanism this task retires.) Inert in the legacy world: the legacy\n * chrome CSS puts nothing in the layer.\n */\nexport const CANVAS_BACKDROP_LAYER_STATEMENT = '@layer ec-canvas-backdrop;'\n\n/**\n * NEW-world backdrop (AD-B item 4): the studio-utility body-class default is\n * retired; these neutral hand-written rules give the canvas a light, site-like\n * page when the consumer theme paints nothing, in the weakest possible cascade\n * position (see {@link CANVAS_BACKDROP_LAYER_STATEMENT}). Background sits on\n * BODY (not html) so a consumer `body { background }` keeps its normal\n * canvas-propagation behavior — short pages paint edge-to-edge exactly as the\n * delivery build does.\n */\nconst NEW_WORLD_BACKDROP_CSS = `\n @layer ec-canvas-backdrop {\n html { color-scheme: light; }\n body { background-color: #ffffff; }\n }\n`\n\n/**\n * LEGACY-world backdrop — today's rule, verbatim and unlayered (EC-389-era\n * behavior applies EXACTLY in the legacy world, per AD-C): the canvas backdrop\n * is deliberately always light/site-like, so the native form-control/scrollbar\n * theme is forced to match and a page with real inputs never shows dark native\n * chrome floating on a light page. The light page itself comes from the studio\n * body-class default (`DEFAULT_CANVAS_BODY_CLASS`) resolving against the merged\n * stylesheet's studio tokens.\n */\nconst LEGACY_BACKDROP_CSS = `\n html { color-scheme: light; }\n`\n\n/**\n * The chrome UI rules — identical in both worlds. Sections:\n * page frame, containment recipe, editable wrappers, selection/hover overlay\n * (EC-404), inline editing, rich-text toolbar, locale-fallback chips, drag &\n * drop indicators.\n */\nconst CHROME_UI_CSS = `\n /* ── Page frame ─────────────────────────────────────────────────────────\n The canvas gutter — unlayered on purpose (identical in both worlds, and\n it must beat a consumer body margin: the 16px frame is editor ergonomics,\n not page styling). */\n body { margin: 0; padding: 16px; }\n\n /* ── Containment recipe (chrome contract item 2) ────────────────────────\n Every chrome ROOT (toolbar, spacing handles, in-canvas placeholders)\n carries .ec-chrome: reset everything, then re-establish the chrome's own\n typography and color-scheme explicitly. Inherited custom properties pierce\n 'all: initial' (and even shadow DOM) — that is why chrome never consumes\n var() names (contract-tested), not something this recipe could fix.\n The :where(div, span, p) guard re-links descendant text to the chrome\n root's typography with class-level specificity, so a consumer's bare\n 'div, span { color: … }' element rules cannot restyle chrome text; chrome\n elements styled inline stay inline-styled (inline always wins here). */\n .ec-chrome {\n all: initial;\n display: block;\n box-sizing: border-box;\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: 13px;\n line-height: 1.5;\n color: rgb(30, 35, 45);\n color-scheme: light;\n }\n .ec-chrome *,\n .ec-chrome *::before,\n .ec-chrome *::after { box-sizing: border-box; }\n .ec-chrome :where(div, span, p) { color: inherit; font: inherit; }\n\n /* ── Editable wrappers ──────────────────────────────────────────────────\n Scoped to the per-node editable wrappers. The wrappers WRAP consumer\n content, so they are deliberately NOT contained (no .ec-chrome — they must\n stay layout-transparent). EC-404 RETIRED the in-document selection/hover\n outlines (data-selected / data-hovered): an outline paints in the\n element's own stacking order and any full-bleed background or higher\n z-index sibling paints over it — a structural occlusion, not a z-index bug.\n Selection + hover are now drawn by the topmost .ec-overlay layer below.\n The outline-offset stays for the editing outline (and DnD 'into' overrides\n it); its !important (contract item 3) keeps a site theme's element rules\n from hiding those states. */\n .ec-editable { outline-offset: -1px !important; cursor: default; }\n\n /* ── Selection/hover overlay (EC-404) ───────────────────────────────────\n The topmost editing layer, portaled into the iframe body as a sibling of\n the page composition and pinned to the maximum stacking level, so NOTHING\n the page paints can occlude the selection frame. The root carries the\n .ec-chrome containment recipe; geometry (left/top/width/height) is inline,\n everything visual lives here. Element-matching declarations carry\n !important (contract item 3): the frame boxes are <div>s and the controls\n are <button>s a site theme's element rules would otherwise restyle. Frames\n are click-through (pointer-events:none); only the bar re-enables events. */\n .ec-overlay {\n position: absolute !important; top: 0 !important; left: 0 !important;\n z-index: 2147483647 !important; pointer-events: none !important;\n width: 0 !important; height: 0 !important;\n }\n .ec-overlay-frame {\n position: absolute !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 2px !important;\n }\n /* Hover frame (EC-435 AD-3): accent-leaning and heavier than the old 1px\n grey-blue dash (easy to overlook over imagery; live-tuned on light AND\n busy sections — 1.5px collapses to 1px at 1x DPR, so 2px is the smallest\n deterministic bump). Still clearly secondary to the SELECTED frame: the\n same weight but dashed and translucent vs. solid opaque. */\n .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 2px dashed rgba(91,140,255,0.8) !important; }\n .ec-overlay-frame[data-ec-selected] { z-index: 2 !important; border: 2px solid #5b8cff !important; }\n /* ── Prop highlight (EC-408 reverse: field focus → sub-part) ─────────────\n A quiet FILLED tint (no hard border) on the focused prop's element,\n deliberately distinct from the selection frame (solid border) and the\n hover frame (dashed border). Sits below both frames so their borders stay\n crisp; click-through like the frames. */\n .ec-overlay-prop {\n position: absolute !important; z-index: 1 !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 3px !important;\n background: rgba(91,140,255,0.16) !important;\n box-shadow: inset 0 0 0 1px rgba(91,140,255,0.45) !important;\n }\n .ec-overlay-bar {\n position: absolute !important; z-index: 3 !important; pointer-events: auto !important;\n display: flex !important; align-items: center !important; gap: 1px !important;\n height: 22px !important; max-width: 320px !important; padding: 0 2px 0 6px !important;\n border-radius: 6px !important; background: #5b8cff !important;\n box-shadow: 0 2px 8px rgba(8,12,24,0.35) !important;\n font: 600 11px/1 ui-sans-serif, system-ui, sans-serif !important;\n color: #ffffff !important; white-space: nowrap !important;\n }\n .ec-overlay-label {\n overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important;\n max-width: 180px !important; padding-right: 2px !important;\n font: inherit !important; color: #ffffff !important;\n }\n .ec-overlay-sep {\n width: 1px !important; height: 12px !important; margin: 0 3px !important;\n background: rgba(255,255,255,0.4) !important;\n }\n .ec-overlay-btn {\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 18px !important; padding: 0 !important; margin: 0 !important;\n border: 0 !important; border-radius: 4px !important; background: transparent !important;\n color: #ffffff !important; cursor: pointer !important; -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-btn:hover { background: rgba(255,255,255,0.22) !important; }\n .ec-overlay-btn:disabled { opacity: 0.4 !important; cursor: default !important; }\n\n /* ── Insertion affordances (EC-405) ─────────────────────────────────────\n A \"+\" button centered ON a block boundary (before/after edge, or the center\n of an empty container) — the geometry is inline (the anchor point), the\n translate here centers the button on it. Appears only on hover/selection\n (never during a drag or inline edit); pointer-events re-enabled so it is\n clickable while the overlay root stays click-through. Element-matching\n declarations !important (contract item 3): it is a <button> a site theme's\n element rules would otherwise restyle. */\n .ec-overlay-add {\n position: absolute !important; z-index: 4 !important; pointer-events: auto !important;\n transform: translate(-50%, -50%) !important;\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 20px !important; padding: 0 !important; margin: 0 !important;\n border: 1.5px solid #ffffff !important; border-radius: 999px !important;\n background: #5b8cff !important; color: #ffffff !important; cursor: pointer !important;\n box-shadow: 0 1px 5px rgba(8,12,24,0.4) !important;\n -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-add:hover { background: #3f74f5 !important; }\n\n /* ── Inline editing (EC-158 text, EC-159 rich text) ─────────────────────\n The rendered element itself becomes the editor — no overlay, no\n replacement input — so typography, alignment, and layout are\n pixel-identical between edit and render states. The wrapper keeps the\n selection outline; the host only gets a caret. pre-wrap applies to PLAIN\n text hosts only — rich-text markup must keep its rendered whitespace\n handling (no layout shift). */\n .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff !important; }\n .ec-editable [data-ec-edit] { outline: none; cursor: text; }\n .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }\n /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,\n images, embeds): visible and rendered, but not text-editable. */\n .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }\n\n /* ── Floating mark toolbar (EC-159), portaled into the iframe ───────────\n All visual declarations !important (contract item 3): the toolbar is\n stylesheet-styled chrome whose button/input elements a site theme's\n element rules would otherwise match. */\n .ec-rt-toolbar { color-scheme: dark !important; }\n .ec-rt-toolbar-row { display: flex !important; align-items: center !important; gap: 2px !important;\n padding: 3px !important; margin: 0 !important;\n border: 1px solid rgba(130,150,190,0.4) !important; border-radius: 8px !important;\n background: rgb(24,26,32) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; }\n .ec-rt-toolbar-btn { min-width: 26px !important; height: 24px !important; padding: 0 7px !important;\n border: 0 !important; border-radius: 5px !important;\n background: transparent !important; color: rgb(225,228,236) !important;\n font: 600 12px/1 ui-sans-serif, system-ui, sans-serif !important;\n cursor: pointer !important; }\n .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25) !important; }\n .ec-rt-toolbar-btn[data-active] { background: #5b8cff !important; color: white !important; }\n .ec-rt-toolbar-input { width: 200px !important; height: 24px !important; padding: 0 7px !important;\n border-radius: 5px !important;\n border: 1px solid rgba(130,150,190,0.4) !important; background: rgb(16,18,22) !important;\n color: rgb(225,228,236) !important;\n font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }\n\n /* ── Locale-fallback chips (EC-176) ─────────────────────────────────────\n A node whose bound content is currently served via locale fallback wears a\n small corner chip naming the locale it actually came from — the canvas\n never silently mixes locales. A pseudo-element cannot carry .ec-chrome, so\n every declaration is !important (contract item 3). */\n .ec-editable[data-ec-locale-fallback] { position: relative; }\n .ec-editable[data-ec-locale-fallback]::after {\n content: attr(data-ec-locale-fallback) !important;\n position: absolute !important; top: 2px !important; right: 2px !important;\n z-index: 5 !important; pointer-events: none !important;\n padding: 0 4px !important; border-radius: 4px !important;\n font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif !important;\n letter-spacing: 0.05em !important; text-transform: uppercase !important;\n color: #fbbf24 !important; background: rgba(180,83,9,0.25) !important;\n border: 1px solid rgba(245,158,11,0.45) !important;\n }\n\n /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────\n Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these\n classes only *show* the resolution. Rejection is explicit and visible\n during the drag — never a silent failed drop. Before/after bars follow the\n parent's flow axis (data-ec-drop-axis); left/right are the\n wrap-into-columns gesture (violet, paired highlight). Indicator\n declarations !important (contract item 3). */\n .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399 !important; outline-offset: -2px !important; background-color: rgba(52, 211, 153, 0.08) !important; }\n .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444 !important; outline-offset: -2px !important; background-color: rgba(239, 68, 68, 0.08) !important; cursor: not-allowed !important; }\n`\n\n/**\n * The complete chrome stylesheet for a world — the `ec-outline-css` element's\n * content (id kept from EC-389; the cascade position is pinned by test).\n */\nexport function canvasChromeCss(world: CanvasWorld): string {\n return (world === 'legacy' ? LEGACY_BACKDROP_CSS : NEW_WORLD_BACKDROP_CSS) + CHROME_UI_CSS\n}\n"]}
@@ -1,22 +1 @@
1
- import type { ComponentNode } from '@elytracms/core/project-graph';
2
- import type { ComponentManifest, PropField } from '@elytracms/core/component-registry';
3
1
  export declare function PropertyInspector(): import("react").JSX.Element;
4
- /**
5
- * The prop rows the inspector renders for a node (EC-393). Manifest props MINUS:
6
- * layout-managed props (they render in the Layout section, EC-162); `blocks` props
7
- * (they are child regions shown in Slots, EC-190); and any prop hidden RIGHT NOW by
8
- * its `form.hidden` — static `true`, or a predicate over the SIBLING prop values.
9
- *
10
- * The predicate's context is the component instance's EFFECTIVE prop values
11
- * (`effectivePropValues` — static values with manifest defaults filled), the prop
12
- * analogue of the document form's active-locale draft values (`isFieldHidden` there).
13
- * So a conditional row shows/hides live as a sibling prop changes and unmounts
14
- * exactly like a document-form field. Pure (no store) so the behaviour is testable.
15
- */
16
- export declare function visibleInspectorProps(manifest: ComponentManifest, node: ComponentNode, layoutManaged: ReadonlySet<string>): [string, PropField][];
17
- declare function PropField({ node, name, spec }: {
18
- node: ComponentNode;
19
- name: string;
20
- spec: PropField;
21
- }): import("react").JSX.Element;
22
- export {};