@elytracms/studio 0.0.88 → 0.0.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/convex/asset-revalidate.test.ts +49 -43
  2. package/convex/cms.ts +3 -4
  3. package/convex/delivery.ts +8 -11
  4. package/convex/document-revalidate.test.ts +188 -0
  5. package/convex/redirect-revalidate.test.ts +50 -37
  6. package/convex/revalidate.ts +18 -30
  7. package/convex/triggers.ts +47 -38
  8. package/convex/webhooks.ts +77 -62
  9. package/dist/components/account/self-account-view-source.js +2 -1
  10. package/dist/components/account/self-account-view-source.js.map +1 -1
  11. package/dist/components/ai/ai-chat-panel.js +1 -1
  12. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  13. package/dist/components/app-views/default-modules.js +3 -3
  14. package/dist/components/app-views/default-modules.js.map +1 -1
  15. package/dist/components/assets/asset-grid.js +1 -1
  16. package/dist/components/assets/asset-grid.js.map +1 -1
  17. package/dist/components/assets/asset-selection.d.ts +8 -9
  18. package/dist/components/assets/asset-selection.js +26 -16
  19. package/dist/components/assets/asset-selection.js.map +1 -1
  20. package/dist/components/assets/assets-collection-view-source.js +3 -2
  21. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  22. package/dist/components/builder/blocks-model.d.ts +7 -5
  23. package/dist/components/builder/blocks-model.js +11 -9
  24. package/dist/components/builder/blocks-model.js.map +1 -1
  25. package/dist/components/builder/blocks-panel.js +1 -1
  26. package/dist/components/builder/blocks-panel.js.map +1 -1
  27. package/dist/components/builder/builder-store.d.ts +11 -3
  28. package/dist/components/builder/builder-store.js +12 -5
  29. package/dist/components/builder/builder-store.js.map +1 -1
  30. package/dist/components/builder/canvas-dnd.d.ts +7 -1
  31. package/dist/components/builder/canvas-dnd.js +6 -3
  32. package/dist/components/builder/canvas-dnd.js.map +1 -1
  33. package/dist/components/builder/canvas-render-context.d.ts +13 -1
  34. package/dist/components/builder/canvas-render-context.js +28 -18
  35. package/dist/components/builder/canvas-render-context.js.map +1 -1
  36. package/dist/components/builder/canvas.js +1 -1
  37. package/dist/components/builder/canvas.js.map +1 -1
  38. package/dist/components/builder/clipboard-actions.d.ts +0 -6
  39. package/dist/components/builder/clipboard-actions.js +9 -4
  40. package/dist/components/builder/clipboard-actions.js.map +1 -1
  41. package/dist/components/builder/composition-field-editor.d.ts +22 -0
  42. package/dist/components/builder/composition-field-editor.js +47 -8
  43. package/dist/components/builder/composition-field-editor.js.map +1 -1
  44. package/dist/components/builder/inline-edit.d.ts +7 -4
  45. package/dist/components/builder/inline-edit.js +11 -8
  46. package/dist/components/builder/inline-edit.js.map +1 -1
  47. package/dist/components/builder/insert-context.js +9 -5
  48. package/dist/components/builder/insert-context.js.map +1 -1
  49. package/dist/components/builder/layout-inspector.d.ts +2 -2
  50. package/dist/components/builder/layout-inspector.js +8 -6
  51. package/dist/components/builder/layout-inspector.js.map +1 -1
  52. package/dist/components/builder/node-factory.d.ts +31 -14
  53. package/dist/components/builder/node-factory.js +73 -93
  54. package/dist/components/builder/node-factory.js.map +1 -1
  55. package/dist/components/builder/page-tree.d.ts +3 -1
  56. package/dist/components/builder/page-tree.js +6 -4
  57. package/dist/components/builder/page-tree.js.map +1 -1
  58. package/dist/components/builder/property-inspector.js +2 -1
  59. package/dist/components/builder/property-inspector.js.map +1 -1
  60. package/dist/components/builder/reuse-context.js +2 -2
  61. package/dist/components/builder/reuse-context.js.map +1 -1
  62. package/dist/components/builder/reuse.d.ts +7 -3
  63. package/dist/components/builder/reuse.js +19 -11
  64. package/dist/components/builder/reuse.js.map +1 -1
  65. package/dist/components/builder/selection-overlay.d.ts +2 -2
  66. package/dist/components/builder/selection-overlay.js +3 -3
  67. package/dist/components/builder/selection-overlay.js.map +1 -1
  68. package/dist/components/builder/spacing-handles.d.ts +2 -2
  69. package/dist/components/builder/spacing-handles.js +3 -3
  70. package/dist/components/builder/spacing-handles.js.map +1 -1
  71. package/dist/components/cms/asset-field.d.ts +1 -1
  72. package/dist/components/cms/asset-field.js +15 -13
  73. package/dist/components/cms/asset-field.js.map +1 -1
  74. package/dist/components/cms/asset-picker-core.js +3 -4
  75. package/dist/components/cms/asset-picker-core.js.map +1 -1
  76. package/dist/components/cms/cell-edit.d.ts +0 -6
  77. package/dist/components/cms/cell-edit.js +5 -23
  78. package/dist/components/cms/cell-edit.js.map +1 -1
  79. package/dist/components/cms/cms-store.d.ts +3 -2
  80. package/dist/components/cms/cms-store.js +23 -24
  81. package/dist/components/cms/cms-store.js.map +1 -1
  82. package/dist/components/cms/collection-table.js +14 -4
  83. package/dist/components/cms/collection-table.js.map +1 -1
  84. package/dist/components/cms/detail-editor.js +28 -24
  85. package/dist/components/cms/detail-editor.js.map +1 -1
  86. package/dist/components/cms/discard-draft.d.ts +1 -2
  87. package/dist/components/cms/discard-draft.js +1 -1
  88. package/dist/components/cms/discard-draft.js.map +1 -1
  89. package/dist/components/cms/duplicate-document.d.ts +3 -2
  90. package/dist/components/cms/duplicate-document.js +5 -5
  91. package/dist/components/cms/duplicate-document.js.map +1 -1
  92. package/dist/components/cms/editable-cell.js +11 -7
  93. package/dist/components/cms/editable-cell.js.map +1 -1
  94. package/dist/components/cms/field-control-lib.d.ts +10 -0
  95. package/dist/components/cms/field-control-lib.js +20 -0
  96. package/dist/components/cms/field-control-lib.js.map +1 -1
  97. package/dist/components/cms/field-control.js +10 -9
  98. package/dist/components/cms/field-control.js.map +1 -1
  99. package/dist/components/cms/object-array-field.js +3 -5
  100. package/dist/components/cms/object-array-field.js.map +1 -1
  101. package/dist/components/cms/prop-fields.d.ts +16 -3
  102. package/dist/components/cms/prop-fields.js +57 -12
  103. package/dist/components/cms/prop-fields.js.map +1 -1
  104. package/dist/components/cms/publish-clean.d.ts +13 -6
  105. package/dist/components/cms/publish-clean.js +14 -6
  106. package/dist/components/cms/publish-clean.js.map +1 -1
  107. package/dist/components/cms/rich-text/embed-props.d.ts +6 -1
  108. package/dist/components/cms/rich-text/embed-props.js +7 -12
  109. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  110. package/dist/components/cms/rich-text/insert.d.ts +13 -6
  111. package/dist/components/cms/rich-text/insert.js +16 -17
  112. package/dist/components/cms/rich-text/insert.js.map +1 -1
  113. package/dist/components/cms/use-cell-edit.js +7 -12
  114. package/dist/components/cms/use-cell-edit.js.map +1 -1
  115. package/dist/components/cms/use-discard-controller.d.ts +1 -2
  116. package/dist/components/cms/use-discard-controller.js +1 -2
  117. package/dist/components/cms/use-discard-controller.js.map +1 -1
  118. package/dist/components/cms/use-publish-controller.js +3 -2
  119. package/dist/components/cms/use-publish-controller.js.map +1 -1
  120. package/dist/components/cms/versions-pane.js +2 -2
  121. package/dist/components/cms/versions-pane.js.map +1 -1
  122. package/dist/components/components/component-detail.d.ts +1 -1
  123. package/dist/components/components/component-detail.js +2 -4
  124. package/dist/components/components/component-detail.js.map +1 -1
  125. package/dist/components/components/component-preview.js +9 -4
  126. package/dist/components/components/component-preview.js.map +1 -1
  127. package/dist/components/components/components-index.d.ts +2 -2
  128. package/dist/components/components/components-index.js +3 -6
  129. package/dist/components/components/components-index.js.map +1 -1
  130. package/dist/components/components/field-inspector.js +5 -2
  131. package/dist/components/components/field-inspector.js.map +1 -1
  132. package/dist/components/components/registry-browser.d.ts +6 -5
  133. package/dist/components/components/registry-browser.js +11 -37
  134. package/dist/components/components/registry-browser.js.map +1 -1
  135. package/dist/components/components/taxonomy.d.ts +14 -21
  136. package/dist/components/components/taxonomy.js +5 -29
  137. package/dist/components/components/taxonomy.js.map +1 -1
  138. package/dist/components/components/transfer-dialog.js +1 -0
  139. package/dist/components/components/transfer-dialog.js.map +1 -1
  140. package/dist/components/components/transfer.d.ts +20 -12
  141. package/dist/components/components/transfer.js +27 -21
  142. package/dist/components/components/transfer.js.map +1 -1
  143. package/dist/components/delivery-health-banner.d.ts +3 -2
  144. package/dist/components/delivery-health-banner.js +3 -2
  145. package/dist/components/delivery-health-banner.js.map +1 -1
  146. package/dist/components/redirects/redirects-collection-view-source.js +3 -2
  147. package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
  148. package/dist/components/users/users-collection-view-source.js +2 -1
  149. package/dist/components/users/users-collection-view-source.js.map +1 -1
  150. package/dist/lib/ai/apply-content.js +32 -28
  151. package/dist/lib/ai/apply-content.js.map +1 -1
  152. package/dist/lib/ai/constraints.d.ts +16 -14
  153. package/dist/lib/ai/constraints.js +64 -48
  154. package/dist/lib/ai/constraints.js.map +1 -1
  155. package/dist/lib/ai/translate-document.js +11 -7
  156. package/dist/lib/ai/translate-document.js.map +1 -1
  157. package/dist/lib/ai/types.d.ts +9 -5
  158. package/dist/lib/ai/types.js.map +1 -1
  159. package/dist/lib/ai/update-fields.js +15 -32
  160. package/dist/lib/ai/update-fields.js.map +1 -1
  161. package/dist/lib/ai/vite.d.ts +22 -2
  162. package/dist/lib/ai/vite.js +12 -3
  163. package/dist/lib/ai/vite.js.map +1 -1
  164. package/dist/lib/assets/asset-placements.d.ts +3 -10
  165. package/dist/lib/assets/asset-placements.js +17 -8
  166. package/dist/lib/assets/asset-placements.js.map +1 -1
  167. package/dist/lib/clipboard/paste-target.js +3 -3
  168. package/dist/lib/clipboard/paste-target.js.map +1 -1
  169. package/dist/lib/cms/published-documents.d.ts +14 -3
  170. package/dist/lib/cms/published-documents.js +12 -3
  171. package/dist/lib/cms/published-documents.js.map +1 -1
  172. package/dist/lib/cms-views/view-model.d.ts +4 -4
  173. package/dist/lib/cms-views/view-model.js +26 -20
  174. package/dist/lib/cms-views/view-model.js.map +1 -1
  175. package/dist/lib/data/collection-content.d.ts +10 -7
  176. package/dist/lib/data/collection-content.js +70 -29
  177. package/dist/lib/data/collection-content.js.map +1 -1
  178. package/dist/lib/delivery/instant-publish.d.ts +0 -3
  179. package/dist/lib/delivery/instant-publish.js +0 -1
  180. package/dist/lib/delivery/instant-publish.js.map +1 -1
  181. package/dist/lib/dnd/drop-rules.d.ts +22 -7
  182. package/dist/lib/dnd/drop-rules.js +21 -14
  183. package/dist/lib/dnd/drop-rules.js.map +1 -1
  184. package/dist/lib/engine-version.d.ts +1 -1
  185. package/dist/lib/engine-version.js +1 -1
  186. package/dist/lib/engine-version.js.map +1 -1
  187. package/dist/lib/i18n/messages.de.js +25 -9
  188. package/dist/lib/i18n/messages.de.js.map +1 -1
  189. package/dist/lib/i18n/messages.en.d.ts +21 -7
  190. package/dist/lib/i18n/messages.en.js +28 -9
  191. package/dist/lib/i18n/messages.en.js.map +1 -1
  192. package/dist/lib/locale/fallback.d.ts +5 -19
  193. package/dist/lib/locale/fallback.js +18 -39
  194. package/dist/lib/locale/fallback.js.map +1 -1
  195. package/dist/lib/locale/locale-store.js +2 -1
  196. package/dist/lib/locale/locale-store.js.map +1 -1
  197. package/dist/lib/operations/cms-persistence.js +7 -4
  198. package/dist/lib/operations/cms-persistence.js.map +1 -1
  199. package/dist/lib/operations/studio-operations.js +4 -6
  200. package/dist/lib/operations/studio-operations.js.map +1 -1
  201. package/dist/lib/persistence/backend-mode.d.ts +5 -25
  202. package/dist/lib/persistence/backend-mode.js +13 -30
  203. package/dist/lib/persistence/backend-mode.js.map +1 -1
  204. package/dist/lib/persona/labels.d.ts +4 -9
  205. package/dist/lib/persona/labels.js +5 -12
  206. package/dist/lib/persona/labels.js.map +1 -1
  207. package/dist/lib/references/reference-model.d.ts +17 -5
  208. package/dist/lib/references/reference-model.js +20 -28
  209. package/dist/lib/references/reference-model.js.map +1 -1
  210. package/dist/lib/references/references-store.js +7 -0
  211. package/dist/lib/references/references-store.js.map +1 -1
  212. package/dist/lib/registry/__fixtures__/demo-components.d.ts +32 -0
  213. package/dist/lib/registry/{project-components.js → __fixtures__/demo-components.js} +18 -5
  214. package/dist/lib/registry/__fixtures__/demo-components.js.map +1 -0
  215. package/dist/lib/registry/index.d.ts +9 -13
  216. package/dist/lib/registry/index.js +34 -61
  217. package/dist/lib/registry/index.js.map +1 -1
  218. package/dist/lib/registry/preview.d.ts +22 -7
  219. package/dist/lib/registry/preview.js +26 -89
  220. package/dist/lib/registry/preview.js.map +1 -1
  221. package/dist/lib/workspace/index.d.ts +0 -7
  222. package/dist/lib/workspace/index.js +0 -9
  223. package/dist/lib/workspace/index.js.map +1 -1
  224. package/dist/previews/enumerate.d.ts +23 -27
  225. package/dist/previews/enumerate.js +27 -48
  226. package/dist/previews/enumerate.js.map +1 -1
  227. package/dist/previews/harness.js +1 -1
  228. package/dist/previews/harness.js.map +1 -1
  229. package/package.json +4 -7
  230. package/dist/lib/registry/project-components.d.ts +0 -16
  231. package/dist/lib/registry/project-components.js.map +0 -1
  232. package/dist/lib/webhooks/config.d.ts +0 -15
  233. package/dist/lib/webhooks/config.js +0 -65
  234. package/dist/lib/webhooks/config.js.map +0 -1
  235. package/dist/lib/webhooks/dispatcher.d.ts +0 -70
  236. package/dist/lib/webhooks/dispatcher.js +0 -61
  237. package/dist/lib/webhooks/dispatcher.js.map +0 -1
  238. package/dist/lib/webhooks/index.d.ts +0 -11
  239. package/dist/lib/webhooks/index.js +0 -10
  240. package/dist/lib/webhooks/index.js.map +0 -1
  241. package/dist/lib/webhooks/signing.d.ts +0 -15
  242. package/dist/lib/webhooks/signing.js +0 -24
  243. package/dist/lib/webhooks/signing.js.map +0 -1
  244. package/dist/lib/webhooks/tags.d.ts +0 -25
  245. package/dist/lib/webhooks/tags.js +0 -289
  246. package/dist/lib/webhooks/tags.js.map +0 -1
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * Semantics (ratified 2026-07-17):
7
7
  * - The duplicate copies the current DRAFT field values — `values` plus
