@elytracms/studio 0.0.87 → 0.0.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/convex/asset-revalidate.test.ts +35 -42
- package/convex/delivery.ts +8 -11
- package/convex/document-revalidate.test.ts +175 -0
- package/convex/redirect-revalidate.test.ts +36 -36
- package/convex/revalidate.ts +18 -30
- package/convex/triggers.ts +47 -38
- package/convex/webhooks.ts +77 -62
- package/dist/components/account/self-account-view-source.js +2 -1
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/ai/ai-chat-panel.js +9 -2
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/app-views/default-modules.js +3 -3
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/assets/asset-grid.js +1 -1
- package/dist/components/assets/asset-grid.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.js +3 -2
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +11 -1
- package/dist/components/assets/assets-store.js +10 -0
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/focal-point.d.ts +0 -7
- package/dist/components/assets/focal-point.js +0 -10
- package/dist/components/assets/focal-point.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +12 -1
- package/dist/components/builder/canvas-render-context.js +20 -16
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/reuse-context.js +2 -2
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +6 -2
- package/dist/components/builder/reuse.js +12 -7
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/asset-field.d.ts +2 -1
- package/dist/components/cms/asset-field.js +27 -59
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker-core.d.ts +77 -40
- package/dist/components/cms/asset-picker-core.js +73 -46
- package/dist/components/cms/asset-picker-core.js.map +1 -1
- package/dist/components/cms/asset-picker.d.ts +48 -19
- package/dist/components/cms/asset-picker.js +69 -30
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +3 -7
- package/dist/components/cms/cell-edit.js +7 -25
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +3 -2
- package/dist/components/cms/cms-store.js +38 -47
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +14 -4
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +20 -21
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/duplicate-document.d.ts +3 -2
- package/dist/components/cms/duplicate-document.js +5 -5
- package/dist/components/cms/duplicate-document.js.map +1 -1
- package/dist/components/cms/editable-cell.js +3 -6
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.js +7 -6
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +27 -2
- package/dist/components/cms/gate-validation.js +19 -1
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/publish-clean.d.ts +13 -6
- package/dist/components/cms/publish-clean.js +14 -6
- package/dist/components/cms/publish-clean.js.map +1 -1
- package/dist/components/cms/rich-text/editor.js +2 -2
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/insert.d.ts +7 -0
- package/dist/components/cms/rich-text/insert.js +7 -0
- package/dist/components/cms/rich-text/insert.js.map +1 -1
- package/dist/components/cms/use-publish-controller.js +3 -2
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +1 -0
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +10 -4
- package/dist/components/components/transfer.js +11 -9
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/delivery-health-banner.d.ts +3 -2
- package/dist/components/delivery-health-banner.js +3 -2
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/nav-issues.d.ts +23 -3
- package/dist/components/nav-issues.js +38 -2
- package/dist/components/nav-issues.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.js +3 -2
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/section-nav.js +26 -2
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/users/users-collection-view-source.js +2 -1
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/lib/ai/apply-content.js +32 -28
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +10 -4
- package/dist/lib/ai/tools.js +12 -4
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.js +11 -7
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/types.d.ts +9 -5
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.js +10 -29
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/ai/vite.d.ts +22 -2
- package/dist/lib/ai/vite.js +12 -3
- package/dist/lib/ai/vite.js.map +1 -1
- package/dist/lib/cms/published-documents.d.ts +14 -3
- package/dist/lib/cms/published-documents.js +12 -3
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +4 -4
- package/dist/lib/cms-views/view-model.js +16 -13
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +22 -1
- package/dist/lib/data/collection-content.js +51 -14
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/delivery/instant-publish.d.ts +0 -3
- package/dist/lib/delivery/instant-publish.js +0 -1
- package/dist/lib/delivery/instant-publish.js.map +1 -1
- package/dist/lib/engine-version.d.ts +3 -3
- package/dist/lib/engine-version.js +3 -3
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +5 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +4 -0
- package/dist/lib/i18n/messages.en.js +6 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/locale/fallback.d.ts +5 -19
- package/dist/lib/locale/fallback.js +18 -39
- package/dist/lib/locale/fallback.js.map +1 -1
- package/dist/lib/locale/locale-store.js +2 -1
- package/dist/lib/locale/locale-store.js.map +1 -1
- package/dist/lib/operations/session-hydration.d.ts +24 -26
- package/dist/lib/operations/session-hydration.js +15 -53
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/operations/studio-operations.js +4 -6
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/backend-mode.d.ts +5 -25
- package/dist/lib/persistence/backend-mode.js +13 -30
- package/dist/lib/persistence/backend-mode.js.map +1 -1
- package/dist/lib/references/reference-model.d.ts +17 -3
- package/dist/lib/references/reference-model.js +17 -23
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.js +7 -0
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/workspace/config-issues.d.ts +6 -0
- package/dist/lib/workspace/config-issues.js +8 -4
- package/dist/lib/workspace/config-issues.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +1 -8
- package/dist/lib/workspace/index.js +1 -10
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/router/navigate-once.d.ts +19 -4
- package/dist/router/navigate-once.js +22 -5
- package/dist/router/navigate-once.js.map +1 -1
- package/package.json +3 -7
- package/dist/lib/webhooks/config.d.ts +0 -15
- package/dist/lib/webhooks/config.js +0 -65
- package/dist/lib/webhooks/config.js.map +0 -1
- package/dist/lib/webhooks/dispatcher.d.ts +0 -70
- package/dist/lib/webhooks/dispatcher.js +0 -61
- package/dist/lib/webhooks/dispatcher.js.map +0 -1
- package/dist/lib/webhooks/index.d.ts +0 -11
- package/dist/lib/webhooks/index.js +0 -10
- package/dist/lib/webhooks/index.js.map +0 -1
- package/dist/lib/webhooks/signing.d.ts +0 -15
- package/dist/lib/webhooks/signing.js +0 -24
- package/dist/lib/webhooks/signing.js.map +0 -1
- package/dist/lib/webhooks/tags.d.ts +0 -25
- package/dist/lib/webhooks/tags.js +0 -289
- package/dist/lib/webhooks/tags.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duplicate-document.js","sourceRoot":"","sources":["../../../src/components/cms/duplicate-document.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CmsDocument, CollectionDef, DocumentRef, FieldDef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\n\n/**\n * EC-442 — the duplicate-document derivation (pure core) + the source-composed\n * dispatch behind the list row `⋮` menu's and the detail-editor header's\n * **Duplicate** action.\n *\n * Semantics (ratified 2026-07-17):\n * - The duplicate copies the current DRAFT field values — `values` plus\n * `localized` across ALL locales plus `defaultLocale` — into a fresh document\n * id. It is created as a **draft, never auto-published** (\"live is\n * published-only\"): published-ness is the version pointer (EC-224), and\n * nothing here touches it.\n * - `alias` is deliberately NOT copied: an alias is a unique-within-collection\n * slot handle (EC-284) — code addresses THE document holding it, and a copied\n * alias would immediately surface as uniqueness drift. The duplicate is new\n * content, not the slot holder.\n * - **Slug strategy: uniquify, never clear.** Every slug field (a `text` field\n * named `slug` or opted in via `form.control: 'slug'` — the EC-315\n * convention) gets a deterministic `-copy` / `-copy-2` / … suffix walked past\n * every slug string the collection already holds (base values AND every\n * locale variant of every sibling). Rationale: a cleared slug lands the new\n * draft in an error-looking validation state, while a uniquified one reads as\n * what it is (\"about-copy\") and keeps the draft one review away from\n * publishable. A missing/duplicate slug stays publish-gating only — this\n * derivation never blocks a create.\n * - Composition (`blocks`) field values copy VERBATIM (deep clone): node ids\n * only need uniqueness within one composition value (`validateCompositionValue`\n * checks duplicates per value; the builder's fresh-id counter re-derives from\n * the open value via `maxNodeIdSeq`), so identical node ids across two\n * documents collide nowhere.\n * - References/relations copy as-is (the duplicate points at the same targets);\n * the EC-277 in-transaction reference derivation indexes the new document on\n * its first persisted write because it catches every writer.\n */\n\n/** The conventional slug field name (mirrors `field-control`'s slug auto-upgrade). */\nconst SLUG_FIELD_NAME = 'slug'\n/** The built-in control name that opts any `text` field into slug behaviour (EC-315). */\nconst SLUG_CONTROL = 'slug'\n\n/** The EC-315 slug-field convention: a `text` field named `slug`, or any `text`\n * field with `form.control: 'slug'` (same predicate as the studio slug control). */\nfunction isSlugField(field: FieldDef): boolean {\n return (\n field.type === 'text' && (field.form?.control === SLUG_CONTROL || field.name === SLUG_FIELD_NAME)\n )\n}\n\n/**\n * Every slug string the collection already holds for `fieldName` — the base\n * `values` slot AND every per-locale slot of every sibling (the original\n * included). A union pool across locales is deliberately conservative: a\n * candidate free in the pool can never collide with any sibling in any locale,\n * including through the EC-018 base-value fallback.\n */\nfunction slugPool(fieldName: string, siblings: readonly CmsDocument[]): Set<string> {\n const pool = new Set<string>()\n const add = (value: unknown) => {\n if (typeof value === 'string' && value.trim() !== '') pool.add(value.trim())\n }\n for (const doc of siblings) {\n add(doc.values?.[fieldName])\n for (const localeValues of Object.values(doc.localized ?? {})) add(localeValues[fieldName])\n }\n return pool\n}\n\n/**\n * The deterministic duplicate slug: `<slug>-copy`, then `<slug>-copy-2`,\n * `<slug>-copy-3`, … — the first candidate not in `taken`. Pure function of\n * `(slug, taken)`, so the SAME original string in the base slot and a locale\n * slot maps to the SAME uniquified string (the duplicate keeps the original's\n * internal base/locale relationship).\n */\nexport function uniquifySlug(slug: string, taken: ReadonlySet<string>): string {\n const base = `${slug}-copy`\n if (!taken.has(base)) return base\n let n = 2\n while (taken.has(`${base}-${n}`)) n += 1\n return `${base}-${n}`\n}\n\n/** Uniquify one stored slug slot in place; empty/non-string slots stay untouched\n * (a missing slug remains a publish-gating issue, never a create blocker). */\nfunction uniquifySlot(\n values: Record<string, unknown> | undefined,\n fieldName: string,\n pool: ReadonlySet<string>,\n): void {\n if (!values) return\n const raw = values[fieldName]\n if (typeof raw !== 'string' || raw.trim() === '') return\n values[fieldName] = uniquifySlug(raw.trim(), pool)\n}\n\nexport interface DeriveDuplicateInput {\n /** The document being duplicated — the CURRENT draft values (list row / editor buffer). */\n original: CmsDocument\n /** The freshly minted id for the duplicate (source-owned, via `source.create()`). */\n newId: string\n /** The collection schema — identifies the slug fields to uniquify. */\n schema: CollectionDef\n /** The collection's current rows (the original included) — the slug pool. */\n siblings: readonly CmsDocument[]\n}\n\n/**\n * Derive the duplicate document (pure): deep-copied `values` + `localized` (all\n * locales) + `defaultLocale` under the fresh id, `alias` dropped, every slug\n * field's stored slots uniquified against the collection's slug pool. See the\n * module doc for the ratified semantics each rule pins.\n */\nexport function deriveDuplicateDocument({\n original,\n newId,\n schema,\n siblings,\n}: DeriveDuplicateInput): CmsDocument {\n const duplicate: CmsDocument = {\n collection: original.collection,\n id: newId,\n // Deep clones: composition trees, rich text, nested objects/arrays must be\n // independent of the original (editing one must never mutate the other).\n values: structuredClone(original.values ?? {}),\n ...(original.localized !== undefined ? { localized: structuredClone(original.localized) } : {}),\n ...(original.defaultLocale !== undefined ? { defaultLocale: original.defaultLocale } : {}),\n // `alias` deliberately dropped (unique slot handle — see module doc).\n }\n for (const field of schema.fields) {\n if (!isSlugField(field)) continue\n const pool = slugPool(field.name, siblings)\n uniquifySlot(duplicate.values, field.name, pool)\n for (const localeValues of Object.values(duplicate.localized ?? {})) {\n uniquifySlot(localeValues, field.name, pool)\n }\n }\n return duplicate\n}\n\n/**\n * Duplicate `original` through a {@link CollectionViewSource} — the ONE dispatch\n * both surfaces (list row menu, detail-editor header) share. Composes the seam's\n * EXISTING write paths, never a parallel one (AD-17):\n *\n * 1. `source.create()` mints the fresh id + inserts the rail's blank record\n * (the same collision-free mint the New button uses);\n * 2. `source.save(derived)` lands the copied values under that id (the CMS\n * save is a replace-or-append upsert; a rail save persists its draft).\n *\n * The session store applies both optimistically, so the new row is visible\n * without a reload, and the persister mirrors them onto the operations facade\n * with the user's session — no new trust boundary. Callers gate on the\n * explicit `capabilities.duplicate` flag (the row-menu vocabulary / the header\n * gate) before dispatching — CMS collections only (owner call 2026-07-17;\n * every primitive rail declares `duplicate: false`).\n */\nexport function duplicateFromSource(\n source: CollectionViewSource,\n original: CmsDocument,\n): DocumentRef {\n // Snapshot the slug pool BEFORE create() inserts the blank record.\n const siblings = source.documents()\n const ref = source.create()\n source.save(deriveDuplicateDocument({ original, newId: ref.id, schema: source.schema, siblings }))\n return ref\n}\n"]}
|
|
1
|
+
{"version":3,"file":"duplicate-document.js","sourceRoot":"","sources":["../../../src/components/cms/duplicate-document.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CmsDocument, CollectionDef, DocumentRef, FieldDef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\n\n/**\n * EC-442 — the duplicate-document derivation (pure core) + the source-composed\n * dispatch behind the list row `⋮` menu's and the detail-editor header's\n * **Duplicate** action.\n *\n * Semantics (ratified 2026-07-17):\n * - The duplicate copies the current DRAFT field values — `values` plus\n * `localized` across ALL locales — into a fresh document id (EC-580 deleted the\n * per-document `defaultLocale` this used to copy). It is created as a **draft, never auto-published** (\"live is\n * published-only\"): published-ness is the version pointer (EC-224), and\n * nothing here touches it.\n * - `alias` is deliberately NOT copied: an alias is a unique-within-collection\n * slot handle (EC-284) — code addresses THE document holding it, and a copied\n * alias would immediately surface as uniqueness drift. The duplicate is new\n * content, not the slot holder.\n * - **Slug strategy: uniquify, never clear.** Every slug field (a `text` field\n * named `slug` or opted in via `form.control: 'slug'` — the EC-315\n * convention) gets a deterministic `-copy` / `-copy-2` / … suffix walked past\n * every slug string the collection already holds (base values AND every\n * locale variant of every sibling). Rationale: a cleared slug lands the new\n * draft in an error-looking validation state, while a uniquified one reads as\n * what it is (\"about-copy\") and keeps the draft one review away from\n * publishable. A missing/duplicate slug stays publish-gating only — this\n * derivation never blocks a create.\n * - Composition (`blocks`) field values copy VERBATIM (deep clone): node ids\n * only need uniqueness within one composition value (`validateCompositionValue`\n * checks duplicates per value; the builder's fresh-id counter re-derives from\n * the open value via `maxNodeIdSeq`), so identical node ids across two\n * documents collide nowhere.\n * - References/relations copy as-is (the duplicate points at the same targets);\n * the EC-277 in-transaction reference derivation indexes the new document on\n * its first persisted write because it catches every writer.\n */\n\n/** The conventional slug field name (mirrors `field-control`'s slug auto-upgrade). */\nconst SLUG_FIELD_NAME = 'slug'\n/** The built-in control name that opts any `text` field into slug behaviour (EC-315). */\nconst SLUG_CONTROL = 'slug'\n\n/** The EC-315 slug-field convention: a `text` field named `slug`, or any `text`\n * field with `form.control: 'slug'` (same predicate as the studio slug control). */\nfunction isSlugField(field: FieldDef): boolean {\n return (\n field.type === 'text' && (field.form?.control === SLUG_CONTROL || field.name === SLUG_FIELD_NAME)\n )\n}\n\n/**\n * Every slug string the collection already holds for `fieldName` — the base\n * `values` slot AND every per-locale slot of every sibling (the original\n * included). A union pool across locales is deliberately conservative: a\n * candidate free in the pool can never collide with any sibling in any locale,\n * including through the EC-018 base-value fallback.\n */\nfunction slugPool(fieldName: string, siblings: readonly CmsDocument[]): Set<string> {\n const pool = new Set<string>()\n const add = (value: unknown) => {\n if (typeof value === 'string' && value.trim() !== '') pool.add(value.trim())\n }\n for (const doc of siblings) {\n add(doc.values?.[fieldName])\n for (const localeValues of Object.values(doc.localized ?? {})) add(localeValues[fieldName])\n }\n return pool\n}\n\n/**\n * The deterministic duplicate slug: `<slug>-copy`, then `<slug>-copy-2`,\n * `<slug>-copy-3`, … — the first candidate not in `taken`. Pure function of\n * `(slug, taken)`, so the SAME original string in the base slot and a locale\n * slot maps to the SAME uniquified string (the duplicate keeps the original's\n * internal base/locale relationship).\n */\nexport function uniquifySlug(slug: string, taken: ReadonlySet<string>): string {\n const base = `${slug}-copy`\n if (!taken.has(base)) return base\n let n = 2\n while (taken.has(`${base}-${n}`)) n += 1\n return `${base}-${n}`\n}\n\n/** Uniquify one stored slug slot in place; empty/non-string slots stay untouched\n * (a missing slug remains a publish-gating issue, never a create blocker). */\nfunction uniquifySlot(\n values: Record<string, unknown> | undefined,\n fieldName: string,\n pool: ReadonlySet<string>,\n): void {\n if (!values) return\n const raw = values[fieldName]\n if (typeof raw !== 'string' || raw.trim() === '') return\n values[fieldName] = uniquifySlug(raw.trim(), pool)\n}\n\nexport interface DeriveDuplicateInput {\n /** The document being duplicated — the CURRENT draft values (list row / editor buffer). */\n original: CmsDocument\n /** The freshly minted id for the duplicate (source-owned, via `source.create()`). */\n newId: string\n /** The collection schema — identifies the slug fields to uniquify. */\n schema: CollectionDef\n /** The collection's current rows (the original included) — the slug pool. */\n siblings: readonly CmsDocument[]\n}\n\n/**\n * Derive the duplicate document (pure): deep-copied `values` + `localized` (all\n * locales) under the fresh id, `alias` dropped, every slug field's stored slots\n * uniquified against the collection's slug pool. (EC-580: there is no per-document\n * `defaultLocale` to copy — the project's config is the only default.) See the\n * module doc for the ratified semantics each rule pins.\n */\nexport function deriveDuplicateDocument({\n original,\n newId,\n schema,\n siblings,\n}: DeriveDuplicateInput): CmsDocument {\n const duplicate: CmsDocument = {\n collection: original.collection,\n id: newId,\n // Deep clones: composition trees, rich text, nested objects/arrays must be\n // independent of the original (editing one must never mutate the other).\n values: structuredClone(original.values ?? {}),\n ...(original.localized !== undefined ? { localized: structuredClone(original.localized) } : {}),\n // `alias` deliberately dropped (unique slot handle — see module doc).\n }\n for (const field of schema.fields) {\n if (!isSlugField(field)) continue\n const pool = slugPool(field.name, siblings)\n uniquifySlot(duplicate.values, field.name, pool)\n for (const localeValues of Object.values(duplicate.localized ?? {})) {\n uniquifySlot(localeValues, field.name, pool)\n }\n }\n return duplicate\n}\n\n/**\n * Duplicate `original` through a {@link CollectionViewSource} — the ONE dispatch\n * both surfaces (list row menu, detail-editor header) share. Composes the seam's\n * EXISTING write paths, never a parallel one (AD-17):\n *\n * 1. `source.create()` mints the fresh id + inserts the rail's blank record\n * (the same collision-free mint the New button uses);\n * 2. `source.save(derived)` lands the copied values under that id (the CMS\n * save is a replace-or-append upsert; a rail save persists its draft).\n *\n * The session store applies both optimistically, so the new row is visible\n * without a reload, and the persister mirrors them onto the operations facade\n * with the user's session — no new trust boundary. Callers gate on the\n * explicit `capabilities.duplicate` flag (the row-menu vocabulary / the header\n * gate) before dispatching — CMS collections only (owner call 2026-07-17;\n * every primitive rail declares `duplicate: false`).\n */\nexport function duplicateFromSource(\n source: CollectionViewSource,\n original: CmsDocument,\n): DocumentRef {\n // Snapshot the slug pool BEFORE create() inserts the blank record.\n const siblings = source.documents()\n const ref = source.create()\n source.save(deriveDuplicateDocument({ original, newId: ref.id, schema: source.schema, siblings }))\n return ref\n}\n"]}
|
|
@@ -5,7 +5,7 @@ import { Badge, Input, Row, Select, Text } from '@elytracms/studio/ui';
|
|
|
5
5
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
6
6
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
7
7
|
import { editCreatesVariant, resolveDisplayedValue } from '@elytracms/studio/lib/locale';
|
|
8
|
-
import {
|
|
8
|
+
import { assetPickerBinding } from './asset-picker-core.js';
|
|
9
9
|
import { CellValue, toRefArray } from './cell.js';
|
|
10
10
|
import { DateInput } from './date-input.js';
|
|
11
11
|
import { datePickerBounds } from './date-input-core.js';
|
|
@@ -173,13 +173,10 @@ function CellPicker({ field, value, onCommit, onCancel, }) {
|
|
|
173
173
|
}, onChange: (refs) => onCommit(field.multiple ? refs : refs[0], null) }));
|
|
174
174
|
}
|
|
175
175
|
if (field.type === 'asset') {
|
|
176
|
-
return (_jsx(AssetPicker, {
|
|
176
|
+
return (_jsx(AssetPicker, { open: true, onOpenChange: (open) => {
|
|
177
177
|
if (!open)
|
|
178
178
|
onCancel();
|
|
179
|
-
},
|
|
180
|
-
// EC-558: the SAME pick operation the detail editor commits. A re-pick keeps
|
|
181
|
-
// the placement's per-use sub-fields, and an append keeps every held entry's.
|
|
182
|
-
onChange: (picked) => onCommit(applyAssetSelection(value, picked, field.multiple, assetPickForm(field)), null) }));
|
|
179
|
+
}, ...assetPickerBinding(field, value, (next) => onCommit(next, null)) }));
|
|
183
180
|
}
|
|
184
181
|
return null;
|
|
185
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editable-cell.js","sourceRoot":"","sources":["../../../src/components/cms/editable-cell.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useRef, useState } from 'react'\nimport type { FocusEvent, KeyboardEvent, ReactNode } from 'react'\nimport { isFieldLocalized } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef, Locale, SelectFieldDef } from '@elytracms/core/cms-core'\nimport { Badge, Input, Row, Select, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { editCreatesVariant, resolveDisplayedValue } from '@elytracms/studio/lib/locale'\nimport { applyAssetSelection, assetPickForm, singleAssetIdOf } from '../assets/asset-selection'\nimport { CellValue, toRefArray } from './cell'\nimport { DateInput } from './date-input'\nimport { datePickerBounds } from './date-input-core'\nimport { cellEditKind, cellKey } from './cell-edit'\nimport { fieldLabel } from './cms-labels'\nimport type { AdvanceDirection } from './cell-edit'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { RelationPicker } from './relation-picker'\nimport { AssetPicker } from './asset-picker'\n\n/**\n * One editable table cell (EC-168, the Airtable-feel wedge of vision 3.2).\n *\n * Renders the field's `CellValue` until activated (click / Enter / F2), then\n * swaps to an inline editor (text, number, boolean, select, date) or opens the\n * existing relation/asset picker from the cell. Commits flow through the\n * table's cell-edit controller; rejections render as an error ring + message\n * on this cell — never a toast.\n */\nexport function EditableCell({\n collection,\n doc,\n field,\n editing,\n busy,\n errorMessage,\n onStartEdit,\n onCancel,\n onCommit,\n}: {\n collection: CollectionDef\n doc: CmsDocument\n field: FieldDef\n editing: boolean\n busy: boolean\n errorMessage: string | null\n onStartEdit: () => void\n onCancel: () => void\n onCommit: (value: unknown, advance: AdvanceDirection | null) => void\n}) {\n const source = useCollectionView()\n // Inline editing is a capability. EC-463: the gate used to sit only on the\n // Tab-advance list, so a rail WITHOUT it (redirects, users, the self-account\n // view) still rendered focusable, clickable, editor-opening cells whose commit\n // then hit \"no `inlineEditing.commitField` → close without dispatching\" — the\n // edit was accepted on screen and silently thrown away. A read-only rail now\n // really does yield no editable cells.\n const kind = source.capabilities.inlineEdit\n ? cellEditKind(field, source.isFieldReadOnly(field))\n : 'none'\n // The table's locale resolution (EC-176): the cell displays the AMBIENT\n // active locale (EC-018 fallback applied) and edits write that locale only.\n // Editing a fallback-shown value therefore creates the localized variant —\n // the fallback locale's value is never mutated.\n const locale = source.activeLocale\n const localized = isFieldLocalized(collection, field.name)\n const { value, fallback } = resolveDisplayedValue(\n collection,\n doc,\n field.name,\n locale,\n source.localeConfig,\n )\n const key = cellKey({ docId: doc.id, field: field.name })\n\n if (editing && kind === 'inline') {\n return (\n <CellEditorFrame\n locale={localized ? locale : null}\n createsLocalizedVariant={\n fallback !== null &&\n editCreatesVariant(collection, doc, field.name, locale, source.localeConfig)\n }\n cellKeyId={key}\n >\n <InlineCellEditor field={field} value={value} busy={busy} onCommit={onCommit} onCancel={onCancel} />\n </CellEditorFrame>\n )\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n // EC-463: Space too — it is half of the native activation contract the\n // `role=\"button\"` this cell now carries promises.\n if (event.key === 'Enter' || event.key === 'F2' || event.key === ' ') {\n event.preventDefault()\n onStartEdit()\n }\n }\n\n const editable = kind !== 'none'\n return (\n <div\n data-cell-key={key}\n data-testid={`cell-${key}`}\n tabIndex={editable ? 0 : undefined}\n // EC-463: an editable cell WAS an anonymous focusable `div` — the\n // accessibility tree listed it as a nameless \"generic, clickable\", so\n // neither a screen reader nor an agent editor could tell what activating it\n // would edit. It is an activation surface, so it says so and names itself.\n role={editable ? 'button' : undefined}\n aria-label={\n editable\n ? translate('cms.cell.editAria', {\n field: fieldLabel(field),\n title: source.titleOf({ collection: collection.id, id: doc.id }),\n })\n : undefined\n }\n // While a picker cell is editing, the dialog renders inside this div. React\n // bubbles portal events through the COMPONENT tree, so a click on the picker\n // (e.g. its ✕) would re-fire onStartEdit and reopen it — hence the `editing`\n // guard. ESC/click-outside dodge this (keydown / close-on-pointerdown).\n onClick={kind === 'none' || editing ? undefined : onStartEdit}\n onKeyDown={kind === 'none' || editing ? undefined : handleKeyDown}\n aria-invalid={errorMessage ? true : undefined}\n className={cn(\n '-mx-1.5 min-h-7 rounded-md px-1.5 py-0.5 outline-none',\n kind !== 'none' &&\n 'cursor-pointer transition-colors hover:bg-muted/70 focus-visible:ring-2 focus-visible:ring-ring/50',\n errorMessage && 'bg-destructive/5 ring-2 ring-inset ring-destructive/60',\n busy && 'opacity-60',\n )}\n >\n <CellValue collection={collection} doc={doc} field={field} />\n {errorMessage ? (\n <Text size=\"xs\" className=\"mt-0.5 text-destructive\" data-testid={`cell-error-${key}`}>\n {errorMessage}\n </Text>\n ) : null}\n {editing && kind === 'picker' ? (\n <CellPicker field={field} value={value} onCommit={onCommit} onCancel={onCancel} />\n ) : null}\n </div>\n )\n}\n\n/**\n * Editing chrome: marks the edited locale at the point of edit (EC-018), and\n * says so explicitly when committing will CREATE the active locale's variant\n * from a fallback-shown value (EC-176) — translation is a visible act.\n */\nfunction CellEditorFrame({\n locale,\n createsLocalizedVariant = false,\n cellKeyId,\n children,\n}: {\n locale: Locale | null\n /** True when the cell showed a fallback — committing creates the variant. */\n createsLocalizedVariant?: boolean\n cellKeyId: string\n children: ReactNode\n}) {\n return (\n <Row gap=\"1\" align=\"center\" data-cell-key={cellKeyId} className=\"-mx-1.5 min-w-[9rem]\">\n {locale ? (\n <Badge\n variant={createsLocalizedVariant ? 'info' : 'outline'}\n className=\"uppercase\"\n data-testid=\"cell-editing-locale\"\n title={\n createsLocalizedVariant\n ? translate('cms.cell.createsVariantTitle', { locale: locale.toUpperCase() })\n : undefined\n }\n >\n {locale}\n {createsLocalizedVariant ? (\n <span className=\"lowercase\"> · {translate('cms.cell.newVariant')}</span>\n ) : null}\n </Badge>\n ) : null}\n {children}\n </Row>\n )\n}\n\ntype CellDraft = string | boolean | string[]\n\n/**\n * The in-cell editor: draft → commit on Enter (advance down), Tab/Shift-Tab\n * (advance next/prev) or blur; Esc cancels without dispatching anything.\n */\nfunction InlineCellEditor({\n field,\n value,\n busy,\n onCommit,\n onCancel,\n}: {\n field: FieldDef\n value: unknown\n busy: boolean\n onCommit: (value: unknown, advance: AdvanceDirection | null) => void\n onCancel: () => void\n}) {\n const [draft, setDraft] = useState<CellDraft>(() => initialDraft(field, value))\n const draftRef = useRef(draft)\n draftRef.current = draft\n // EC-463: the ref moves SYNCHRONOUSLY with the control, not at the next\n // render. A control that only emits its final value on the settling keystroke\n // (the date control's Enter) would otherwise be committed one render stale —\n // the same event that produces the value also triggers `commit()` below.\n function updateDraft(next: CellDraft) {\n draftRef.current = next\n setDraft(next)\n }\n // Each editor instance settles exactly once (Tab keydown + the blur it\n // causes must not commit twice).\n const settledRef = useRef(false)\n\n function commit(advance: AdvanceDirection | null) {\n if (settledRef.current) return\n settledRef.current = true\n onCommit(draftValue(field, draftRef.current), advance)\n }\n\n function cancel() {\n if (settledRef.current) return\n settledRef.current = true\n onCancel()\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n if (event.key === 'Enter') {\n event.preventDefault()\n commit('down')\n } else if (event.key === 'Tab') {\n event.preventDefault()\n commit(event.shiftKey ? 'prev' : 'next')\n } else if (event.key === 'Escape') {\n event.preventDefault()\n event.stopPropagation()\n cancel()\n }\n }\n\n function handleBlur(event: FocusEvent<HTMLDivElement>) {\n // Focus left the editor entirely (e.g. a click elsewhere) → commit in place.\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) commit(null)\n }\n\n return (\n <div\n onKeyDown={handleKeyDown}\n onBlur={handleBlur}\n onClick={(event) => event.stopPropagation()}\n className=\"min-w-0 flex-1\"\n data-testid=\"cell-editor\"\n aria-busy={busy || undefined}\n >\n <CellEditorControl field={field} draft={draft} onDraft={updateDraft} />\n </div>\n )\n}\n\n/** The actual input for the field type (compact, table-density variants). */\nfunction CellEditorControl({\n field,\n draft,\n onDraft,\n}: {\n field: FieldDef\n draft: CellDraft\n onDraft: (draft: CellDraft) => void\n}) {\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-7 items-center gap-2 text-sm\">\n <input\n autoFocus\n type=\"checkbox\"\n checked={draft === true}\n onChange={(event) => onDraft(event.target.checked)}\n className=\"size-4 accent-primary\"\n data-testid=\"cell-editor-boolean\"\n />\n <Text as=\"span\" size=\"xs\" tone=\"muted\">\n {draft === true ? translate('common.true') : translate('common.false')}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n autoFocus\n type=\"number\"\n value={typeof draft === 'string' ? draft : ''}\n onChange={(event) => onDraft(event.target.value)}\n className=\"h-7 text-sm\"\n data-testid=\"cell-editor-number\"\n />\n )\n case 'date':\n // EC-463: the shared date control. The cell's own draft already commits on\n // Enter/Tab/blur, so what this adds here is the working insert/paste path\n // and the guard against a re-render clobbering half-typed segments.\n return (\n <DateInput\n autoFocus\n value={typeof draft === 'string' ? draft : ''}\n onChange={(stored) => onDraft(stored ? stored.slice(0, 10) : '')}\n className=\"h-7 text-sm\"\n testId=\"cell-editor-date\"\n {...datePickerBounds(field.validation)}\n />\n )\n case 'select':\n if (field.multiple) {\n return <MultiSelectControl field={field} draft={draft} onDraft={onDraft} />\n }\n return (\n <Select\n autoFocus\n value={typeof draft === 'string' ? draft : ''}\n onChange={(event) => onDraft(event.target.value)}\n className=\"h-7 py-0 text-sm\"\n data-testid=\"cell-editor-select\"\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n default:\n return (\n <Input\n autoFocus\n value={typeof draft === 'string' ? draft : ''}\n onChange={(event) => onDraft(event.target.value)}\n className=\"h-7 text-sm\"\n data-testid=\"cell-editor-text\"\n />\n )\n }\n}\n\nfunction MultiSelectControl({\n field,\n draft,\n onDraft,\n}: {\n field: SelectFieldDef\n draft: CellDraft\n onDraft: (draft: CellDraft) => void\n}) {\n const selected = Array.isArray(draft) ? draft : []\n return (\n <Row gap=\"2\" wrap data-testid=\"cell-editor-multiselect\">\n {field.options.map((opt, index) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-xs\">\n <input\n autoFocus={index === 0}\n type=\"checkbox\"\n checked={on}\n onChange={() =>\n onDraft(on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value])\n }\n className=\"size-3.5 accent-primary\"\n />\n {localizedLabel(opt.label, opt.value)}\n </label>\n )\n })}\n </Row>\n )\n}\n\n/** Relation/asset cells reuse the existing lightweight pickers, opened from the cell. */\nfunction CellPicker({\n field,\n value,\n onCommit,\n onCancel,\n}: {\n field: FieldDef\n value: unknown\n onCommit: (value: unknown, advance: AdvanceDirection | null) => void\n onCancel: () => void\n}) {\n if (field.type === 'relation') {\n return (\n <RelationPicker\n targetCollection={field.target}\n multiple={field.multiple}\n value={toRefArray(value)}\n open\n onOpenChange={(open) => {\n if (!open) onCancel()\n }}\n onChange={(refs) => onCommit(field.multiple ? refs : refs[0], null)}\n />\n )\n }\n if (field.type === 'asset') {\n return (\n <AssetPicker\n value={field.multiple === false ? singleAssetIdOf(value) : null}\n open\n onOpenChange={(open) => {\n if (!open) onCancel()\n }}\n // EC-558: the SAME pick operation the detail editor commits. A re-pick keeps\n // the placement's per-use sub-fields, and an append keeps every held entry's.\n onChange={(picked) =>\n onCommit(applyAssetSelection(value, picked, field.multiple, assetPickForm(field)), null)\n }\n />\n )\n }\n return null\n}\n\n// --- Draft <-> stored value mapping (mirrors `FieldControl`'s conventions) -----\n\nfunction initialDraft(field: FieldDef, value: unknown): CellDraft {\n switch (field.type) {\n case 'boolean':\n return value === true\n case 'number':\n return typeof value === 'number' ? String(value) : ''\n case 'date':\n return typeof value === 'string' ? value.slice(0, 10) : ''\n case 'select':\n if (field.multiple) {\n return Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : []\n }\n return typeof value === 'string' ? value : ''\n default:\n return typeof value === 'string' ? value : ''\n }\n}\n\nfunction draftValue(field: FieldDef, draft: CellDraft): unknown {\n switch (field.type) {\n case 'boolean':\n return draft === true\n case 'number':\n return draft === '' ? undefined : Number(draft)\n case 'date':\n return draft === '' || typeof draft !== 'string' ? undefined : `${draft}T00:00:00.000Z`\n case 'select':\n if (field.multiple) return Array.isArray(draft) ? draft : []\n return draft === '' ? undefined : draft\n default:\n return draft === '' ? undefined : draft\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"editable-cell.js","sourceRoot":"","sources":["../../../src/components/cms/editable-cell.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAE3D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,UAAU,EACV,GAAG,EACH,KAAK,EACL,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,QAAQ,GAWT;IACC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,2EAA2E;IAC3E,6EAA6E;IAC7E,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,uCAAuC;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,UAAU;QACzC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC,CAAC,MAAM,CAAA;IACV,wEAAwE;IACxE,4EAA4E;IAC5E,2EAA2E;IAC3E,gDAAgD;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAA;IAClC,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IAC1D,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,qBAAqB,CAC/C,UAAU,EACV,GAAG,EACH,KAAK,CAAC,IAAI,EACV,MAAM,EACN,MAAM,CAAC,YAAY,CACpB,CAAA;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IAEzD,IAAI,OAAO,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,CACL,KAAC,eAAe,IACd,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EACjC,uBAAuB,EACrB,QAAQ,KAAK,IAAI;gBACjB,kBAAkB,CAAC,UAAU,EAAE,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,EAE9E,SAAS,EAAE,GAAG,YAEd,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,GACpF,CACnB,CAAA;IACH,CAAC;IAED,SAAS,aAAa,CAAC,KAAoC;QACzD,uEAAuE;QACvE,kDAAkD;QAClD,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;YACrE,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,WAAW,EAAE,CAAA;QACf,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,KAAK,MAAM,CAAA;IAChC,OAAO,CACL,gCACiB,GAAG,iBACL,QAAQ,GAAG,EAAE,EAC1B,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;QAClC,kEAAkE;QAClE,sEAAsE;QACtE,4EAA4E;QAC5E,2EAA2E;QAC3E,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,gBAEnC,QAAQ;YACN,CAAC,CAAC,SAAS,CAAC,mBAAmB,EAAE;gBAC7B,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC;gBACxB,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;aACjE,CAAC;YACJ,CAAC,CAAC,SAAS;QAEf,4EAA4E;QAC5E,6EAA6E;QAC7E,6EAA6E;QAC7E,wEAAwE;QACxE,OAAO,EAAE,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAC7D,SAAS,EAAE,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,kBACnD,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAC7C,SAAS,EAAE,EAAE,CACX,uDAAuD,EACvD,IAAI,KAAK,MAAM;YACb,oGAAoG,EACtG,YAAY,IAAI,wDAAwD,EACxE,IAAI,IAAI,YAAY,CACrB,aAED,KAAC,SAAS,IAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAI,EAC5D,YAAY,CAAC,CAAC,CAAC,CACd,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,yBAAyB,iBAAc,cAAc,GAAG,EAAE,YACjF,YAAY,GACR,CACR,CAAC,CAAC,CAAC,IAAI,EACP,OAAO,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAC9B,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,CACnF,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,EACvB,MAAM,EACN,uBAAuB,GAAG,KAAK,EAC/B,SAAS,EACT,QAAQ,GAOT;IACC,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,mBAAgB,SAAS,EAAE,SAAS,EAAC,sBAAsB,aACnF,MAAM,CAAC,CAAC,CAAC,CACR,MAAC,KAAK,IACJ,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACrD,SAAS,EAAC,WAAW,iBACT,qBAAqB,EACjC,KAAK,EACH,uBAAuB;oBACrB,CAAC,CAAC,SAAS,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;oBAC7E,CAAC,CAAC,SAAS,aAGd,MAAM,EACN,uBAAuB,CAAC,CAAC,CAAC,CACzB,gBAAM,SAAS,EAAC,WAAW,yBAAK,SAAS,CAAC,qBAAqB,CAAC,IAAQ,CACzE,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAC,CAAC,CAAC,IAAI,EACP,QAAQ,IACL,CACP,CAAA;AACH,CAAC;AAID;;;GAGG;AACH,SAAS,gBAAgB,CAAC,EACxB,KAAK,EACL,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,QAAQ,GAOT;IACC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAY,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IAC/E,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9B,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;IACxB,wEAAwE;IACxE,8EAA8E;IAC9E,6EAA6E;IAC7E,yEAAyE;IACzE,SAAS,WAAW,CAAC,IAAe;QAClC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAA;QACvB,QAAQ,CAAC,IAAI,CAAC,CAAA;IAChB,CAAC;IACD,uEAAuE;IACvE,iCAAiC;IACjC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAEhC,SAAS,MAAM,CAAC,OAAgC;QAC9C,IAAI,UAAU,CAAC,OAAO;YAAE,OAAM;QAC9B,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;QACzB,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAA;IACxD,CAAC;IAED,SAAS,MAAM;QACb,IAAI,UAAU,CAAC,OAAO;YAAE,OAAM;QAC9B,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;QACzB,QAAQ,EAAE,CAAA;IACZ,CAAC;IAED,SAAS,aAAa,CAAC,KAAoC;QACzD,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YAC1B,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,MAAM,CAAC,MAAM,CAAC,CAAA;QAChB,CAAC;aAAM,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;YAC/B,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAC1C,CAAC;aAAM,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YAClC,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,KAAK,CAAC,eAAe,EAAE,CAAA;YACvB,MAAM,EAAE,CAAA;QACV,CAAC;IACH,CAAC;IAED,SAAS,UAAU,CAAC,KAAiC;QACnD,6EAA6E;QAC7E,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,aAA4B,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,CAAA;IACrF,CAAC;IAED,OAAO,CACL,cACE,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EAC3C,SAAS,EAAC,gBAAgB,iBACd,aAAa,eACd,IAAI,IAAI,SAAS,YAE5B,KAAC,iBAAiB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,GAAI,GACnE,CACP,CAAA;AACH,CAAC;AAED,6EAA6E;AAC7E,SAAS,iBAAiB,CAAC,EACzB,KAAK,EACL,KAAK,EACL,OAAO,GAKR;IACC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,CACL,iBAAO,SAAS,EAAC,qCAAqC,aACpD,gBACE,SAAS,QACT,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,KAAK,KAAK,IAAI,EACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAClD,SAAS,EAAC,uBAAuB,iBACrB,qBAAqB,GACjC,EACF,KAAC,IAAI,IAAC,EAAE,EAAC,MAAM,EAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACnC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,GACjE,IACD,CACT,CAAA;QACH,KAAK,QAAQ;YACX,OAAO,CACL,KAAC,KAAK,IACJ,SAAS,QACT,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAChD,SAAS,EAAC,aAAa,iBACX,oBAAoB,GAChC,CACH,CAAA;QACH,KAAK,MAAM;YACT,2EAA2E;YAC3E,0EAA0E;YAC1E,oEAAoE;YACpE,OAAO,CACL,KAAC,SAAS,IACR,SAAS,QACT,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAChE,SAAS,EAAC,aAAa,EACvB,MAAM,EAAC,kBAAkB,KACrB,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,GACtC,CACH,CAAA;QACH,KAAK,QAAQ;YACX,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,OAAO,KAAC,kBAAkB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CAAA;YAC7E,CAAC;YACD,OAAO,CACL,MAAC,MAAM,IACL,SAAS,QACT,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAChD,SAAS,EAAC,kBAAkB,iBAChB,oBAAoB,aAEhC,iBAAQ,KAAK,EAAC,EAAE,uBAAW,EAC1B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAC1B,iBAAwB,KAAK,EAAE,GAAG,CAAC,KAAK,YACrC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAD1B,GAAG,CAAC,KAAK,CAEb,CACV,CAAC,IACK,CACV,CAAA;QACH;YACE,OAAO,CACL,KAAC,KAAK,IACJ,SAAS,QACT,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAChD,SAAS,EAAC,aAAa,iBACX,kBAAkB,GAC9B,CACH,CAAA;IACL,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,OAAO,GAKR;IACC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IAClD,OAAO,CACL,KAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,uBAAa,yBAAyB,YACpD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YAChC,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACvC,OAAO,CACL,iBAAuB,SAAS,EAAC,mCAAmC,aAClE,gBACE,SAAS,EAAE,KAAK,KAAK,CAAC,EACtB,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,EAAE,EACX,QAAQ,EAAE,GAAG,EAAE,CACb,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAElF,SAAS,EAAC,yBAAyB,GACnC,EACD,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,KAV3B,GAAG,CAAC,KAAK,CAWb,CACT,CAAA;QACH,CAAC,CAAC,GACE,CACP,CAAA;AACH,CAAC;AAED,yFAAyF;AACzF,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GAMT;IACC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC9B,OAAO,CACL,KAAC,cAAc,IACb,gBAAgB,EAAE,KAAK,CAAC,MAAM,EAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ,EACxB,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EACxB,IAAI,QACJ,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE;gBACrB,IAAI,CAAC,IAAI;oBAAE,QAAQ,EAAE,CAAA;YACvB,CAAC,EACD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GACnE,CACH,CAAA;IACH,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,CACL,KAAC,WAAW,IACV,IAAI,QACJ,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE;gBACrB,IAAI,CAAC,IAAI;oBAAE,QAAQ,EAAE,CAAA;YACvB,CAAC,KAKG,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GACpE,CACH,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,kFAAkF;AAElF,SAAS,YAAY,CAAC,KAAe,EAAE,KAAc;IACnD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,KAAK,KAAK,IAAI,CAAA;QACvB,KAAK,QAAQ;YACX,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACvD,KAAK,MAAM;YACT,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAC5D,KAAK,QAAQ;YACX,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAC5F,CAAC;YACD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC/C;YACE,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IACjD,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAe,EAAE,KAAgB;IACnD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,KAAK,KAAK,IAAI,CAAA;QACvB,KAAK,QAAQ;YACX,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACjD,KAAK,MAAM;YACT,OAAO,KAAK,KAAK,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,gBAAgB,CAAA;QACzF,KAAK,QAAQ;YACX,IAAI,KAAK,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;YAC5D,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;QACzC;YACE,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;IAC3C,CAAC;AACH,CAAC","sourcesContent":["import { useRef, useState } from 'react'\nimport type { FocusEvent, KeyboardEvent, ReactNode } from 'react'\nimport { isFieldLocalized } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef, Locale, SelectFieldDef } from '@elytracms/core/cms-core'\nimport { Badge, Input, Row, Select, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { editCreatesVariant, resolveDisplayedValue } from '@elytracms/studio/lib/locale'\nimport { assetPickerBinding } from './asset-picker-core'\nimport { CellValue, toRefArray } from './cell'\nimport { DateInput } from './date-input'\nimport { datePickerBounds } from './date-input-core'\nimport { cellEditKind, cellKey } from './cell-edit'\nimport { fieldLabel } from './cms-labels'\nimport type { AdvanceDirection } from './cell-edit'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { RelationPicker } from './relation-picker'\nimport { AssetPicker } from './asset-picker'\n\n/**\n * One editable table cell (EC-168, the Airtable-feel wedge of vision 3.2).\n *\n * Renders the field's `CellValue` until activated (click / Enter / F2), then\n * swaps to an inline editor (text, number, boolean, select, date) or opens the\n * existing relation/asset picker from the cell. Commits flow through the\n * table's cell-edit controller; rejections render as an error ring + message\n * on this cell — never a toast.\n */\nexport function EditableCell({\n collection,\n doc,\n field,\n editing,\n busy,\n errorMessage,\n onStartEdit,\n onCancel,\n onCommit,\n}: {\n collection: CollectionDef\n doc: CmsDocument\n field: FieldDef\n editing: boolean\n busy: boolean\n errorMessage: string | null\n onStartEdit: () => void\n onCancel: () => void\n onCommit: (value: unknown, advance: AdvanceDirection | null) => void\n}) {\n const source = useCollectionView()\n // Inline editing is a capability. EC-463: the gate used to sit only on the\n // Tab-advance list, so a rail WITHOUT it (redirects, users, the self-account\n // view) still rendered focusable, clickable, editor-opening cells whose commit\n // then hit \"no `inlineEditing.commitField` → close without dispatching\" — the\n // edit was accepted on screen and silently thrown away. A read-only rail now\n // really does yield no editable cells.\n const kind = source.capabilities.inlineEdit\n ? cellEditKind(field, source.isFieldReadOnly(field))\n : 'none'\n // The table's locale resolution (EC-176): the cell displays the AMBIENT\n // active locale (EC-018 fallback applied) and edits write that locale only.\n // Editing a fallback-shown value therefore creates the localized variant —\n // the fallback locale's value is never mutated.\n const locale = source.activeLocale\n const localized = isFieldLocalized(collection, field.name)\n const { value, fallback } = resolveDisplayedValue(\n collection,\n doc,\n field.name,\n locale,\n source.localeConfig,\n )\n const key = cellKey({ docId: doc.id, field: field.name })\n\n if (editing && kind === 'inline') {\n return (\n <CellEditorFrame\n locale={localized ? locale : null}\n createsLocalizedVariant={\n fallback !== null &&\n editCreatesVariant(collection, doc, field.name, locale, source.localeConfig)\n }\n cellKeyId={key}\n >\n <InlineCellEditor field={field} value={value} busy={busy} onCommit={onCommit} onCancel={onCancel} />\n </CellEditorFrame>\n )\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n // EC-463: Space too — it is half of the native activation contract the\n // `role=\"button\"` this cell now carries promises.\n if (event.key === 'Enter' || event.key === 'F2' || event.key === ' ') {\n event.preventDefault()\n onStartEdit()\n }\n }\n\n const editable = kind !== 'none'\n return (\n <div\n data-cell-key={key}\n data-testid={`cell-${key}`}\n tabIndex={editable ? 0 : undefined}\n // EC-463: an editable cell WAS an anonymous focusable `div` — the\n // accessibility tree listed it as a nameless \"generic, clickable\", so\n // neither a screen reader nor an agent editor could tell what activating it\n // would edit. It is an activation surface, so it says so and names itself.\n role={editable ? 'button' : undefined}\n aria-label={\n editable\n ? translate('cms.cell.editAria', {\n field: fieldLabel(field),\n title: source.titleOf({ collection: collection.id, id: doc.id }),\n })\n : undefined\n }\n // While a picker cell is editing, the dialog renders inside this div. React\n // bubbles portal events through the COMPONENT tree, so a click on the picker\n // (e.g. its ✕) would re-fire onStartEdit and reopen it — hence the `editing`\n // guard. ESC/click-outside dodge this (keydown / close-on-pointerdown).\n onClick={kind === 'none' || editing ? undefined : onStartEdit}\n onKeyDown={kind === 'none' || editing ? undefined : handleKeyDown}\n aria-invalid={errorMessage ? true : undefined}\n className={cn(\n '-mx-1.5 min-h-7 rounded-md px-1.5 py-0.5 outline-none',\n kind !== 'none' &&\n 'cursor-pointer transition-colors hover:bg-muted/70 focus-visible:ring-2 focus-visible:ring-ring/50',\n errorMessage && 'bg-destructive/5 ring-2 ring-inset ring-destructive/60',\n busy && 'opacity-60',\n )}\n >\n <CellValue collection={collection} doc={doc} field={field} />\n {errorMessage ? (\n <Text size=\"xs\" className=\"mt-0.5 text-destructive\" data-testid={`cell-error-${key}`}>\n {errorMessage}\n </Text>\n ) : null}\n {editing && kind === 'picker' ? (\n <CellPicker field={field} value={value} onCommit={onCommit} onCancel={onCancel} />\n ) : null}\n </div>\n )\n}\n\n/**\n * Editing chrome: marks the edited locale at the point of edit (EC-018), and\n * says so explicitly when committing will CREATE the active locale's variant\n * from a fallback-shown value (EC-176) — translation is a visible act.\n */\nfunction CellEditorFrame({\n locale,\n createsLocalizedVariant = false,\n cellKeyId,\n children,\n}: {\n locale: Locale | null\n /** True when the cell showed a fallback — committing creates the variant. */\n createsLocalizedVariant?: boolean\n cellKeyId: string\n children: ReactNode\n}) {\n return (\n <Row gap=\"1\" align=\"center\" data-cell-key={cellKeyId} className=\"-mx-1.5 min-w-[9rem]\">\n {locale ? (\n <Badge\n variant={createsLocalizedVariant ? 'info' : 'outline'}\n className=\"uppercase\"\n data-testid=\"cell-editing-locale\"\n title={\n createsLocalizedVariant\n ? translate('cms.cell.createsVariantTitle', { locale: locale.toUpperCase() })\n : undefined\n }\n >\n {locale}\n {createsLocalizedVariant ? (\n <span className=\"lowercase\"> · {translate('cms.cell.newVariant')}</span>\n ) : null}\n </Badge>\n ) : null}\n {children}\n </Row>\n )\n}\n\ntype CellDraft = string | boolean | string[]\n\n/**\n * The in-cell editor: draft → commit on Enter (advance down), Tab/Shift-Tab\n * (advance next/prev) or blur; Esc cancels without dispatching anything.\n */\nfunction InlineCellEditor({\n field,\n value,\n busy,\n onCommit,\n onCancel,\n}: {\n field: FieldDef\n value: unknown\n busy: boolean\n onCommit: (value: unknown, advance: AdvanceDirection | null) => void\n onCancel: () => void\n}) {\n const [draft, setDraft] = useState<CellDraft>(() => initialDraft(field, value))\n const draftRef = useRef(draft)\n draftRef.current = draft\n // EC-463: the ref moves SYNCHRONOUSLY with the control, not at the next\n // render. A control that only emits its final value on the settling keystroke\n // (the date control's Enter) would otherwise be committed one render stale —\n // the same event that produces the value also triggers `commit()` below.\n function updateDraft(next: CellDraft) {\n draftRef.current = next\n setDraft(next)\n }\n // Each editor instance settles exactly once (Tab keydown + the blur it\n // causes must not commit twice).\n const settledRef = useRef(false)\n\n function commit(advance: AdvanceDirection | null) {\n if (settledRef.current) return\n settledRef.current = true\n onCommit(draftValue(field, draftRef.current), advance)\n }\n\n function cancel() {\n if (settledRef.current) return\n settledRef.current = true\n onCancel()\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n if (event.key === 'Enter') {\n event.preventDefault()\n commit('down')\n } else if (event.key === 'Tab') {\n event.preventDefault()\n commit(event.shiftKey ? 'prev' : 'next')\n } else if (event.key === 'Escape') {\n event.preventDefault()\n event.stopPropagation()\n cancel()\n }\n }\n\n function handleBlur(event: FocusEvent<HTMLDivElement>) {\n // Focus left the editor entirely (e.g. a click elsewhere) → commit in place.\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) commit(null)\n }\n\n return (\n <div\n onKeyDown={handleKeyDown}\n onBlur={handleBlur}\n onClick={(event) => event.stopPropagation()}\n className=\"min-w-0 flex-1\"\n data-testid=\"cell-editor\"\n aria-busy={busy || undefined}\n >\n <CellEditorControl field={field} draft={draft} onDraft={updateDraft} />\n </div>\n )\n}\n\n/** The actual input for the field type (compact, table-density variants). */\nfunction CellEditorControl({\n field,\n draft,\n onDraft,\n}: {\n field: FieldDef\n draft: CellDraft\n onDraft: (draft: CellDraft) => void\n}) {\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-7 items-center gap-2 text-sm\">\n <input\n autoFocus\n type=\"checkbox\"\n checked={draft === true}\n onChange={(event) => onDraft(event.target.checked)}\n className=\"size-4 accent-primary\"\n data-testid=\"cell-editor-boolean\"\n />\n <Text as=\"span\" size=\"xs\" tone=\"muted\">\n {draft === true ? translate('common.true') : translate('common.false')}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n autoFocus\n type=\"number\"\n value={typeof draft === 'string' ? draft : ''}\n onChange={(event) => onDraft(event.target.value)}\n className=\"h-7 text-sm\"\n data-testid=\"cell-editor-number\"\n />\n )\n case 'date':\n // EC-463: the shared date control. The cell's own draft already commits on\n // Enter/Tab/blur, so what this adds here is the working insert/paste path\n // and the guard against a re-render clobbering half-typed segments.\n return (\n <DateInput\n autoFocus\n value={typeof draft === 'string' ? draft : ''}\n onChange={(stored) => onDraft(stored ? stored.slice(0, 10) : '')}\n className=\"h-7 text-sm\"\n testId=\"cell-editor-date\"\n {...datePickerBounds(field.validation)}\n />\n )\n case 'select':\n if (field.multiple) {\n return <MultiSelectControl field={field} draft={draft} onDraft={onDraft} />\n }\n return (\n <Select\n autoFocus\n value={typeof draft === 'string' ? draft : ''}\n onChange={(event) => onDraft(event.target.value)}\n className=\"h-7 py-0 text-sm\"\n data-testid=\"cell-editor-select\"\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n default:\n return (\n <Input\n autoFocus\n value={typeof draft === 'string' ? draft : ''}\n onChange={(event) => onDraft(event.target.value)}\n className=\"h-7 text-sm\"\n data-testid=\"cell-editor-text\"\n />\n )\n }\n}\n\nfunction MultiSelectControl({\n field,\n draft,\n onDraft,\n}: {\n field: SelectFieldDef\n draft: CellDraft\n onDraft: (draft: CellDraft) => void\n}) {\n const selected = Array.isArray(draft) ? draft : []\n return (\n <Row gap=\"2\" wrap data-testid=\"cell-editor-multiselect\">\n {field.options.map((opt, index) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-xs\">\n <input\n autoFocus={index === 0}\n type=\"checkbox\"\n checked={on}\n onChange={() =>\n onDraft(on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value])\n }\n className=\"size-3.5 accent-primary\"\n />\n {localizedLabel(opt.label, opt.value)}\n </label>\n )\n })}\n </Row>\n )\n}\n\n/** Relation/asset cells reuse the existing lightweight pickers, opened from the cell. */\nfunction CellPicker({\n field,\n value,\n onCommit,\n onCancel,\n}: {\n field: FieldDef\n value: unknown\n onCommit: (value: unknown, advance: AdvanceDirection | null) => void\n onCancel: () => void\n}) {\n if (field.type === 'relation') {\n return (\n <RelationPicker\n targetCollection={field.target}\n multiple={field.multiple}\n value={toRefArray(value)}\n open\n onOpenChange={(open) => {\n if (!open) onCancel()\n }}\n onChange={(refs) => onCommit(field.multiple ? refs : refs[0], null)}\n />\n )\n }\n if (field.type === 'asset') {\n return (\n <AssetPicker\n open\n onOpenChange={(open) => {\n if (!open) onCancel()\n }}\n // EC-588: the SAME picker configuration the detail editor uses, derived from\n // the field — its `accept` (the grid offered PDFs to an image field before),\n // `multiple` with the batch commit (an N-file upload attached one, EC-570 #1),\n // and the EC-558 pick operation that keeps each placement's sub-fields.\n {...assetPickerBinding(field, value, (next) => onCommit(next, null))}\n />\n )\n }\n return null\n}\n\n// --- Draft <-> stored value mapping (mirrors `FieldControl`'s conventions) -----\n\nfunction initialDraft(field: FieldDef, value: unknown): CellDraft {\n switch (field.type) {\n case 'boolean':\n return value === true\n case 'number':\n return typeof value === 'number' ? String(value) : ''\n case 'date':\n return typeof value === 'string' ? value.slice(0, 10) : ''\n case 'select':\n if (field.multiple) {\n return Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : []\n }\n return typeof value === 'string' ? value : ''\n default:\n return typeof value === 'string' ? value : ''\n }\n}\n\nfunction draftValue(field: FieldDef, draft: CellDraft): unknown {\n switch (field.type) {\n case 'boolean':\n return draft === true\n case 'number':\n return draft === '' ? undefined : Number(draft)\n case 'date':\n return draft === '' || typeof draft !== 'string' ? undefined : `${draft}T00:00:00.000Z`\n case 'select':\n if (field.multiple) return Array.isArray(draft) ? draft : []\n return draft === '' ? undefined : draft\n default:\n return draft === '' ? undefined : draft\n }\n}\n"]}
|
|
@@ -2,13 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { AlertTriangle, Link2, Pencil, Plus, RotateCw, X } from 'lucide-react';
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
|
|
5
|
-
import { BUILTIN_FIELD_CONTROLS, applySlugify, descendantIds,
|
|
5
|
+
import { BUILTIN_FIELD_CONTROLS, applySlugify, descendantIds, fieldViewLocale, findSlugCollision, parentRelationField, resolveSlugPath, resolveUrlSegments, slugSourceValue, valuesInLocale, } from '@elytracms/core/cms-core';
|
|
6
6
|
import { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
|
|
7
7
|
import { Button, Input, Row, RowItem, Select, Stack, Text, } from '@elytracms/studio/ui';
|
|
8
8
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
9
9
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
10
10
|
import { customizationIssueMessage } from '@elytracms/studio/lib/persona/labels';
|
|
11
|
-
import { activeLocaleValues } from '@elytracms/studio/lib/locale';
|
|
12
11
|
import { useCollectionView } from '../collection-view/collection-view-context.js';
|
|
13
12
|
import { fieldLabel } from './cms-labels.js';
|
|
14
13
|
import { useOptionalDocumentEditor } from './document-editor-context.js';
|
|
@@ -163,10 +162,12 @@ function SlugField({ field, value, onChange, readOnly, id, }) {
|
|
|
163
162
|
// German-derived slug preview for a field that is shared across locales (and,
|
|
164
163
|
// with the EC-461 derive latch, a locale switch alone could then WRITE it).
|
|
165
164
|
// A localized slug keeps per-locale derivation, which is the whole point of it.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
// EC-580: both halves are the one locale rule — `fieldViewLocale` names the view
|
|
166
|
+
// (the default locale's for a shared field, the project's ONLY default), and the
|
|
167
|
+
// source is read in that DISPLAYED view (`valuesInLocale`, with the fallback), so
|
|
168
|
+
// the slug derives from the title the editor actually shows.
|
|
169
|
+
const deriveLocale = fieldViewLocale(source.schema, field.name, locale, localeConfig);
|
|
170
|
+
const values = useMemo(() => (draft ? valuesInLocale(source.schema, draft, deriveLocale, localeConfig) : {}), [draft, deriveLocale, source.schema, localeConfig]);
|
|
170
171
|
const derived = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values));
|
|
171
172
|
// Track whether the slug was manually set — auto-derive stays active while the
|
|
172
173
|
// slug is empty or was last produced by auto-derivation (never overwrites a manual
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-control.js","sourceRoot":"","sources":["../../../src/components/cms/field-control.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC5D,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAOjF,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,OAAO,EACL,MAAM,EACN,KAAK,EACL,GAAG,EACH,OAAO,EACP,MAAM,EACN,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAA;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AAUzB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAA;IACrC,MAAM,OAAO,GAAG,KAAC,mBAAmB,OAAK,KAAK,GAAI,CAAA;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAA;IACzC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC3D,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CACL,KAAC,qBAAqB,IACpB,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,KAAC,kBAAkB,IAAC,OAAO,EAAE,SAAS,CAAC,2BAA2B,CAAC,GAAI,YAE9E,KAAC,MAAM,OAAK,KAAK,GAAI,GACC,CACzB,CAAA;IACH,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,CAAA;QACvE,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aAEZ,KAAC,kBAAkB,IAAC,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,GAAI,EACnE,OAAO,IACF,CACT,CAAA;QACH,CAAC;QACD,2EAA2E;QAC3E,gFAAgF;QAChF,IAAI,CAAE,sBAA4C,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACrE,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,KAAC,kBAAkB,IAAC,OAAO,EAAE,SAAS,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,CAAC,GAAI,EAC9E,OAAO,IACF,CACT,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,oFAAoF;AACpF,MAAM,eAAe,GAAG,MAAM,CAAA;AAC9B,2FAA2F;AAC3F,MAAM,YAAY,GAAG,MAAM,CAAA;AAE3B;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAe;IACpC,OAAO,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,KAAK,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAA;AAC1G,CAAC;AAED,SAAS,mBAAmB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAqB;IACtF,iFAAiF;IACjF,mFAAmF;IACnF,mFAAmF;IACnF,wDAAwD;IACxD,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAC,SAAS,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAI,CAAA;IAClG,CAAC;IACD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,CACL,iBAAO,SAAS,EAAC,qCAAqC,aACpD,gBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,KAAK,KAAK,IAAI,EACvB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAC3C,SAAS,EAAC,2CAA2C,GACrD,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC,GACtD,IACD,CACT,CAAA;QACH,KAAK,QAAQ;YACX,OAAO,CACL,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GACrF,CACH,CAAA;QACH,KAAK,MAAM;YACT,wEAAwE;YACxE,qEAAqE;YACrE,6BAA6B;YAC7B,OAAO,CACL,KAAC,SAAS,IACR,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,cAAc,KAAK,CAAC,IAAI,EAAE,KAC9B,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,GACtC,CACH,CAAA;QACH,KAAK,QAAQ;YACX,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAkB,CAAC,CAAC,CAAC,EAAE,CAAA;gBAChE,OAAO,CACL,KAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,kBACd,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;wBACzB,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;wBACvC,OAAO,CACL,iBAAuB,SAAS,EAAC,mCAAmC,aAClE,gBACE,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,EAAE,EACX,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,GAAG,EAAE,CACb,QAAQ,CACN,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CACxE,EAEH,SAAS,EAAC,uBAAuB,GACjC,EACD,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,KAZ3B,GAAG,CAAC,KAAK,CAab,CACT,CAAA;oBACH,CAAC,CAAC,GACE,CACP,CAAA;YACH,CAAC;YACD,4EAA4E;YAC5E,kEAAkE;YAClE,IAAI,KAAK,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC;gBACtC,OAAO,CACL,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAI,CACjG,CAAA;YACH,CAAC;YACD,OAAO,CACL,MAAC,MAAM,IACL,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,aAEtD,iBAAQ,KAAK,EAAC,EAAE,uBAAW,EAC1B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAC1B,iBAAwB,KAAK,EAAE,GAAG,CAAC,KAAK,YACrC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAD1B,GAAG,CAAC,KAAK,CAEb,CACV,CAAC,IACK,CACV,CAAA;QACH,KAAK,UAAU;YACb,OAAO,CACL,KAAC,cAAc,IACb,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,uEAAuE;gBACvE,6EAA6E;gBAC7E,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,GACxB,CACH,CAAA;QACH,KAAK,UAAU;YACb,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAAA;QAC9F,KAAK,OAAO;YACV,OAAO,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAAA;QAC3F,KAAK,QAAQ;YACX,OAAO,KAAC,WAAW,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAI,CAAA;QACpG;YACE,OAAO,CACL,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,GACtD,CACH,CAAA;IACL,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,SAAS,CAAC,EACjB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,EAAE,GAOH;IACC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,MAAM,GAAG,yBAAyB,EAAE,CAAA;IAC1C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAmC,CAAA;IAC5D,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,OAAO,KAAK,YAAY,CAAA;IACrD,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAA;IAClC,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAA;IAExC,gFAAgF;IAChF,uEAAuE;IACvE,mFAAmF;IACnF,4EAA4E;IAC5E,iFAAiF;IACjF,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAA;IACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,UAAU,KAAK,MAAM,CAAC,YAAY,CAAC,CAAA;IAEnF,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,CAAA;IAC3B,+EAA+E;IAC/E,8EAA8E;IAC9E,8EAA8E;IAC9E,8EAA8E;IAC9E,4EAA4E;IAC5E,gFAAgF;IAChF,MAAM,YAAY,GAChB,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;QACnD,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,qBAAqB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IAChD,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAC5D,CAAC,KAAK,EAAE,YAAY,CAAC,CACtB,CAAA;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAE5F,+EAA+E;IAC/E,mFAAmF;IACnF,sEAAsE;IACtE,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACjF,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IACpC,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAA;IAC9B,6EAA6E;IAC7E,yEAAyE;IACzE,0EAA0E;IAC1E,8EAA8E;IAC9E,6EAA6E;IAC7E,8EAA8E;IAC9E,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;IACzC,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACpC,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,QAAQ,GAAG,sBAAsB,CAAC;YACtC,OAAO;YACP,OAAO;YACP,cAAc,EAAE,iBAAiB,CAAC,OAAO;YACzC,SAAS,EAAE,cAAc,CAAC,OAAO;YACjC,cAAc;YACd,QAAQ;SACT,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,SAAS;YAAE,cAAc,CAAC,OAAO,GAAG,IAAI,CAAA;QACrD,IAAI,QAAQ,CAAC,KAAK;YAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAClD,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;IAEhD,8EAA8E;IAC9E,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAChD,kFAAkF;IAClF,kFAAkF;IAClF,iFAAiF;IACjF,oFAAoF;IACpF,+EAA+E;IAC/E,qFAAqF;IACrF,qFAAqF;IACrF,kFAAkF;IAClF,MAAM,KAAK,GACT,KAAK,IAAI,QAAQ;QACf,CAAC,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QACvF,CAAC,CAAC,SAAS,CAAA;IACf,oFAAoF;IACpF,iFAAiF;IACjF,mFAAmF;IACnF,sFAAsF;IACtF,MAAM,eAAe,GACnB,KAAK,IAAI,QAAQ;QACf,CAAC,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAC7F,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAC5B;QACH,CAAC,CAAC,EAAE,CAAA;IACR,MAAM,WAAW,GAAG,eAAe,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAE/D,4EAA4E;IAC5E,MAAM,qBAAqB,GAAG,OAAO,CAAgB,GAAG,EAAE;QACxD,IAAI,UAAU,EAAE,MAAM,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;QAC9B,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO,IAAI,CAAA;QAC/B,MAAM,GAAG,GAAG,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE;YAChE,UAAU,EAAE,MAAM,CAAC,MAAM;YACzB,MAAM;YACN,YAAY;SACb,CAAC,CAAA;QACF,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACrF,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAA;IAE9D,0DAA0D;IAC1D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,UAAU,EAAE,MAAM,CAAA;QACjC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACtB,aAAa,CAAC,KAAK,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;QAC9B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACnB,aAAa,CAAC,KAAK,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;aACxF,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YACX,IAAI,CAAC,SAAS;gBAAE,aAAa,CAAC,EAAE,KAAK,KAAK,CAAC,CAAA;QAC7C,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACV,IAAI,CAAC,SAAS;gBAAE,aAAa,CAAC,KAAK,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QACJ,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhD,gFAAgF;IAChF,0EAA0E;IAC1E,+EAA+E;IAC/E,mFAAmF;IACnF,iFAAiF;IACjF,kFAAkF;IAClF,kFAAkF;IAClF,kFAAkF;IAClF,8EAA8E;IAC9E,8BAA8B;IAC9B,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC;QACrF,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,UAAU,EAAE,MAAM;YAClB,CAAC,CAAC,UAAU;gBACV,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC;gBAC1C,CAAC,CAAC,SAAS;YACb,CAAC,CAAC,qBAAqB;gBACrB,CAAC,CAAC,SAAS,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;gBACrE,CAAC,CAAC,SAAS,CAAA;IAEjB,SAAS,WAAW,CAAC,IAAY;QAC/B,QAAQ,CAAC,IAAI,IAAI,SAAS,CAAC,CAAA;QAC3B,6EAA6E;QAC7E,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACvC,CAAC;IAED,SAAS,UAAU;QACjB,iBAAiB,CAAC,KAAK,CAAC,CAAA;QACxB,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,EAAE,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;QACzF,QAAQ,CAAC,IAAI,IAAI,SAAS,CAAC,CAAA;IAC7B,CAAC;IAED,gFAAgF;IAChF,+EAA+E;IAC/E,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,OAAO,CACL,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,GACtD,CACH,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,CACZ,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,OAAO,IAAI,SAAS,EACjC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAC5C,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,yDAAyD,CAAC,CAAC,CAAC,QAAQ,CAAC,GAC3F,CACH,CAAA;IACD,MAAM,gBAAgB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CACnC,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,EACd,OAAO,EAAE,UAAU,gBACP,SAAS,CAAC,2BAA2B,CAAC,EAClD,KAAK,EAAE,SAAS,CAAC,2BAA2B,CAAC,YAE7C,KAAC,QAAQ,KAAG,GACL,CACV,CAAC,CAAC,CAAC,IAAI,CAAA;IAER,uFAAuF;IACvF,kFAAkF;IAClF,iFAAiF;IACjF,gFAAgF;IAChF,sFAAsF;IACtF,MAAM,WAAW,GACf,KAAK,EAAE,IAAI;QACX,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAE1F,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACX,KAAK,CAAC,CAAC,CAAC,CACP,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,iBAAa,oBAAoB,aAC1D,eACE,SAAS,EAAC,iJAAiJ,EAC3J,KAAK,EAAE,WAAW,iBACN,aAAa,YAExB,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAC9B,oCAAkB,SAAS,EAAC,0BAA0B,kBAE/C,CACR,CAAC,CAAC,CAAC,CACF,8BACE,oCAAkB,SAAS,EAAC,0BAA0B,YACnD,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GACnC,EACN,WAAW,CAAC,CAAC,CAAC,CACb,KAAC,kBAAkB,IACjB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,MAAM,EAAE,SAAS,EAC5B,YAAY,EAAE,MAAM,CAAC,YAAY,GACjC,CACH,CAAC,CAAC,CAAC,IAAI,EACR,oCAAkB,SAAS,EAAC,0BAA0B,kBAE/C,IACN,CACJ,GACI,EACN,KAAK,EACL,gBAAgB,IACb,CACP,CAAC,CAAC,CAAC,CACF,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aACxB,KAAK,EACL,gBAAgB,IACb,CACP,EAEA,gBAAgB,CAAC,CAAC,CAAC,CAClB,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,iBAAa,gBAAgB,aACtD,KAAC,aAAa,IAAC,SAAS,EAAC,gCAAgC,wBAAe,EACxE,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,gBAAgB,GACZ,IACH,CACP,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,EACrB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GAMT;IACC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,MAAM,GAAG,yBAAyB,EAAE,CAAA;IAC1C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;IAElC,yEAAyE;IACzE,gFAAgF;IAChF,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,MAAM,UAAU,GAAG,OAAO,CAA0B,GAAG,EAAE;QACvD,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,UAAU,CAAC,EAAE;YAAE,OAAO,SAAS,CAAA;QACtE,IAAI,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,KAAK,KAAK,CAAC,IAAI;YAAE,OAAO,SAAS,CAAA;QACjF,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAA;QAC7F,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACzB,OAAO,GAAG,CAAA;IACZ,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAE9C,SAAS,KAAK,CAAC,IAAmB;QAChC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAA;IAC1D,CAAC;IAED,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACX,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACjB,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,YACX,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;oBACnB,4EAA4E;oBAC5E,0EAA0E;oBAC1E,uEAAuE;oBACvE,sEAAsE;oBACtE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;oBACjC,MAAM,KAAK,GAAG,CACZ,gBAAM,SAAS,EAAC,0CAA0C,aACxD,KAAC,KAAK,IAAC,SAAS,EAAC,yCAAyC,GAAG,EAC7D,eAAM,SAAS,EAAC,kBAAkB,YAAE,KAAK,GAAQ,IAC5C,CACR,CAAA;oBACD,OAAO,CACL,KAAC,OAAO,mBAEM,gBAAgB,EAC5B,OAAO,EACL,CAAC,QAAQ,CAAC,CAAC,CAAC,CACV,8BACG,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAC1B,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACF,SAAS,CAAC,qBAAqB,CAAC,EAC5C,KAAK,EAAE,SAAS,CAAC,qBAAqB,CAAC,EACvC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,YAE5B,KAAC,MAAM,KAAG,GACH,CACV,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACF,SAAS,CAAC,eAAe,CAAC,EACtC,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,EACjC,SAAS,EAAC,wBAAwB,EAClC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,YAEzD,KAAC,CAAC,KAAG,GACE,IACR,CACJ,CAAC,CAAC,CAAC,SAAS,YAGd,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,IAAI,IACH,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAC7E,KAAK,EAAE,SAAS,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,CAAC,EACrD,SAAS,EAAC,wDAAwD,YAEjE,KAAK,GACD,CACR,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,EAAE,EAAC,MAAM,EAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,YACtC,KAAK,GACD,CACR,IA3CI,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,CA4Cb,CACX,CAAA;gBACH,CAAC,CAAC,GACI,CACT,CAAC,CAAC,CAAC,IAAI,EACP,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CACpD,KAAC,GAAG,cACF,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,aAC9D,KAAC,IAAI,KAAG,OAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,IAC/E,GACL,CACP,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,cAAc,IACb,gBAAgB,EAAE,KAAK,CAAC,MAAM,EAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ,EACxB,KAAK,EAAE,IAAI,EACX,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,OAAO,EACrB,QAAQ,EAAE,KAAK,EACf,UAAU,EAAE,UAAU,GACtB,IACI,CACT,CAAA;AACH,CAAC","sourcesContent":["import { AlertTriangle, Link2, Pencil, Plus, RotateCw, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport type {\n DocumentRef,\n FieldDef,\n RelationFieldDef,\n UrlSegment,\n} from '@elytracms/core/cms-core'\nimport {\n BUILTIN_FIELD_CONTROLS,\n applySlugify,\n descendantIds,\n documentDefaultLocale,\n findSlugCollision,\n isFieldLocalized,\n parentRelationField,\n resolveSlugPath,\n resolveUrlSegments,\n slugSourceValue,\n} from '@elytracms/core/cms-core'\nimport type { SlugFieldConfig, SlugPathParts } from '@elytracms/core/cms-core'\nimport { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport {\n Button,\n Input,\n Row,\n RowItem,\n Select,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { customizationIssueMessage } from '@elytracms/studio/lib/persona/labels'\nimport { activeLocaleValues } from '@elytracms/studio/lib/locale'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { fieldLabel } from './cms-labels'\nimport { useOptionalDocumentEditor } from './document-editor-context'\nimport { toRefArray } from './cell'\nimport { RelationPicker } from './relation-picker'\nimport { RichTextEditor } from './rich-text'\nimport { DocumentUrlSegment } from './document-url-path'\nimport { DateInput } from './date-input'\nimport { datePickerBounds } from './date-input-core'\nimport { AssetField } from './asset-field'\nimport { SegmentedOptions } from './field-control-lib'\nimport { slugAutoDeriveDecision } from './slug-auto-derive'\nimport { engineOwnsUniqueness } from './slug-collision-report'\nimport { ObjectField } from './object-array-field'\nimport {\n FieldRendererBoundary,\n FieldRendererIssue,\n useFieldRenderers,\n} from './field-renderer'\n\ninterface FieldControlProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\n\n/**\n * The document-editor field control. Resolves a project's custom renderer by the\n * field's `form.control` (EC-238, customization seam EC-236) and falls back to the\n * built-in control for the field's datatype: a registered renderer renders inside\n * an error boundary (a throw → the built-in + a visible issue); a rejected\n * registration (non-component) shows the built-in with the issue above it; a field\n * naming a control with NO renderer and NO built-in name (a typo) shows the\n * built-in with a loud issue — never a silent fall-through; otherwise the built-in\n * renders alone.\n *\n * EC-330 split: the asset-field family lives in `asset-field.tsx`, the object /\n * repeater editors in `object-array-field.tsx`, and their pure companions\n * (reorder/summary/depth/table decisions, the `options` segmented control) in\n * `field-control-lib.tsx` — this file keeps the dispatcher plus the slug and\n * relation controls.\n */\nexport function FieldControl(props: FieldControlProps) {\n const renderers = useFieldRenderers()\n const builtIn = <BuiltInFieldControl {...props} />\n const control = props.field.form?.control\n const Custom = control ? renderers.get(control) : undefined\n if (Custom) {\n return (\n <FieldRendererBoundary\n fallback={builtIn}\n issue={<FieldRendererIssue message={translate('cms.field.rendererCrashed')} />}\n >\n <Custom {...props} />\n </FieldRendererBoundary>\n )\n }\n if (control) {\n const rejected = renderers.issues.find((issue) => issue.id === control)\n if (rejected) {\n return (\n <Stack gap=\"1\">\n {/* EC-478: the issue's catalog ref, resolved for the active locale. */}\n <FieldRendererIssue message={customizationIssueMessage(rejected)} />\n {builtIn}\n </Stack>\n )\n }\n // The field asks for a control with no registered renderer and no built-in\n // name — surface it (a typo'd `control` would otherwise read as a plain input).\n if (!(BUILTIN_FIELD_CONTROLS as readonly string[]).includes(control)) {\n return (\n <Stack gap=\"1\">\n <FieldRendererIssue message={translate('cms.field.noRenderer', { control })} />\n {builtIn}\n </Stack>\n )\n }\n }\n return builtIn\n}\n\n/** The conventional slug field name (mirrors `url-for-document`'s `SLUG_FIELD`). */\nconst SLUG_FIELD_NAME = 'slug'\n/** The built-in control name that opts any `text` field into the slug control (EC-315). */\nconst SLUG_CONTROL = 'slug'\n\n/**\n * EC-315: a `text` field is rendered by the smart slug control when it opts in via\n * `form.control: 'slug'`, OR (the default auto-upgrade) when it is simply named\n * `slug` — the long-standing convention `url-for-document`/hierarchy already key off.\n */\nfunction isSlugControl(field: FieldDef): boolean {\n return field.type === 'text' && (field.form?.control === SLUG_CONTROL || field.name === SLUG_FIELD_NAME)\n}\n\nfunction BuiltInFieldControl({ field, value, onChange, readOnly, id }: FieldControlProps) {\n // EC-315: the smart slug control (composed path, auto-slug, per-locale segments)\n // subsumes the old inline `slug` input + PathPreview. It degrades to a plain input\n // when the collection isn't routable (name-based auto-upgrade only) or no document\n // editor context is present (e.g. a table-cell picker).\n if (isSlugControl(field)) {\n return <SlugField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n }\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={value === true}\n disabled={readOnly}\n onChange={(e) => onChange(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {translate(value === true ? 'common.true' : 'common.false')}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={typeof value === 'number' ? value : ''}\n onChange={(e) => onChange(e.target.value === '' ? undefined : Number(e.target.value))}\n />\n )\n case 'date':\n // EC-463: never write a mid-entry segment state into the document — see\n // `date-input.tsx` (this control silently stored `0002-09-18` for an\n // editor typing 2026-09-18).\n return (\n <DateInput\n id={id}\n value={value}\n onChange={onChange}\n disabled={readOnly}\n testId={`field-date-${field.name}`}\n {...datePickerBounds(field.validation)}\n />\n )\n case 'select':\n if (field.multiple) {\n const selected = Array.isArray(value) ? (value as string[]) : []\n return (\n <Row gap=\"2\" wrap>\n {field.options.map((opt) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-sm\">\n <input\n type=\"checkbox\"\n checked={on}\n disabled={readOnly}\n onChange={() =>\n onChange(\n on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value],\n )\n }\n className=\"size-4 accent-primary\"\n />\n {localizedLabel(opt.label, opt.value)}\n </label>\n )\n })}\n </Row>\n )\n }\n // EC-295: the `options` control renders a small fixed enumeration as inline\n // segmented buttons instead of the dropdown (single-select only).\n if (field.form?.control === 'options') {\n return (\n <SegmentedOptions field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n )\n }\n return (\n <Select\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n case 'richText':\n return (\n <RichTextEditor\n value={value}\n onChange={onChange}\n readOnly={readOnly}\n id={id}\n allow={field.allow}\n // EC-436: the SAME resolved localized label the field row renders (the\n // EC-241 `fieldLabel` path) — titles the expand stage \"<label> — Rich text\".\n label={fieldLabel(field)}\n />\n )\n case 'relation':\n return <RelationField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'asset':\n return <AssetField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'object':\n return <ObjectField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n default:\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n}\n\n/**\n * EC-315 — the smart slug / URL-segment control. A `text` field named `slug` (or any\n * `text` field with `form.control: 'slug'`) renders here instead of a bare input:\n *\n * - **Composed path**: the route-derived URL, decomposed by {@link resolveSlugPath}\n * into read-only parent/context segments + the editable slug segment inline + the\n * locale prefix (non-default locales). Reuses the same `urlForDocument` the live\n * site uses, so the preview matches what visitors get. Draft-derived (updates as\n * you type); the live \"View live\" button in the header stays the published open.\n * - **Auto-slug**: while the slug is empty or was last produced by auto-derivation,\n * it live-derives from the source field (default: the collection's `titleField`).\n * A manual edit stops the auto-fill; the ↻ button always re-derives.\n * - **Per-locale**: reads/writes through the shared `value`/`onChange` seam, so a\n * `localized` slug edits the active locale's segment with no bespoke locale code.\n * - **Uniqueness**: an inline warning when the candidate collides with a sibling in\n * the same scope (same parent when hierarchical, same locale). A custom\n * `slug.unique` hook replaces the default sibling scan.\n *\n * Degrades to a plain input when there is no document editor context (a table-cell\n * picker) or when a name-based auto-upgrade lands on a non-routable collection.\n */\nfunction SlugField({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const slugConfig = field.slug as SlugFieldConfig | undefined\n const explicit = field.form?.control === SLUG_CONTROL\n const current = typeof value === 'string' ? value : ''\n const locale = source.activeLocale\n const localeConfig = source.localeConfig\n\n // 0.0.39 regression fix: resolve the deployment routes on EVERY render (a cheap\n // synchronous accessor), never freeze them at first paint. The earlier\n // `useMemo(() => resolveDeploymentRoutes(), [])` captured the EMPTY route set from\n // before the workspace/config finished loading — so the composed path never\n // appeared once routes arrived, and a stale set could paint a misleading prefix.\n const routes = resolveDeploymentRoutes()\n const routable = routes.some((r) => r.document?.collection === source.collectionId)\n\n const draft = editor?.draft\n // EC-463: a slug field that is NOT localized holds ONE value for every locale,\n // so its auto-slug must read ONE locale's source fields — the document's base\n // locale. Deriving from the ACTIVE locale made switching to the DE tab show a\n // German-derived slug preview for a field that is shared across locales (and,\n // with the EC-461 derive latch, a locale switch alone could then WRITE it).\n // A localized slug keeps per-locale derivation, which is the whole point of it.\n const deriveLocale =\n isFieldLocalized(source.schema, field.name) || !draft\n ? locale\n : documentDefaultLocale(draft, localeConfig)\n const values = useMemo<Record<string, unknown>>(\n () => (draft ? activeLocaleValues(draft, deriveLocale) : {}),\n [draft, deriveLocale],\n )\n const derived = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n\n // Track whether the slug was manually set — auto-derive stays active while the\n // slug is empty or was last produced by auto-derivation (never overwrites a manual\n // slug). Seeded true when the loaded document already carries a slug.\n const [manuallyEdited, setManuallyEdited] = useState(() => current.trim() !== '')\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n // EC-461: opening a document is not an edit. Auto-derive fires only once the\n // derived value has moved away from its mount state (the editor actually\n // changed a source field this session) — the old on-mount fill made every\n // slug-less document dirty on open, and the 1s autosave then WROTE it (in the\n // playground: the stale re-seeded baseline over the adapter's newer record —\n // the destructive half of the EC-461 loss family). Decision extracted pure in\n // `slug-auto-derive.ts`; the latch keeps derivation live after the first\n // divergence so title edits that return to the initial value still land.\n const initialDerivedRef = useRef(derived)\n const deriveLatchRef = useRef(false)\n useEffect(() => {\n const decision = slugAutoDeriveDecision({\n derived,\n current,\n initialDerived: initialDerivedRef.current,\n latchOpen: deriveLatchRef.current,\n manuallyEdited,\n readOnly,\n })\n if (decision.openLatch) deriveLatchRef.current = true\n if (decision.write) onChangeRef.current(derived)\n }, [derived, manuallyEdited, readOnly, current])\n\n // The composed path parts (routable collections with a document editor only).\n const siblings = draft ? source.documents() : []\n // Draft-derived (routes + locale only, no `frontendUrl`) — the composed path is a\n // read-only preview that updates as you type, mirroring the detail editor's draft\n // URL. The header's published-gated \"View live\" stays the sole open-live action,\n // so the draft/live distinction is preserved (an unpublished slug never links out).\n // EC-518: this preview therefore needs no `addressable` check — it renders the\n // segments as TEXT (the only link is an ancestor's EDITOR, via `DocumentUrlSegment`)\n // and never offers the live URL, so it cannot present an unreachable one as working;\n // the unaddressable finding itself surfaces as the field's inline issue (EC-492).\n const parts: SlugPathParts | undefined =\n draft && routable\n ? resolveSlugPath({ routes, localeConfig }, source.schema, draft, { siblings, locale })\n : undefined\n // The classified context segments (0.0.42, round 3) — the same `resolveUrlSegments`\n // the header URL row uses, so the slug prefix and the header path attribute each\n // segment identically. Everything before this document's own `current` slug is the\n // context prefix; its LAST segment is the immediate parent shown in the compact pill.\n const contextSegments: UrlSegment[] =\n draft && routable\n ? resolveUrlSegments({ routes, localeConfig }, source.schema, draft, { siblings, locale }).filter(\n (s) => s.kind !== 'current',\n )\n : []\n const lastContext = contextSegments[contextSegments.length - 1]\n\n // Default (sync) uniqueness — the first colliding sibling's title, or null.\n const defaultCollisionTitle = useMemo<string | null>(() => {\n if (slugConfig?.unique || !draft) return null\n const trimmed = current.trim()\n if (trimmed === '') return null\n const hit = findSlugCollision(trimmed, draft, source.documents(), {\n collection: source.schema,\n locale,\n localeConfig,\n })\n return hit ? source.titleOf({ collection: source.collectionId, id: hit.id }) : null\n }, [current, draft, slugConfig, locale, localeConfig, source])\n\n // Custom (possibly async) uniqueness hook — race-guarded.\n const [asyncTaken, setAsyncTaken] = useState(false)\n useEffect(() => {\n const unique = slugConfig?.unique\n if (!unique || !draft) {\n setAsyncTaken(false)\n return\n }\n const trimmed = current.trim()\n if (trimmed === '') {\n setAsyncTaken(false)\n return\n }\n let cancelled = false\n Promise.resolve(unique(trimmed, { document: draft, locale, siblings: source.documents() }))\n .then((ok) => {\n if (!cancelled) setAsyncTaken(ok === false)\n })\n .catch(() => {\n if (!cancelled) setAsyncTaken(false)\n })\n return () => {\n cancelled = true\n }\n }, [current, slugConfig, draft, locale, source])\n\n // EC-511 — one collision, one message. The engine's `validation.unique` finding\n // OWNS this field's uniqueness whenever it speaks: it is collection-wide,\n // localized, and it blocks publish, and it already renders inline in this very\n // column (the FieldRow `error` slot) plus the Issues pane. The control's hint then\n // fills only the gap the engine leaves — a collision that exists once the active\n // locale falls back, which `validation.unique` deliberately does not see, and any\n // field that declares no `validation.unique` at all. The two rules stay different\n // on purpose (AD-497b); only the double presentation goes. Full argument, and why\n // this matches on field+locale rather than on the colliding document's id, in\n // `slug-collision-report.ts`.\n const collisionMessage = engineOwnsUniqueness(editor?.issues ?? [], field.name, locale)\n ? undefined\n : slugConfig?.unique\n ? asyncTaken\n ? translate('cms.field.slug.takenGeneric')\n : undefined\n : defaultCollisionTitle\n ? translate('cms.field.slug.taken', { title: defaultCollisionTitle })\n : undefined\n\n function handleInput(next: string) {\n onChange(next || undefined)\n // Clearing re-enables auto-derive; any non-empty entry is a manual override.\n setManuallyEdited(next.trim() !== '')\n }\n\n function regenerate() {\n setManuallyEdited(false)\n const next = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n onChange(next || undefined)\n }\n\n // No document editor (a picker), or a name-based auto-upgrade on a non-routable\n // collection: keep the historical plain input, no slug chrome (no regression).\n if (!editor || (!explicit && !routable)) {\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n\n const input = (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n placeholder={derived || undefined}\n onChange={(e) => handleInput(e.target.value)}\n className={cn(parts ? 'h-7 min-w-[10ch] flex-1 bg-background font-mono text-xs' : 'flex-1')}\n />\n )\n const regenerateButton = !readOnly ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={regenerate}\n aria-label={translate('cms.field.slug.regenerate')}\n title={translate('cms.field.slug.regenerate')}\n >\n <RotateCw />\n </Button>\n ) : null\n\n // The compact, ellipsized read-only prefix (0.0.39 \"hybrid split\", round-3 clickable):\n // only the LAST context segment (`…/produkte/`) so a deep path can't overflow the\n // narrow Details sidebar — but that shown segment is now CLICKABLE with the same\n // navigation semantics as the header URL row (an ancestor DOCUMENT links to its\n // editor; a static route literal stays plain). The full composed path is the tooltip.\n const prefixTitle =\n parts?.path ??\n (contextSegments.length > 0 ? `/${contextSegments.map((s) => s.text).join('/')}/` : '/')\n\n return (\n <Stack gap=\"2\">\n {parts ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-composed-path\">\n <span\n className=\"inline-flex min-w-0 shrink-0 items-center rounded bg-muted/40 px-1.5 py-1 font-mono text-xs text-muted-foreground ring-1 ring-inset ring-border\"\n title={prefixTitle}\n data-testid=\"slug-prefix\"\n >\n {contextSegments.length === 0 ? (\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n ) : (\n <>\n <span aria-hidden className=\"text-muted-foreground/50\">\n {contextSegments.length > 1 ? '…/' : '/'}\n </span>\n {lastContext ? (\n <DocumentUrlSegment\n segment={lastContext}\n projectId={editor?.projectId}\n collectionId={source.collectionId}\n />\n ) : null}\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n </>\n )}\n </span>\n {input}\n {regenerateButton}\n </Row>\n ) : (\n <Row gap=\"1\" align=\"center\">\n {input}\n {regenerateButton}\n </Row>\n )}\n\n {collisionMessage ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-collision\">\n <AlertTriangle className=\"size-3.5 shrink-0 text-warning\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {collisionMessage}\n </Text>\n </Row>\n ) : null}\n </Stack>\n )\n}\n\nfunction RelationField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: RelationFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const [open, setOpen] = useState(false)\n const refs = toRefArray(value)\n const projectId = source.projectId\n\n // EC-218: for the editing document's `parent` self-relation, exclude the\n // document itself and its descendants from the picker — choosing one would make\n // the page its own ancestor (a cycle). Only applies to the parent field of a\n // hierarchical collection; ordinary relations (and other self-relations) are\n // unrestricted. validateDocument is the backstop where this context is absent.\n const excludeIds = useMemo<Set<string> | undefined>(() => {\n if (!editor || field.target !== editor.collection.id) return undefined\n if (parentRelationField(editor.collection)?.name !== field.name) return undefined\n const ids = descendantIds(editor.docRef.id, source.referenceCandidates(editor.collection.id))\n ids.add(editor.docRef.id)\n return ids\n }, [editor, field.target, field.name, source])\n\n function apply(next: DocumentRef[]) {\n onChange(field.multiple ? next : (next[0] ?? undefined))\n }\n\n return (\n <Stack gap=\"2\">\n {refs.length > 0 ? (\n <Stack gap=\"1\">\n {refs.map((ref, i) => {\n // EC-296: the relation title navigates to the referenced document (autosave\n // keeps the current edit safe). Without a projectId (no persistence, e.g.\n // fixtures) it stays plain text. EC-246c: full-width row, not a pill —\n // long titles wrap awkwardly in a pill and a narrow field crushes it.\n const title = source.titleOf(ref)\n const label = (\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <Link2 className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 truncate\">{title}</span>\n </span>\n )\n return (\n <RowItem\n key={`${ref.id}-${i}`}\n data-testid=\"relation-value\"\n actions={\n !readOnly ? (\n <>\n {field.multiple === false ? (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('cms.relation.change')}\n title={translate('cms.relation.change')}\n onClick={() => setOpen(true)}\n >\n <Pencil />\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('common.remove')}\n title={translate('common.remove')}\n className=\"hover:text-destructive\"\n onClick={() => apply(refs.filter((r) => r.id !== ref.id))}\n >\n <X />\n </Button>\n </>\n ) : undefined\n }\n >\n {projectId ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.cmsDocument(ref.collection, ref.id) }}\n title={translate('cms.relation.openTitle', { title })}\n className=\"text-sm font-medium hover:text-primary hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <Text as=\"span\" size=\"sm\" weight=\"medium\">\n {label}\n </Text>\n )}\n </RowItem>\n )\n })}\n </Stack>\n ) : null}\n {!readOnly && (refs.length === 0 || field.multiple) ? (\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Plus /> {refs.length === 0 ? translate('cms.relation.pick') : translate('common.add')}\n </Button>\n </Row>\n ) : null}\n <RelationPicker\n targetCollection={field.target}\n multiple={field.multiple}\n value={refs}\n open={open}\n onOpenChange={setOpen}\n onChange={apply}\n excludeIds={excludeIds}\n />\n </Stack>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"field-control.js","sourceRoot":"","sources":["../../../src/components/cms/field-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, Link2, Pencil, Plus, RotateCw, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport type {\n DocumentRef,\n FieldDef,\n RelationFieldDef,\n UrlSegment,\n} from '@elytracms/core/cms-core'\nimport {\n BUILTIN_FIELD_CONTROLS,\n applySlugify,\n descendantIds,\n fieldViewLocale,\n findSlugCollision,\n parentRelationField,\n resolveSlugPath,\n resolveUrlSegments,\n slugSourceValue,\n valuesInLocale,\n} from '@elytracms/core/cms-core'\nimport type { SlugFieldConfig, SlugPathParts } from '@elytracms/core/cms-core'\nimport { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport {\n Button,\n Input,\n Row,\n RowItem,\n Select,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { customizationIssueMessage } from '@elytracms/studio/lib/persona/labels'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { fieldLabel } from './cms-labels'\nimport { useOptionalDocumentEditor } from './document-editor-context'\nimport { toRefArray } from './cell'\nimport { RelationPicker } from './relation-picker'\nimport { RichTextEditor } from './rich-text'\nimport { DocumentUrlSegment } from './document-url-path'\nimport { DateInput } from './date-input'\nimport { datePickerBounds } from './date-input-core'\nimport { AssetField } from './asset-field'\nimport { SegmentedOptions } from './field-control-lib'\nimport { slugAutoDeriveDecision } from './slug-auto-derive'\nimport { engineOwnsUniqueness } from './slug-collision-report'\nimport { ObjectField } from './object-array-field'\nimport {\n FieldRendererBoundary,\n FieldRendererIssue,\n useFieldRenderers,\n} from './field-renderer'\n\ninterface FieldControlProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\n\n/**\n * The document-editor field control. Resolves a project's custom renderer by the\n * field's `form.control` (EC-238, customization seam EC-236) and falls back to the\n * built-in control for the field's datatype: a registered renderer renders inside\n * an error boundary (a throw → the built-in + a visible issue); a rejected\n * registration (non-component) shows the built-in with the issue above it; a field\n * naming a control with NO renderer and NO built-in name (a typo) shows the\n * built-in with a loud issue — never a silent fall-through; otherwise the built-in\n * renders alone.\n *\n * EC-330 split: the asset-field family lives in `asset-field.tsx`, the object /\n * repeater editors in `object-array-field.tsx`, and their pure companions\n * (reorder/summary/depth/table decisions, the `options` segmented control) in\n * `field-control-lib.tsx` — this file keeps the dispatcher plus the slug and\n * relation controls.\n */\nexport function FieldControl(props: FieldControlProps) {\n const renderers = useFieldRenderers()\n const builtIn = <BuiltInFieldControl {...props} />\n const control = props.field.form?.control\n const Custom = control ? renderers.get(control) : undefined\n if (Custom) {\n return (\n <FieldRendererBoundary\n fallback={builtIn}\n issue={<FieldRendererIssue message={translate('cms.field.rendererCrashed')} />}\n >\n <Custom {...props} />\n </FieldRendererBoundary>\n )\n }\n if (control) {\n const rejected = renderers.issues.find((issue) => issue.id === control)\n if (rejected) {\n return (\n <Stack gap=\"1\">\n {/* EC-478: the issue's catalog ref, resolved for the active locale. */}\n <FieldRendererIssue message={customizationIssueMessage(rejected)} />\n {builtIn}\n </Stack>\n )\n }\n // The field asks for a control with no registered renderer and no built-in\n // name — surface it (a typo'd `control` would otherwise read as a plain input).\n if (!(BUILTIN_FIELD_CONTROLS as readonly string[]).includes(control)) {\n return (\n <Stack gap=\"1\">\n <FieldRendererIssue message={translate('cms.field.noRenderer', { control })} />\n {builtIn}\n </Stack>\n )\n }\n }\n return builtIn\n}\n\n/** The conventional slug field name (mirrors `url-for-document`'s `SLUG_FIELD`). */\nconst SLUG_FIELD_NAME = 'slug'\n/** The built-in control name that opts any `text` field into the slug control (EC-315). */\nconst SLUG_CONTROL = 'slug'\n\n/**\n * EC-315: a `text` field is rendered by the smart slug control when it opts in via\n * `form.control: 'slug'`, OR (the default auto-upgrade) when it is simply named\n * `slug` — the long-standing convention `url-for-document`/hierarchy already key off.\n */\nfunction isSlugControl(field: FieldDef): boolean {\n return field.type === 'text' && (field.form?.control === SLUG_CONTROL || field.name === SLUG_FIELD_NAME)\n}\n\nfunction BuiltInFieldControl({ field, value, onChange, readOnly, id }: FieldControlProps) {\n // EC-315: the smart slug control (composed path, auto-slug, per-locale segments)\n // subsumes the old inline `slug` input + PathPreview. It degrades to a plain input\n // when the collection isn't routable (name-based auto-upgrade only) or no document\n // editor context is present (e.g. a table-cell picker).\n if (isSlugControl(field)) {\n return <SlugField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n }\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={value === true}\n disabled={readOnly}\n onChange={(e) => onChange(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {translate(value === true ? 'common.true' : 'common.false')}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={typeof value === 'number' ? value : ''}\n onChange={(e) => onChange(e.target.value === '' ? undefined : Number(e.target.value))}\n />\n )\n case 'date':\n // EC-463: never write a mid-entry segment state into the document — see\n // `date-input.tsx` (this control silently stored `0002-09-18` for an\n // editor typing 2026-09-18).\n return (\n <DateInput\n id={id}\n value={value}\n onChange={onChange}\n disabled={readOnly}\n testId={`field-date-${field.name}`}\n {...datePickerBounds(field.validation)}\n />\n )\n case 'select':\n if (field.multiple) {\n const selected = Array.isArray(value) ? (value as string[]) : []\n return (\n <Row gap=\"2\" wrap>\n {field.options.map((opt) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-sm\">\n <input\n type=\"checkbox\"\n checked={on}\n disabled={readOnly}\n onChange={() =>\n onChange(\n on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value],\n )\n }\n className=\"size-4 accent-primary\"\n />\n {localizedLabel(opt.label, opt.value)}\n </label>\n )\n })}\n </Row>\n )\n }\n // EC-295: the `options` control renders a small fixed enumeration as inline\n // segmented buttons instead of the dropdown (single-select only).\n if (field.form?.control === 'options') {\n return (\n <SegmentedOptions field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n )\n }\n return (\n <Select\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n case 'richText':\n return (\n <RichTextEditor\n value={value}\n onChange={onChange}\n readOnly={readOnly}\n id={id}\n allow={field.allow}\n // EC-436: the SAME resolved localized label the field row renders (the\n // EC-241 `fieldLabel` path) — titles the expand stage \"<label> — Rich text\".\n label={fieldLabel(field)}\n />\n )\n case 'relation':\n return <RelationField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'asset':\n return <AssetField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'object':\n return <ObjectField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n default:\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n}\n\n/**\n * EC-315 — the smart slug / URL-segment control. A `text` field named `slug` (or any\n * `text` field with `form.control: 'slug'`) renders here instead of a bare input:\n *\n * - **Composed path**: the route-derived URL, decomposed by {@link resolveSlugPath}\n * into read-only parent/context segments + the editable slug segment inline + the\n * locale prefix (non-default locales). Reuses the same `urlForDocument` the live\n * site uses, so the preview matches what visitors get. Draft-derived (updates as\n * you type); the live \"View live\" button in the header stays the published open.\n * - **Auto-slug**: while the slug is empty or was last produced by auto-derivation,\n * it live-derives from the source field (default: the collection's `titleField`).\n * A manual edit stops the auto-fill; the ↻ button always re-derives.\n * - **Per-locale**: reads/writes through the shared `value`/`onChange` seam, so a\n * `localized` slug edits the active locale's segment with no bespoke locale code.\n * - **Uniqueness**: an inline warning when the candidate collides with a sibling in\n * the same scope (same parent when hierarchical, same locale). A custom\n * `slug.unique` hook replaces the default sibling scan.\n *\n * Degrades to a plain input when there is no document editor context (a table-cell\n * picker) or when a name-based auto-upgrade lands on a non-routable collection.\n */\nfunction SlugField({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const slugConfig = field.slug as SlugFieldConfig | undefined\n const explicit = field.form?.control === SLUG_CONTROL\n const current = typeof value === 'string' ? value : ''\n const locale = source.activeLocale\n const localeConfig = source.localeConfig\n\n // 0.0.39 regression fix: resolve the deployment routes on EVERY render (a cheap\n // synchronous accessor), never freeze them at first paint. The earlier\n // `useMemo(() => resolveDeploymentRoutes(), [])` captured the EMPTY route set from\n // before the workspace/config finished loading — so the composed path never\n // appeared once routes arrived, and a stale set could paint a misleading prefix.\n const routes = resolveDeploymentRoutes()\n const routable = routes.some((r) => r.document?.collection === source.collectionId)\n\n const draft = editor?.draft\n // EC-463: a slug field that is NOT localized holds ONE value for every locale,\n // so its auto-slug must read ONE locale's source fields — the document's base\n // locale. Deriving from the ACTIVE locale made switching to the DE tab show a\n // German-derived slug preview for a field that is shared across locales (and,\n // with the EC-461 derive latch, a locale switch alone could then WRITE it).\n // A localized slug keeps per-locale derivation, which is the whole point of it.\n // EC-580: both halves are the one locale rule — `fieldViewLocale` names the view\n // (the default locale's for a shared field, the project's ONLY default), and the\n // source is read in that DISPLAYED view (`valuesInLocale`, with the fallback), so\n // the slug derives from the title the editor actually shows.\n const deriveLocale = fieldViewLocale(source.schema, field.name, locale, localeConfig)\n const values = useMemo<Record<string, unknown>>(\n () => (draft ? valuesInLocale(source.schema, draft, deriveLocale, localeConfig) : {}),\n [draft, deriveLocale, source.schema, localeConfig],\n )\n const derived = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n\n // Track whether the slug was manually set — auto-derive stays active while the\n // slug is empty or was last produced by auto-derivation (never overwrites a manual\n // slug). Seeded true when the loaded document already carries a slug.\n const [manuallyEdited, setManuallyEdited] = useState(() => current.trim() !== '')\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n // EC-461: opening a document is not an edit. Auto-derive fires only once the\n // derived value has moved away from its mount state (the editor actually\n // changed a source field this session) — the old on-mount fill made every\n // slug-less document dirty on open, and the 1s autosave then WROTE it (in the\n // playground: the stale re-seeded baseline over the adapter's newer record —\n // the destructive half of the EC-461 loss family). Decision extracted pure in\n // `slug-auto-derive.ts`; the latch keeps derivation live after the first\n // divergence so title edits that return to the initial value still land.\n const initialDerivedRef = useRef(derived)\n const deriveLatchRef = useRef(false)\n useEffect(() => {\n const decision = slugAutoDeriveDecision({\n derived,\n current,\n initialDerived: initialDerivedRef.current,\n latchOpen: deriveLatchRef.current,\n manuallyEdited,\n readOnly,\n })\n if (decision.openLatch) deriveLatchRef.current = true\n if (decision.write) onChangeRef.current(derived)\n }, [derived, manuallyEdited, readOnly, current])\n\n // The composed path parts (routable collections with a document editor only).\n const siblings = draft ? source.documents() : []\n // Draft-derived (routes + locale only, no `frontendUrl`) — the composed path is a\n // read-only preview that updates as you type, mirroring the detail editor's draft\n // URL. The header's published-gated \"View live\" stays the sole open-live action,\n // so the draft/live distinction is preserved (an unpublished slug never links out).\n // EC-518: this preview therefore needs no `addressable` check — it renders the\n // segments as TEXT (the only link is an ancestor's EDITOR, via `DocumentUrlSegment`)\n // and never offers the live URL, so it cannot present an unreachable one as working;\n // the unaddressable finding itself surfaces as the field's inline issue (EC-492).\n const parts: SlugPathParts | undefined =\n draft && routable\n ? resolveSlugPath({ routes, localeConfig }, source.schema, draft, { siblings, locale })\n : undefined\n // The classified context segments (0.0.42, round 3) — the same `resolveUrlSegments`\n // the header URL row uses, so the slug prefix and the header path attribute each\n // segment identically. Everything before this document's own `current` slug is the\n // context prefix; its LAST segment is the immediate parent shown in the compact pill.\n const contextSegments: UrlSegment[] =\n draft && routable\n ? resolveUrlSegments({ routes, localeConfig }, source.schema, draft, { siblings, locale }).filter(\n (s) => s.kind !== 'current',\n )\n : []\n const lastContext = contextSegments[contextSegments.length - 1]\n\n // Default (sync) uniqueness — the first colliding sibling's title, or null.\n const defaultCollisionTitle = useMemo<string | null>(() => {\n if (slugConfig?.unique || !draft) return null\n const trimmed = current.trim()\n if (trimmed === '') return null\n const hit = findSlugCollision(trimmed, draft, source.documents(), {\n collection: source.schema,\n locale,\n localeConfig,\n })\n return hit ? source.titleOf({ collection: source.collectionId, id: hit.id }) : null\n }, [current, draft, slugConfig, locale, localeConfig, source])\n\n // Custom (possibly async) uniqueness hook — race-guarded.\n const [asyncTaken, setAsyncTaken] = useState(false)\n useEffect(() => {\n const unique = slugConfig?.unique\n if (!unique || !draft) {\n setAsyncTaken(false)\n return\n }\n const trimmed = current.trim()\n if (trimmed === '') {\n setAsyncTaken(false)\n return\n }\n let cancelled = false\n Promise.resolve(unique(trimmed, { document: draft, locale, siblings: source.documents() }))\n .then((ok) => {\n if (!cancelled) setAsyncTaken(ok === false)\n })\n .catch(() => {\n if (!cancelled) setAsyncTaken(false)\n })\n return () => {\n cancelled = true\n }\n }, [current, slugConfig, draft, locale, source])\n\n // EC-511 — one collision, one message. The engine's `validation.unique` finding\n // OWNS this field's uniqueness whenever it speaks: it is collection-wide,\n // localized, and it blocks publish, and it already renders inline in this very\n // column (the FieldRow `error` slot) plus the Issues pane. The control's hint then\n // fills only the gap the engine leaves — a collision that exists once the active\n // locale falls back, which `validation.unique` deliberately does not see, and any\n // field that declares no `validation.unique` at all. The two rules stay different\n // on purpose (AD-497b); only the double presentation goes. Full argument, and why\n // this matches on field+locale rather than on the colliding document's id, in\n // `slug-collision-report.ts`.\n const collisionMessage = engineOwnsUniqueness(editor?.issues ?? [], field.name, locale)\n ? undefined\n : slugConfig?.unique\n ? asyncTaken\n ? translate('cms.field.slug.takenGeneric')\n : undefined\n : defaultCollisionTitle\n ? translate('cms.field.slug.taken', { title: defaultCollisionTitle })\n : undefined\n\n function handleInput(next: string) {\n onChange(next || undefined)\n // Clearing re-enables auto-derive; any non-empty entry is a manual override.\n setManuallyEdited(next.trim() !== '')\n }\n\n function regenerate() {\n setManuallyEdited(false)\n const next = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n onChange(next || undefined)\n }\n\n // No document editor (a picker), or a name-based auto-upgrade on a non-routable\n // collection: keep the historical plain input, no slug chrome (no regression).\n if (!editor || (!explicit && !routable)) {\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n\n const input = (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n placeholder={derived || undefined}\n onChange={(e) => handleInput(e.target.value)}\n className={cn(parts ? 'h-7 min-w-[10ch] flex-1 bg-background font-mono text-xs' : 'flex-1')}\n />\n )\n const regenerateButton = !readOnly ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={regenerate}\n aria-label={translate('cms.field.slug.regenerate')}\n title={translate('cms.field.slug.regenerate')}\n >\n <RotateCw />\n </Button>\n ) : null\n\n // The compact, ellipsized read-only prefix (0.0.39 \"hybrid split\", round-3 clickable):\n // only the LAST context segment (`…/produkte/`) so a deep path can't overflow the\n // narrow Details sidebar — but that shown segment is now CLICKABLE with the same\n // navigation semantics as the header URL row (an ancestor DOCUMENT links to its\n // editor; a static route literal stays plain). The full composed path is the tooltip.\n const prefixTitle =\n parts?.path ??\n (contextSegments.length > 0 ? `/${contextSegments.map((s) => s.text).join('/')}/` : '/')\n\n return (\n <Stack gap=\"2\">\n {parts ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-composed-path\">\n <span\n className=\"inline-flex min-w-0 shrink-0 items-center rounded bg-muted/40 px-1.5 py-1 font-mono text-xs text-muted-foreground ring-1 ring-inset ring-border\"\n title={prefixTitle}\n data-testid=\"slug-prefix\"\n >\n {contextSegments.length === 0 ? (\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n ) : (\n <>\n <span aria-hidden className=\"text-muted-foreground/50\">\n {contextSegments.length > 1 ? '…/' : '/'}\n </span>\n {lastContext ? (\n <DocumentUrlSegment\n segment={lastContext}\n projectId={editor?.projectId}\n collectionId={source.collectionId}\n />\n ) : null}\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n </>\n )}\n </span>\n {input}\n {regenerateButton}\n </Row>\n ) : (\n <Row gap=\"1\" align=\"center\">\n {input}\n {regenerateButton}\n </Row>\n )}\n\n {collisionMessage ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-collision\">\n <AlertTriangle className=\"size-3.5 shrink-0 text-warning\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {collisionMessage}\n </Text>\n </Row>\n ) : null}\n </Stack>\n )\n}\n\nfunction RelationField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: RelationFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const [open, setOpen] = useState(false)\n const refs = toRefArray(value)\n const projectId = source.projectId\n\n // EC-218: for the editing document's `parent` self-relation, exclude the\n // document itself and its descendants from the picker — choosing one would make\n // the page its own ancestor (a cycle). Only applies to the parent field of a\n // hierarchical collection; ordinary relations (and other self-relations) are\n // unrestricted. validateDocument is the backstop where this context is absent.\n const excludeIds = useMemo<Set<string> | undefined>(() => {\n if (!editor || field.target !== editor.collection.id) return undefined\n if (parentRelationField(editor.collection)?.name !== field.name) return undefined\n const ids = descendantIds(editor.docRef.id, source.referenceCandidates(editor.collection.id))\n ids.add(editor.docRef.id)\n return ids\n }, [editor, field.target, field.name, source])\n\n function apply(next: DocumentRef[]) {\n onChange(field.multiple ? next : (next[0] ?? undefined))\n }\n\n return (\n <Stack gap=\"2\">\n {refs.length > 0 ? (\n <Stack gap=\"1\">\n {refs.map((ref, i) => {\n // EC-296: the relation title navigates to the referenced document (autosave\n // keeps the current edit safe). Without a projectId (no persistence, e.g.\n // fixtures) it stays plain text. EC-246c: full-width row, not a pill —\n // long titles wrap awkwardly in a pill and a narrow field crushes it.\n const title = source.titleOf(ref)\n const label = (\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <Link2 className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 truncate\">{title}</span>\n </span>\n )\n return (\n <RowItem\n key={`${ref.id}-${i}`}\n data-testid=\"relation-value\"\n actions={\n !readOnly ? (\n <>\n {field.multiple === false ? (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('cms.relation.change')}\n title={translate('cms.relation.change')}\n onClick={() => setOpen(true)}\n >\n <Pencil />\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('common.remove')}\n title={translate('common.remove')}\n className=\"hover:text-destructive\"\n onClick={() => apply(refs.filter((r) => r.id !== ref.id))}\n >\n <X />\n </Button>\n </>\n ) : undefined\n }\n >\n {projectId ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.cmsDocument(ref.collection, ref.id) }}\n title={translate('cms.relation.openTitle', { title })}\n className=\"text-sm font-medium hover:text-primary hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <Text as=\"span\" size=\"sm\" weight=\"medium\">\n {label}\n </Text>\n )}\n </RowItem>\n )\n })}\n </Stack>\n ) : null}\n {!readOnly && (refs.length === 0 || field.multiple) ? (\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Plus /> {refs.length === 0 ? translate('cms.relation.pick') : translate('common.add')}\n </Button>\n </Row>\n ) : null}\n <RelationPicker\n targetCollection={field.target}\n multiple={field.multiple}\n value={refs}\n open={open}\n onOpenChange={setOpen}\n onChange={apply}\n excludeIds={excludeIds}\n />\n </Stack>\n )\n}\n"]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { DocumentRef, DocumentStore, LocaleConfig, ValidateDocumentOptions } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { AssetRecord } from '@elytracms/core/persistence';
|
|
3
|
+
/** The asset-library oracle the bag carries (EC-588): the record for an id, or `undefined`. */
|
|
4
|
+
export type FindAsset = NonNullable<ValidateDocumentOptions['findAsset']>;
|
|
2
5
|
/**
|
|
3
6
|
* THE option bag (EC-497, AD-497a).
|
|
4
7
|
*
|
|
@@ -30,7 +33,29 @@ export interface GateValidationInput {
|
|
|
30
33
|
localeConfig: LocaleConfig;
|
|
31
34
|
/** Published-ness from the version POINTER (EC-224) — never a flat state flag. */
|
|
32
35
|
isPublished: (ref: DocumentRef) => boolean;
|
|
33
|
-
/**
|
|
34
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The real asset library oracle (EC-588: returns the record, so the validator checks
|
|
38
|
+
* existence AND the field's `accept`); absent where no library is mounted or ready —
|
|
39
|
+
* see {@link assetLibraryOracle}.
|
|
40
|
+
*/
|
|
41
|
+
findAsset?: FindAsset;
|
|
35
42
|
}
|
|
36
43
|
export declare function gateValidateOptions(input: GateValidationInput): ValidateDocumentOptions;
|
|
44
|
+
/** The slice of the assets store the oracle is derived from (`AssetsStore` satisfies it). */
|
|
45
|
+
export interface AssetLibraryState {
|
|
46
|
+
status: 'loading' | 'ready' | 'error';
|
|
47
|
+
assets: readonly AssetRecord[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* EC-588 A2 — THE asset-library oracle for the option bag, derived from the assets
|
|
51
|
+
* store: a record lookup over the loaded library.
|
|
52
|
+
*
|
|
53
|
+
* Absent — so the validator checks neither existence nor `accept` — when no library is
|
|
54
|
+
* mounted (a fixture-only session) AND while it is loading or errored. Until then there
|
|
55
|
+
* is no truthful answer to "which record is this?". This keeps today's outcome
|
|
56
|
+
* (the oracle was `() => true` there, so existence passed and `accept` was never
|
|
57
|
+
* checked) without inventing a fake record. Whether the publish gate should REFUSE
|
|
58
|
+
* while the library is unready is the "validation bound to the operation" question,
|
|
59
|
+
* left to EC-595.
|
|
60
|
+
*/
|
|
61
|
+
export declare function assetLibraryOracle(library: AssetLibraryState | null): FindAsset | undefined;
|
|
@@ -13,7 +13,25 @@ export function gateValidateOptions(input) {
|
|
|
13
13
|
validateComposition,
|
|
14
14
|
validateRichTextEmbeds,
|
|
15
15
|
isPublished: input.isPublished,
|
|
16
|
-
...(input.
|
|
16
|
+
...(input.findAsset ? { findAsset: input.findAsset } : {}),
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* EC-588 A2 — THE asset-library oracle for the option bag, derived from the assets
|
|
21
|
+
* store: a record lookup over the loaded library.
|
|
22
|
+
*
|
|
23
|
+
* Absent — so the validator checks neither existence nor `accept` — when no library is
|
|
24
|
+
* mounted (a fixture-only session) AND while it is loading or errored. Until then there
|
|
25
|
+
* is no truthful answer to "which record is this?". This keeps today's outcome
|
|
26
|
+
* (the oracle was `() => true` there, so existence passed and `accept` was never
|
|
27
|
+
* checked) without inventing a fake record. Whether the publish gate should REFUSE
|
|
28
|
+
* while the library is unready is the "validation bound to the operation" question,
|
|
29
|
+
* left to EC-595.
|
|
30
|
+
*/
|
|
31
|
+
export function assetLibraryOracle(library) {
|
|
32
|
+
if (!library || library.status !== 'ready')
|
|
33
|
+
return undefined;
|
|
34
|
+
const byId = new Map(library.assets.map((asset) => [asset.id, asset]));
|
|
35
|
+
return (id) => byId.get(id);
|
|
36
|
+
}
|
|
19
37
|
//# sourceMappingURL=gate-validation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-validation.js","sourceRoot":"","sources":["../../../src/components/cms/gate-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"gate-validation.js","sourceRoot":"","sources":["../../../src/components/cms/gate-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAmD1F,MAAM,UAAU,mBAAmB,CAAC,KAA0B;IAC5D,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,wEAAwE;QACxE,+EAA+E;QAC/E,4EAA4E;QAC5E,8EAA8E;QAC9E,6EAA6E;QAC7E,0EAA0E;QAC1E,0EAA0E;QAC1E,mBAAmB;QACnB,sBAAsB;QACtB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAA;AACH,CAAC;AAQD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAiC;IAClE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,SAAS,CAAA;IAC5D,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;IACtE,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;AAC7B,CAAC","sourcesContent":["import { validateComposition, validateRichTextEmbeds } from './cms-composition-validators'\nimport type {\n DocumentRef,\n DocumentStore,\n LocaleConfig,\n ValidateDocumentOptions,\n} from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\n\n/** The asset-library oracle the bag carries (EC-588): the record for an id, or `undefined`. */\nexport type FindAsset = NonNullable<ValidateDocumentOptions['findAsset']>\n\n/**\n * THE option bag (EC-497, AD-497a).\n *\n * Before this module there were seven `validateDocument` call sites, each\n * hand-assembling its own subset of `ValidateDocumentOptions` — and the weakest of\n * the seven was the publish gate, the last thing between a draft and the live\n * site. One document therefore earned a different verdict depending on which\n * surface asked, which is a validation system that cannot be reasoned about.\n *\n * The contract, in one line: **surfaces differ in what they DO with the issues,\n * never in what they validated with.** Publish filters by severity, cell edit\n * filters by field + locale, the AI paths take `introducedErrors` (after − before)\n * — all three start from the identical list. There are deliberately NO named\n * \"weaker subsets\": a subset is how this defect happened, and the moment one\n * exists someone has to decide per call site which one applies.\n *\n * The one conditionality that remains is the engine's own and is documented on\n * `ValidateDocumentOptions`: `store`-dependent rules (relation/asset existence,\n * hierarchy cycles, `validation.unique`) fire exactly when a store is supplied.\n *\n * **Out of contract by declaration:** the assets and redirects rails. They\n * validate synthetic, non-`cmsDocuments` collections against their own registries\n * with no session store, and their writes go through `operations.assets.*` /\n * `operations.redirects.*` — a different entity, not a weaker check of this one.\n */\nexport interface GateValidationInput {\n /** The session document store, with the document under judgement swapped in. */\n store: DocumentStore\n localeConfig: LocaleConfig\n /** Published-ness from the version POINTER (EC-224) — never a flat state flag. */\n isPublished: (ref: DocumentRef) => boolean\n /**\n * The real asset library oracle (EC-588: returns the record, so the validator checks\n * existence AND the field's `accept`); absent where no library is mounted or ready —\n * see {@link assetLibraryOracle}.\n */\n findAsset?: FindAsset\n}\n\nexport function gateValidateOptions(input: GateValidationInput): ValidateDocumentOptions {\n return {\n store: input.store,\n localeConfig: input.localeConfig,\n // EC-188/EC-189: the composition + rich-text-embed leashes, injected so\n // `cms-core` stays free of `project-graph` and `rich-text`. These are what the\n // operations layer could never supply on its own — it has no registry — and\n // the reason the canonical validator is injected downward rather than rebuilt\n // there. Since EC-547 both injected validators unwrap the converged v2 block\n // sequence (`{ version: 2, content }`) and validate its component nodes —\n // top-level and nested in typography — with the same field `allow` leash.\n validateComposition,\n validateRichTextEmbeds,\n isPublished: input.isPublished,\n ...(input.findAsset ? { findAsset: input.findAsset } : {}),\n }\n}\n\n/** The slice of the assets store the oracle is derived from (`AssetsStore` satisfies it). */\nexport interface AssetLibraryState {\n status: 'loading' | 'ready' | 'error'\n assets: readonly AssetRecord[]\n}\n\n/**\n * EC-588 A2 — THE asset-library oracle for the option bag, derived from the assets\n * store: a record lookup over the loaded library.\n *\n * Absent — so the validator checks neither existence nor `accept` — when no library is\n * mounted (a fixture-only session) AND while it is loading or errored. Until then there\n * is no truthful answer to \"which record is this?\". This keeps today's outcome\n * (the oracle was `() => true` there, so existence passed and `accept` was never\n * checked) without inventing a fake record. Whether the publish gate should REFUSE\n * while the library is unready is the \"validation bound to the operation\" question,\n * left to EC-595.\n */\nexport function assetLibraryOracle(library: AssetLibraryState | null): FindAsset | undefined {\n if (!library || library.status !== 'ready') return undefined\n const byId = new Map(library.assets.map((asset) => [asset.id, asset]))\n return (id) => byId.get(id)\n}\n"]}
|