8
- * `localized` across ALL locales plus `defaultLocale` — into a fresh document
9
- * id. It is created as a **draft, never auto-published** ("live is
8
+ * `localized` across ALL locales — into a fresh document id (EC-580 deleted the
9
+ * per-document `defaultLocale` this used to copy). It is created as a **draft, never auto-published** ("live is
10
10
  * published-only"): published-ness is the version pointer (EC-224), and
11
11
  * nothing here touches it.
12
12
  * - `alias` is deliberately NOT copied: an alias is a unique-within-collection
@@ -88,8 +88,9 @@ function uniquifySlot(values, fieldName, pool) {
88
88
  }
89
89
  /**
90
90
  * Derive the duplicate document (pure): deep-copied `values` + `localized` (all
91
- * locales) + `defaultLocale` under the fresh id, `alias` dropped, every slug
92
- * field's stored slots uniquified against the collection's slug pool. See the
91
+ * locales) under the fresh id, `alias` dropped, every slug field's stored slots
92
+ * uniquified against the collection's slug pool. (EC-580: there is no per-document
93
+ * `defaultLocale` to copy — the project's config is the only default.) See the
93
94
  * module doc for the ratified semantics each rule pins.
94
95
  */
95
96
  export function deriveDuplicateDocument({ original, newId, schema, siblings, }) {
@@ -100,7 +101,6 @@ export function deriveDuplicateDocument({ original, newId, schema, siblings, })
100
101
  // independent of the original (editing one must never mutate the other).
101
102
  values: structuredClone(original.values ?? {}),
102
103
  ...(original.localized !== undefined ? { localized: structuredClone(original.localized) } : {}),
103
- ...(original.defaultLocale !== undefined ? { defaultLocale: original.defaultLocale } : {}),
104
104
  // `alias` deliberately dropped (unique slot handle — see module doc).
105
105
  };
106
106
  for (const field of schema.fields) {
@@ -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"]}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState } from 'react';
3
- import { isFieldLocalized } from '@elytracms/core/cms-core';
3
+ import { canonicalDateValue, isFieldLocalized, selfTypeFieldValue } from '@elytracms/core/cms-core';
4
4
  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';
@@ -9,6 +9,7 @@ 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';
12
+ import { selectedOptions } from './field-control-lib.js';
12
13
  import { cellEditKind, cellKey } from './cell-edit.js';
13
14
  import { fieldLabel } from './cms-labels.js';
14
15
  import { useCollectionView } from '../collection-view/collection-view-context.js';
@@ -176,7 +177,7 @@ function CellPicker({ field, value, onCommit, onCancel, }) {
176
177
  return (_jsx(AssetPicker, { open: true, onOpenChange: (open) => {
177
178
  if (!open)
178
179
  onCancel();
179
- }, ...assetPickerBinding(field, value, (next) => onCommit(next, null)) }));
180
+ }, ...assetPickerBinding(field, selfTypeFieldValue(field, value), (next) => onCommit(next, null)) }));
180
181
  }
181
182
  return null;
182
183
  }
@@ -188,11 +189,14 @@ function initialDraft(field, value) {
188
189
  case 'number':
189
190
  return typeof value === 'number' ? String(value) : '';
190
191
  case 'date':
191
- return typeof value === 'string' ? value.slice(0, 10) : '';
192
+ // EC-586: read through the one date canonicaliser — an epoch number or an
193
+ // offset instant shows its UTC day instead of digits or nothing.
194
+ return canonicalDateValue(value)?.slice(0, 10) ?? '';
192
195
  case 'select':
193
- if (field.multiple) {
194
- return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
195
- }
196
+ // EC-586: a legacy scalar in a multi-select reads as checked (the same reader
197
+ // as the form), so the next commit keeps it instead of dropping it.
198
+ if (field.multiple)
199
+ return selectedOptions(field, value);
196
200
  return typeof value === 'string' ? value : '';
197
201
  default:
198
202
  return typeof value === 'string' ? value : '';
@@ -205,7 +209,7 @@ function draftValue(field, draft) {
205
209
  case 'number':
206
210
  return draft === '' ? undefined : Number(draft);
207
211
  case 'date':
208
- return draft === '' || typeof draft !== 'string' ? undefined : `${draft}T00:00:00.000Z`;
212
+ return draft === '' || typeof draft !== 'string' ? undefined : canonicalDateValue(draft);
209
213
  case 'select':
210
214
  if (field.multiple)
211
215
  return Array.isArray(draft) ? draft : [];
@@ -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 { 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"]}
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,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAEnG,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,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,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,KAOG,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAC/F,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,0EAA0E;YAC1E,iEAAiE;YACjE,OAAO,kBAAkB,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;QACtD,KAAK,QAAQ;YACX,8EAA8E;YAC9E,oEAAoE;YACpE,IAAI,KAAK,CAAC,QAAQ;gBAAE,OAAO,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;YACxD,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,kBAAkB,CAAC,KAAK,CAAC,CAAA;QAC1F,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 { canonicalDateValue, isFieldLocalized, selfTypeFieldValue } 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 { selectedOptions } from './field-control-lib'\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. EC-586:\n // it picks from the value read through the canonicaliser (the one cardinality\n // rule), so a lone placement in a `multiple` field is kept, not dropped.\n {...assetPickerBinding(field, selfTypeFieldValue(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 // EC-586: read through the one date canonicaliser — an epoch number or an\n // offset instant shows its UTC day instead of digits or nothing.\n return canonicalDateValue(value)?.slice(0, 10) ?? ''\n case 'select':\n // EC-586: a legacy scalar in a multi-select reads as checked (the same reader\n // as the form), so the next commit keeps it instead of dropping it.\n if (field.multiple) return selectedOptions(field, value)\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 : canonicalDateValue(draft)\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"]}
@@ -13,6 +13,16 @@ import type { ObjectFieldDef, SelectFieldDef } from '@elytracms/core/cms-core';
13
13
  * so the two stay aligned through a reorder — a row's child/DOM state (an open
14
14
  * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).
15
15
  */
16
+ /**
17
+ * The options a multi-select currently holds, read through THE write canonicaliser
18
+ * (EC-586) — so a legacy scalar (`'news'`, written by the old bulk "set field") shows
19
+ * as checked instead of nothing, and the next toggle keeps it. The form used to read
20
+ * `Array.isArray(value) ? value : []`, which showed the scalar unchecked and DROPPED it
21
+ * on the next click (measured, round2-B §Q8 1(c)).
22
+ */
23
+ export declare function selectedOptions(field: SelectFieldDef, value: unknown): string[];
24
+ /** Toggle one option of a multi-select; the next value (an emptied list is absent). */
25
+ export declare function toggleSelectOption(field: SelectFieldDef, value: unknown, option: string): string[] | undefined;
16
26
  export declare function reorder<T>(list: readonly T[], from: number, to: number): T[];
17
27
  /**
18
28
  * A scannable one-line summary for a collapsed repeater item (EC-294): the first
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { selfTypeFieldValue } from '@elytracms/core/cms-core';
2
3
  import { Button, Row } from '@elytracms/studio/ui';
3
4
  import { localizedLabel } from '@elytracms/studio/lib/i18n';
4
5
  import { fieldLabel } from './cms-labels.js';
@@ -16,6 +17,25 @@ import { fieldLabel } from './cms-labels.js';
16
17
  * so the two stay aligned through a reorder — a row's child/DOM state (an open
17
18
  * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).
18
19
  */
20
+ /**
21
+ * The options a multi-select currently holds, read through THE write canonicaliser
22
+ * (EC-586) — so a legacy scalar (`'news'`, written by the old bulk "set field") shows
23
+ * as checked instead of nothing, and the next toggle keeps it. The form used to read
24
+ * `Array.isArray(value) ? value : []`, which showed the scalar unchecked and DROPPED it
25
+ * on the next click (measured, round2-B §Q8 1(c)).
26
+ */
27
+ export function selectedOptions(field, value) {
28
+ const canonical = selfTypeFieldValue(field, value);
29
+ return Array.isArray(canonical) ? canonical.filter((v) => typeof v === 'string') : [];
30
+ }
31
+ /** Toggle one option of a multi-select; the next value (an emptied list is absent). */
32
+ export function toggleSelectOption(field, value, option) {
33
+ const selected = selectedOptions(field, value);
34
+ const next = selected.includes(option)
35
+ ? selected.filter((v) => v !== option)
36
+ : [...selected, option];
37
+ return next.length === 0 ? undefined : next;
38
+ }
19
39
  export function reorder(list, from, to) {
20
40
  const next = list.slice();
21
41
  const [moved] = next.splice(from, 1);
@@ -1 +1 @@
1
- {"version":3,"file":"field-control-lib.js","sourceRoot":"","sources":["../../../src/components/cms/field-control-lib.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAI,IAAkB,EAAE,IAAY,EAAE,EAAU;IACrE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,CAAA;IACzB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IACpC,IAAI,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAA;IAClD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;AAE1F;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAmC,EACnC,IAA6B,EAC7B,KAAa;IAEb,MAAM,MAAM,GAAG,CAAC,IAAY,EAAiB,EAAE;QAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAA;QACpB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE;YAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAA;QACtD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAA;QACrE,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACvC,KAAK,MAAM,IAAI,IAAI,wBAAwB,EAAE,CAAC;QAC5C,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;YACtB,IAAI,CAAC;gBAAE,OAAO,eAAe,CAAC,CAAC,CAAC,CAAA;QAClC,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;QACtB,IAAI,CAAC;YAAE,OAAO,eAAe,CAAC,CAAC,CAAC,CAAA;IAClC,CAAC;IACD,OAAO,IAAI,KAAK,GAAG,CAAC,EAAE,CAAA;AACxB,CAAC;AAED,SAAS,eAAe,CAAC,CAAS,EAAE,GAAG,GAAG,EAAE;IAC1C,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AACvD,CAAC;AAED,kFAAkF;AAClF,kFAAkF;AAClF,6EAA6E;AAC7E,qFAAqF;AACrF,sFAAsF;AACtF,+FAA+F;AAC/F,MAAM,UAAU,GAAG,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,sBAAsB,CAAC,CAAA;AACvG,MAAM,UAAU,GAAG,CAAC,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,CAAC,CAAA;AACvF,qFAAqF;AACrF,kFAAkF;AAClF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,CAAE,CAAA;AAC1E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,CAAE,CAAA;AAE1E,oFAAoF;AACpF,sFAAsF;AACtF,sFAAsF;AACtF,kEAAkE;AAClE,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAA;AACpF,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAC3B,MAAM,UAAU,mBAAmB,CAAC,KAAqB;IACvD,OAAO,CACL,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QACvB,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,iBAAiB;QACxC,KAAK,CAAC,MAAM,CAAC,KAAK,CAChB,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,CAC/E,CACF,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,EAAE,GAOH;IACC,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IACtD,OAAO,CACL,KAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,QAAC,IAAI,EAAC,YAAY,EAAC,EAAE,EAAE,EAAE,gBAAc,UAAU,CAAC,KAAK,CAAC,YACtE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACzB,MAAM,MAAM,GAAG,OAAO,KAAK,GAAG,CAAC,KAAK,CAAA;YACpC,OAAO,CACL,KAAC,MAAM,IAEL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EACvC,IAAI,EAAC,IAAI,EACT,IAAI,EAAC,OAAO,kBACE,MAAM,EACpB,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,SAAS,EACnD,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,YAEjC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAVhC,GAAG,CAAC,KAAK,CAWP,CACV,CAAA;QACH,CAAC,CAAC,GACE,CACP,CAAA;AACH,CAAC","sourcesContent":["import type { ObjectFieldDef, SelectFieldDef } from '@elytracms/core/cms-core'\nimport { Button, Row } from '@elytracms/studio/ui'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { fieldLabel } from './cms-labels'\n\n/**\n * Pure companions of the document-editor field controls (EC-330, split from\n * `field-control.tsx`): the repeater reorder/summary/shape decisions and the\n * depth-encoding palette the object-array editors render from, plus the\n * `options` segmented control. Small and dependency-light so the behavioural\n * pins in `field-control.test.ts` reach them without pulling the whole\n * field-control dispatcher.\n */\n\n/**\n * Move `from` → `to`, returning a NEW array (all elements preserved). The repeater\n * applies the same `(from, to)` to its value array AND its parallel stable-key array,\n * so the two stay aligned through a reorder — a row's child/DOM state (an open\n * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).\n */\nexport function reorder<T>(list: readonly T[], from: number, to: number): T[] {\n const next = list.slice()\n const [moved] = next.splice(from, 1)\n if (moved !== undefined) next.splice(to, 0, moved)\n return next\n}\n\nconst SUMMARY_PREFERRED_FIELDS = ['title', 'label', 'name', 'heading', 'text', 'linkType']\n\n/**\n * A scannable one-line summary for a collapsed repeater item (EC-294): the first\n * meaningful scalar subfield value — preferring title-ish field names — else `#n`.\n * Pure (no store): summarises from the item's own scalar values, so it works for\n * any object shape without resolving relations/assets.\n */\nexport function objectItemSummary(\n fields: readonly { name: string }[],\n item: Record<string, unknown>,\n index: number,\n): string {\n const scalar = (name: string): string | null => {\n const v = item[name]\n if (typeof v === 'string' && v.trim()) return v.trim()\n if (typeof v === 'number' || typeof v === 'boolean') return String(v)\n return null\n }\n const names = fields.map((f) => f.name)\n for (const name of SUMMARY_PREFERRED_FIELDS) {\n if (names.includes(name)) {\n const s = scalar(name)\n if (s) return truncateSummary(s)\n }\n }\n for (const name of names) {\n const s = scalar(name)\n if (s) return truncateSummary(s)\n }\n return `#${index + 1}`\n}\n\nfunction truncateSummary(s: string, max = 60): string {\n return s.length > max ? `${s.slice(0, max - 1)}…` : s\n}\n\n// (B) Depth-keyed visuals: a coloured left rail + a matching faint tint, DISTINCT\n// per level so nesting reads at a glance. The outermost array (depth 0) starts at\n// the brand primary — no more neutral, un-encoded first level — then a fixed\n// high-contrast rotation whose adjacent hues never collide (primary→amber→green→red;\n// note `info` is a near-primary blue, so it is deliberately NOT adjacent to primary).\n// It cycles (mod) so pathological depth still renders. Literal classes so Tailwind emits them.\nconst DEPTH_RAIL = ['border-l-primary', 'border-l-warning', 'border-l-success', 'border-l-destructive']\nconst DEPTH_TINT = ['bg-primary/5', 'bg-warning/5', 'bg-success/5', 'bg-destructive/5']\n// `d` is always ≥ 0, so `d % length` is in-bounds — the assertion is sound and gives\n// callers a definite `string` (cn tolerates undefined, but tests read the value).\nexport const depthRail = (d: number) => DEPTH_RAIL[d % DEPTH_RAIL.length]!\nexport const depthTint = (d: number) => DEPTH_TINT[d % DEPTH_TINT.length]!\n\n// (C) An object array renders as a compact TABLE when every subfield is a scalar (a\n// flat row) and there are few enough columns to read; richer/nested object types stay\n// as cards with depth encoding (B). Conditional (function-hidden) subfields force the\n// card path — a table has no place to show/hide a column per row.\nconst TABULAR_FIELD_TYPES = new Set(['text', 'number', 'boolean', 'select', 'date'])\nconst MAX_TABLE_COLUMNS = 4\nexport function isTabularObjectType(field: ObjectFieldDef): boolean {\n return (\n field.fields.length > 0 &&\n field.fields.length <= MAX_TABLE_COLUMNS &&\n field.fields.every(\n (f) => TABULAR_FIELD_TYPES.has(f.type) && typeof f.form?.hidden !== 'function',\n )\n )\n}\n\n/**\n * EC-295: the `options` control — a `select` shown as inline segmented buttons\n * (`[[Page]] [URL] [File]`) instead of a dropdown. Engine built-in (no consumer\n * registration); single-select only. Each option shows `label ?? value` and a\n * `title` tooltip from the option's `description`; the field `default` + `readOnly`\n * flow through the shared form. Picks one — to allow an empty/unset value use the\n * default dropdown control.\n */\nexport function SegmentedOptions({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: SelectFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n const current = typeof value === 'string' ? value : ''\n return (\n <Row gap=\"1\" wrap role=\"radiogroup\" id={id} aria-label={fieldLabel(field)}>\n {field.options.map((opt) => {\n const active = current === opt.value\n return (\n <Button\n key={opt.value}\n type=\"button\"\n variant={active ? 'default' : 'outline'}\n size=\"sm\"\n role=\"radio\"\n aria-checked={active}\n title={localizedLabel(opt.description) || undefined}\n disabled={readOnly}\n onClick={() => onChange(opt.value)}\n >\n {localizedLabel(opt.label, opt.value)}\n </Button>\n )\n })}\n </Row>\n )\n}\n"]}
1
+ {"version":3,"file":"field-control-lib.js","sourceRoot":"","sources":["../../../src/components/cms/field-control-lib.tsx"],"names":[],"mappings":"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","sourcesContent":["import { selfTypeFieldValue } from '@elytracms/core/cms-core'\nimport type { ObjectFieldDef, SelectFieldDef } from '@elytracms/core/cms-core'\nimport { Button, Row } from '@elytracms/studio/ui'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { fieldLabel } from './cms-labels'\n\n/**\n * Pure companions of the document-editor field controls (EC-330, split from\n * `field-control.tsx`): the repeater reorder/summary/shape decisions and the\n * depth-encoding palette the object-array editors render from, plus the\n * `options` segmented control. Small and dependency-light so the behavioural\n * pins in `field-control.test.ts` reach them without pulling the whole\n * field-control dispatcher.\n */\n\n/**\n * Move `from` → `to`, returning a NEW array (all elements preserved). The repeater\n * applies the same `(from, to)` to its value array AND its parallel stable-key array,\n * so the two stay aligned through a reorder — a row's child/DOM state (an open\n * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).\n */\n/**\n * The options a multi-select currently holds, read through THE write canonicaliser\n * (EC-586) — so a legacy scalar (`'news'`, written by the old bulk \"set field\") shows\n * as checked instead of nothing, and the next toggle keeps it. The form used to read\n * `Array.isArray(value) ? value : []`, which showed the scalar unchecked and DROPPED it\n * on the next click (measured, round2-B §Q8 1(c)).\n */\nexport function selectedOptions(field: SelectFieldDef, value: unknown): string[] {\n const canonical = selfTypeFieldValue(field, value)\n return Array.isArray(canonical) ? canonical.filter((v): v is string => typeof v === 'string') : []\n}\n\n/** Toggle one option of a multi-select; the next value (an emptied list is absent). */\nexport function toggleSelectOption(field: SelectFieldDef, value: unknown, option: string): string[] | undefined {\n const selected = selectedOptions(field, value)\n const next = selected.includes(option)\n ? selected.filter((v) => v !== option)\n : [...selected, option]\n return next.length === 0 ? undefined : next\n}\n\nexport function reorder<T>(list: readonly T[], from: number, to: number): T[] {\n const next = list.slice()\n const [moved] = next.splice(from, 1)\n if (moved !== undefined) next.splice(to, 0, moved)\n return next\n}\n\nconst SUMMARY_PREFERRED_FIELDS = ['title', 'label', 'name', 'heading', 'text', 'linkType']\n\n/**\n * A scannable one-line summary for a collapsed repeater item (EC-294): the first\n * meaningful scalar subfield value — preferring title-ish field names — else `#n`.\n * Pure (no store): summarises from the item's own scalar values, so it works for\n * any object shape without resolving relations/assets.\n */\nexport function objectItemSummary(\n fields: readonly { name: string }[],\n item: Record<string, unknown>,\n index: number,\n): string {\n const scalar = (name: string): string | null => {\n const v = item[name]\n if (typeof v === 'string' && v.trim()) return v.trim()\n if (typeof v === 'number' || typeof v === 'boolean') return String(v)\n return null\n }\n const names = fields.map((f) => f.name)\n for (const name of SUMMARY_PREFERRED_FIELDS) {\n if (names.includes(name)) {\n const s = scalar(name)\n if (s) return truncateSummary(s)\n }\n }\n for (const name of names) {\n const s = scalar(name)\n if (s) return truncateSummary(s)\n }\n return `#${index + 1}`\n}\n\nfunction truncateSummary(s: string, max = 60): string {\n return s.length > max ? `${s.slice(0, max - 1)}…` : s\n}\n\n// (B) Depth-keyed visuals: a coloured left rail + a matching faint tint, DISTINCT\n// per level so nesting reads at a glance. The outermost array (depth 0) starts at\n// the brand primary — no more neutral, un-encoded first level — then a fixed\n// high-contrast rotation whose adjacent hues never collide (primary→amber→green→red;\n// note `info` is a near-primary blue, so it is deliberately NOT adjacent to primary).\n// It cycles (mod) so pathological depth still renders. Literal classes so Tailwind emits them.\nconst DEPTH_RAIL = ['border-l-primary', 'border-l-warning', 'border-l-success', 'border-l-destructive']\nconst DEPTH_TINT = ['bg-primary/5', 'bg-warning/5', 'bg-success/5', 'bg-destructive/5']\n// `d` is always ≥ 0, so `d % length` is in-bounds — the assertion is sound and gives\n// callers a definite `string` (cn tolerates undefined, but tests read the value).\nexport const depthRail = (d: number) => DEPTH_RAIL[d % DEPTH_RAIL.length]!\nexport const depthTint = (d: number) => DEPTH_TINT[d % DEPTH_TINT.length]!\n\n// (C) An object array renders as a compact TABLE when every subfield is a scalar (a\n// flat row) and there are few enough columns to read; richer/nested object types stay\n// as cards with depth encoding (B). Conditional (function-hidden) subfields force the\n// card path — a table has no place to show/hide a column per row.\nconst TABULAR_FIELD_TYPES = new Set(['text', 'number', 'boolean', 'select', 'date'])\nconst MAX_TABLE_COLUMNS = 4\nexport function isTabularObjectType(field: ObjectFieldDef): boolean {\n return (\n field.fields.length > 0 &&\n field.fields.length <= MAX_TABLE_COLUMNS &&\n field.fields.every(\n (f) => TABULAR_FIELD_TYPES.has(f.type) && typeof f.form?.hidden !== 'function',\n )\n )\n}\n\n/**\n * EC-295: the `options` control — a `select` shown as inline segmented buttons\n * (`[[Page]] [URL] [File]`) instead of a dropdown. Engine built-in (no consumer\n * registration); single-select only. Each option shows `label ?? value` and a\n * `title` tooltip from the option's `description`; the field `default` + `readOnly`\n * flow through the shared form. Picks one — to allow an empty/unset value use the\n * default dropdown control.\n */\nexport function SegmentedOptions({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: SelectFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n const current = typeof value === 'string' ? value : ''\n return (\n <Row gap=\"1\" wrap role=\"radiogroup\" id={id} aria-label={fieldLabel(field)}>\n {field.options.map((opt) => {\n const active = current === opt.value\n return (\n <Button\n key={opt.value}\n type=\"button\"\n variant={active ? 'default' : 'outline'}\n size=\"sm\"\n role=\"radio\"\n aria-checked={active}\n title={localizedLabel(opt.description) || undefined}\n disabled={readOnly}\n onClick={() => onChange(opt.value)}\n >\n {localizedLabel(opt.label, opt.value)}\n </Button>\n )\n })}\n </Row>\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, documentDefaultLocale, findSlugCollision, isFieldLocalized, parentRelationField, resolveSlugPath, resolveUrlSegments, slugSourceValue, } from '@elytracms/core/cms-core';
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';
@@ -19,7 +18,7 @@ import { DocumentUrlSegment } from './document-url-path.js';
19
18
  import { DateInput } from './date-input.js';
20
19
  import { datePickerBounds } from './date-input-core.js';
21
20
  import { AssetField } from './asset-field.js';
22
- import { SegmentedOptions } from './field-control-lib.js';
21
+ import { SegmentedOptions, selectedOptions, toggleSelectOption } from './field-control-lib.js';
23
22
  import { slugAutoDeriveDecision } from './slug-auto-derive.js';
24
23
  import { engineOwnsUniqueness } from './slug-collision-report.js';
25
24
  import { ObjectField } from './object-array-field.js';
@@ -93,10 +92,10 @@ function BuiltInFieldControl({ field, value, onChange, readOnly, id }) {
93
92
  return (_jsx(DateInput, { id: id, value: value, onChange: onChange, disabled: readOnly, testId: `field-date-${field.name}`, ...datePickerBounds(field.validation) }));
94
93
  case 'select':
95
94
  if (field.multiple) {
96
- const selected = Array.isArray(value) ? value : [];
95
+ const selected = selectedOptions(field, value);
97
96
  return (_jsx(Row, { gap: "2", wrap: true, children: field.options.map((opt) => {
98
97
  const on = selected.includes(opt.value);
99
- return (_jsxs("label", { className: "flex items-center gap-1.5 text-sm", children: [_jsx("input", { type: "checkbox", checked: on, disabled: readOnly, onChange: () => onChange(on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value]), className: "size-4 accent-primary" }), localizedLabel(opt.label, opt.value)] }, opt.value));
98
+ return (_jsxs("label", { className: "flex items-center gap-1.5 text-sm", children: [_jsx("input", { type: "checkbox", checked: on, disabled: readOnly, onChange: () => onChange(toggleSelectOption(field, value, opt.value)), className: "size-4 accent-primary" }), localizedLabel(opt.label, opt.value)] }, opt.value));
100
99
  }) }));
101
100
  }
102
101
  // EC-295: the `options` control renders a small fixed enumeration as inline
@@ -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
- const deriveLocale = isFieldLocalized(source.schema, field.name) || !draft
167
- ? locale
168
- : documentDefaultLocale(draft, localeConfig);
169
- const values = useMemo(() => (draft ? activeLocaleValues(draft, deriveLocale) : {}), [draft, deriveLocale]);
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