@riducms/admin 0.2.2 → 0.3.0

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 (352) hide show
  1. package/THIRD_PARTY_NOTICES.md +60 -37
  2. package/package.json +27 -15
  3. package/src/app/admin-core-view-route.svelte +38 -53
  4. package/src/app/admin-extension-route.svelte +19 -0
  5. package/src/app/admin-layout.scss +175 -0
  6. package/src/app/admin-layout.svelte +144 -410
  7. package/src/app/admin-navigation-progress.svelte +11 -77
  8. package/src/app/admin-page-title.svelte +17 -85
  9. package/src/app/admin-page-title.ts +136 -0
  10. package/src/app/admin-route-error.svelte +24 -0
  11. package/src/app/admin-router-provider.svelte +34 -0
  12. package/src/app/admin-router-root.svelte +41 -0
  13. package/src/app/admin-routes.svelte +56 -19
  14. package/src/app/loading-boundary.svelte +3 -1
  15. package/src/app-meta.ts +13 -0
  16. package/src/app.css +138 -496
  17. package/src/app.svelte +60 -26
  18. package/src/components/brand/ridu-logo.svelte +19 -5
  19. package/src/components/data/list-table.scss +63 -0
  20. package/src/components/icons/LICENSE.md +24 -0
  21. package/src/components/icons/chevron.svelte +8 -0
  22. package/src/components/icons/plus.svelte +12 -0
  23. package/src/components/icons/search.svelte +12 -0
  24. package/src/components/icons/x.svelte +8 -0
  25. package/src/components/json-tree/json-tree-node.svelte +61 -67
  26. package/src/components/json-tree/json-viewer.scss +153 -0
  27. package/src/components/json-tree/json-viewer.svelte +57 -96
  28. package/src/components/ui/banner/banner.scss +28 -0
  29. package/src/components/ui/banner/banner.svelte +9 -8
  30. package/src/components/ui/banner/index.ts +1 -5
  31. package/src/components/ui/code-editor/code-editor.scss +20 -0
  32. package/src/components/ui/code-editor/code-editor.svelte +71 -0
  33. package/src/components/ui/code-editor/code-editor.ts +151 -0
  34. package/src/components/ui/code-editor/code-languages.ts +29 -0
  35. package/src/components/ui/collapsible-panel/collapsible-panel.scss +38 -0
  36. package/src/components/ui/collapsible-panel/collapsible-panel.svelte +13 -0
  37. package/src/components/ui/collapsible-panel/index.ts +1 -0
  38. package/src/components/ui/combobox/combobox.scss +163 -0
  39. package/src/components/ui/combobox/index.ts +22 -0
  40. package/src/components/ui/date-value-control/date-value-calendar.svelte +128 -16
  41. package/src/components/ui/date-value-control/date-value-control-loader.scss +26 -0
  42. package/src/components/ui/date-value-control/date-value-control-loader.svelte +35 -13
  43. package/src/components/ui/date-value-control/date-value-control.scss +223 -0
  44. package/src/components/ui/date-value-control/date-value-control.svelte +62 -44
  45. package/src/components/ui/drawer/drawer-depth.ts +8 -0
  46. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -5
  47. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +2 -5
  48. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -2
  49. package/src/components/ui/dropdown-menu/dropdown-menu.scss +62 -0
  50. package/src/components/ui/kbd/kbd.scss +17 -0
  51. package/src/components/ui/kbd/kbd.svelte +3 -10
  52. package/src/components/ui/navigation-drawer/navigation-drawer.scss +40 -0
  53. package/src/components/ui/navigation-drawer/navigation-drawer.svelte +38 -0
  54. package/src/components/ui/popover/popover-content.svelte +3 -5
  55. package/src/components/ui/popover/popover.scss +18 -0
  56. package/src/components/ui/radio-group/index.ts +0 -1
  57. package/src/components/ui/radio-group/radio-card-item.svelte +7 -12
  58. package/src/components/ui/radio-group/radio-group.svelte +2 -2
  59. package/src/components/ui/radio-group/radio.scss +45 -0
  60. package/src/components/ui/select/select-content.svelte +5 -6
  61. package/src/components/ui/select/select-item.svelte +8 -8
  62. package/src/components/ui/select/select-scroll-down-button.svelte +3 -5
  63. package/src/components/ui/select/select-scroll-up-button.svelte +3 -5
  64. package/src/components/ui/select/select-trigger.svelte +6 -7
  65. package/src/components/ui/select/select.scss +126 -0
  66. package/src/components/ui/sheet/sheet-content.svelte +6 -34
  67. package/src/components/ui/sheet/sheet-description.svelte +2 -2
  68. package/src/components/ui/sheet/sheet-overlay.svelte +2 -21
  69. package/src/components/ui/sheet/sheet-title.svelte +2 -2
  70. package/src/components/ui/sheet/sheet.scss +221 -0
  71. package/src/components/ui/skeleton/skeleton.scss +20 -0
  72. package/src/components/ui/skeleton/skeleton.svelte +4 -16
  73. package/src/components/ui/sonner/sonner.scss +121 -0
  74. package/src/components/ui/sonner/sonner.svelte +11 -10
  75. package/src/components/ui/status-indicator/status-indicator.scss +48 -0
  76. package/src/components/ui/status-indicator/status-indicator.svelte +14 -14
  77. package/src/components/ui/tabs/tabs-content.svelte +2 -2
  78. package/src/components/ui/tabs/tabs-list.svelte +7 -7
  79. package/src/components/ui/tabs/tabs-trigger.svelte +2 -8
  80. package/src/components/ui/tabs/tabs.scss +86 -0
  81. package/src/components/ui/tabs/tabs.svelte +2 -2
  82. package/src/components/ui/timezone-picker/timezone-picker.svelte +152 -0
  83. package/src/core/api/admin-client.ts +2 -2
  84. package/src/core/bootstrap/admin-bootstrap.ts +396 -0
  85. package/src/core/bootstrap/admin-prepared-state.ts +213 -0
  86. package/src/core/bootstrap/admin-route-modules.ts +66 -0
  87. package/src/core/clipboard/copy-feedback.svelte.ts +43 -0
  88. package/src/core/forms/embedded-fields.ts +4 -8
  89. package/src/core/forms/field-editor-authoring.ts +8 -0
  90. package/src/core/forms/field-issue-focus.ts +4 -4
  91. package/src/core/forms/field-occurrence.ts +4 -7
  92. package/src/core/forms/form-controller.svelte.ts +14 -21
  93. package/src/core/forms/form-draft-recovery.ts +17 -4
  94. package/src/core/forms/form-issue-correlation.ts +13 -13
  95. package/src/core/forms/form-path.ts +17 -0
  96. package/src/core/forms/form-schema.ts +12 -43
  97. package/src/core/forms/form-validation.ts +7 -46
  98. package/src/core/forms/plugin-field-authoring.ts +6 -2
  99. package/src/core/forms/plugin-field-write.ts +2 -6
  100. package/src/core/i18n/admin-i18n.svelte.ts +18 -0
  101. package/src/core/i18n/format-admin-date-time.ts +19 -0
  102. package/src/core/i18n/humanize-identifier.ts +5 -0
  103. package/src/core/i18n/time-zone-label.ts +20 -0
  104. package/src/core/loaders/admin-view-host.svelte +72 -0
  105. package/src/core/loaders/admin-view-loader.svelte.ts +90 -0
  106. package/src/core/notifications/validation-toast.svelte +2 -2
  107. package/src/core/plugins/field-registry.ts +7 -8
  108. package/src/core/routing/admin-paths.ts +53 -2
  109. package/src/core/routing/admin-scroll.svelte.ts +74 -0
  110. package/src/core/routing/admin-view-selection.ts +78 -0
  111. package/src/core/runtime/admin-runtime.svelte.ts +121 -124
  112. package/src/core/schema/field-default.ts +33 -0
  113. package/src/core/schema/field-localization.ts +15 -0
  114. package/src/core/schema/read-document-path.ts +11 -0
  115. package/src/features/account/account-menu.scss +37 -0
  116. package/src/features/account/account-menu.svelte +115 -0
  117. package/src/features/{auth → account}/account-navigation.svelte +16 -19
  118. package/src/features/{auth → account}/account-route.svelte +206 -49
  119. package/src/features/account/account-security-controller.svelte.ts +157 -0
  120. package/src/features/{auth → account}/account-security-route.svelte +140 -42
  121. package/src/features/api-reference/api-reference-code.svelte +64 -0
  122. package/src/features/api-reference/api-reference-content.svelte +258 -0
  123. package/src/features/api-reference/api-reference-drawer.scss +74 -0
  124. package/src/features/api-reference/api-reference-examples.ts +381 -0
  125. package/src/features/api-reference/api-reference-schema.ts +332 -0
  126. package/src/features/api-reference/api-reference.scss +190 -0
  127. package/src/features/api-reference/api-reference.svelte +74 -0
  128. package/src/features/auth/auth-brand.svelte +5 -3
  129. package/src/features/auth/auth-field.svelte +92 -0
  130. package/src/features/auth/auth-frame.svelte +5 -8
  131. package/src/features/auth/auth.scss +122 -0
  132. package/src/features/auth/create-first-user-controller.svelte.ts +20 -51
  133. package/src/features/auth/create-first-user-route.svelte +83 -122
  134. package/src/features/auth/forgot-password-route.svelte +31 -23
  135. package/src/features/auth/login-route.svelte +68 -116
  136. package/src/features/auth/logout-button.scss +34 -0
  137. package/src/features/auth/logout-button.svelte +52 -0
  138. package/src/features/auth/request-verification-route.svelte +27 -22
  139. package/src/features/auth/reset-password-route.svelte +108 -41
  140. package/src/features/auth/verify-email-route.svelte +49 -15
  141. package/src/features/bulk-edit/bulk-edit-fields.ts +23 -0
  142. package/src/features/bulk-edit/bulk-edit.scss +152 -0
  143. package/src/features/bulk-edit/field-picker.svelte +176 -0
  144. package/src/features/collections/bulk/bulk-actions.svelte +148 -0
  145. package/src/features/collections/bulk/bulk-confirmation.svelte +153 -0
  146. package/src/features/collections/bulk/bulk-editor-loader.svelte +95 -0
  147. package/src/features/collections/bulk/bulk-editor.svelte +186 -0
  148. package/src/features/collections/collection-list-cell-values.ts +144 -0
  149. package/src/features/collections/collection-list-controller.svelte.ts +268 -379
  150. package/src/features/collections/collection-list-preferences-controller.svelte.ts +54 -40
  151. package/src/features/collections/collection-list-query.ts +223 -0
  152. package/src/features/collections/collection-list-results.svelte +205 -329
  153. package/src/features/collections/collection-list-route.svelte +143 -884
  154. package/src/features/collections/collection-list.scss +261 -0
  155. package/src/features/collections/collection-list.svelte.ts +362 -0
  156. package/src/features/collections/controls/column-picker.svelte +110 -0
  157. package/src/features/collections/controls/column-pill.svelte +62 -0
  158. package/src/features/collections/controls/filter-builder.svelte +193 -0
  159. package/src/features/collections/controls/filter-row.svelte +142 -0
  160. package/src/features/collections/controls/list-controls.scss +196 -0
  161. package/src/features/collections/controls/list-options.svelte +203 -0
  162. package/src/features/collections/controls/list-pagination.scss +78 -0
  163. package/src/features/collections/controls/list-pagination.svelte +115 -0
  164. package/src/features/collections/controls/list-toolbar.svelte +132 -0
  165. package/src/features/collections/controls/sort-heading.scss +52 -0
  166. package/src/features/collections/controls/sort-heading.svelte +78 -0
  167. package/src/features/collections/list-columns.ts +43 -0
  168. package/src/features/collections/list-workspace.ts +155 -54
  169. package/src/features/dashboard/dashboard-route.svelte +82 -86
  170. package/src/features/dashboard/dashboard.scss +131 -0
  171. package/src/features/development/development-tools.svelte +20 -36
  172. package/src/features/documents/document-action-bar.svelte +438 -0
  173. package/src/features/documents/document-api-view-loader.svelte +17 -6
  174. package/src/features/documents/document-api-view.scss +120 -0
  175. package/src/features/documents/document-api-view.svelte +169 -454
  176. package/src/features/documents/document-controller.svelte.ts +526 -613
  177. package/src/features/documents/document-credentials.scss +37 -0
  178. package/src/features/documents/document-credentials.svelte +72 -0
  179. package/src/features/documents/{document-route-dirty.ts → document-credentials.ts} +25 -0
  180. package/src/features/documents/document-field-sections.scss +39 -0
  181. package/src/features/documents/document-field-sections.svelte +4 -13
  182. package/src/features/documents/document-loading.scss +24 -0
  183. package/src/features/documents/document-lock-controller.svelte.ts +187 -0
  184. package/src/features/documents/document-operation-dialogs.svelte +110 -0
  185. package/src/features/documents/document-route-loader.svelte +17 -5
  186. package/src/features/documents/document-route.svelte +305 -811
  187. package/src/features/documents/document-schedule.scss +127 -0
  188. package/src/features/documents/document-schedule.svelte +323 -0
  189. package/src/features/documents/document-title.ts +25 -0
  190. package/src/features/documents/document.scss +274 -0
  191. package/src/features/documents/live-preview-panel.scss +117 -0
  192. package/src/features/documents/live-preview-panel.svelte +167 -168
  193. package/src/features/documents/live-preview.ts +2 -10
  194. package/src/features/documents/upload-document-preview-loader.svelte +26 -23
  195. package/src/features/localization/content-locale-switcher.scss +43 -0
  196. package/src/features/localization/content-locale-switcher.svelte +24 -17
  197. package/src/features/navigation/admin-breadcrumbs.scss +56 -0
  198. package/src/features/navigation/admin-breadcrumbs.svelte +102 -37
  199. package/src/features/navigation/admin-command-menu.svelte +38 -15
  200. package/src/features/navigation/admin-navigation.scss +133 -0
  201. package/src/features/navigation/admin-navigation.svelte +169 -0
  202. package/src/features/navigation/breadcrumb-context.svelte.ts +15 -0
  203. package/src/features/navigation/navigation-preference.svelte.ts +70 -0
  204. package/src/features/reference-browser/reference-browser-editor.svelte +180 -0
  205. package/src/features/reference-browser/reference-browser-list.scss +113 -0
  206. package/src/features/reference-browser/reference-browser-list.svelte +370 -0
  207. package/src/features/reference-browser/reference-browser-loader.svelte +26 -8
  208. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +335 -236
  209. package/src/features/reference-browser/reference-browser.scss +217 -0
  210. package/src/features/reference-browser/reference-browser.svelte +154 -797
  211. package/src/features/reference-browser/reference-collection-picker.svelte +116 -0
  212. package/src/features/reference-browser/reference-list-query.ts +35 -0
  213. package/src/features/uploads/bulk-upload-controller.svelte.ts +604 -0
  214. package/src/features/uploads/bulk-upload-edit-all.svelte +92 -0
  215. package/src/features/uploads/bulk-upload-editor.svelte +191 -0
  216. package/src/features/uploads/bulk-upload-file-picker.svelte +70 -0
  217. package/src/features/uploads/bulk-upload-remote.svelte +100 -0
  218. package/src/features/uploads/bulk-upload-route-loader.svelte +54 -0
  219. package/src/features/uploads/bulk-upload-route.svelte +172 -592
  220. package/src/features/uploads/bulk-upload-sidebar.svelte +125 -0
  221. package/src/features/uploads/bulk-upload.scss +526 -0
  222. package/src/features/uploads/bulk-upload.ts +105 -14
  223. package/src/features/uploads/crop-surface.ts +150 -0
  224. package/src/features/uploads/image-editor.svelte +231 -0
  225. package/src/features/uploads/rendition-preview.svelte +64 -0
  226. package/src/features/uploads/upload-control.svelte +340 -0
  227. package/src/features/uploads/upload-document-contracts.ts +30 -0
  228. package/src/features/uploads/upload-draft.svelte.ts +294 -0
  229. package/src/features/uploads/upload-metadata-fields.scss +32 -0
  230. package/src/features/uploads/upload-metadata-fields.svelte +8 -7
  231. package/src/features/uploads/upload-thumbnail.svelte +47 -0
  232. package/src/features/uploads/upload.scss +361 -0
  233. package/src/features/versions/version-comparison.svelte +361 -0
  234. package/src/features/versions/version-diff-fields.svelte +136 -0
  235. package/src/features/versions/version-diff.ts +296 -41
  236. package/src/features/versions/version-history-controller.svelte.ts +311 -0
  237. package/src/features/versions/version-history-route-loader.svelte +32 -0
  238. package/src/features/versions/version-history-route.svelte +157 -522
  239. package/src/features/versions/version-history.scss +444 -0
  240. package/src/features/versions/version-history.ts +45 -0
  241. package/src/features/versions/version-table.svelte +91 -0
  242. package/src/fields/embedded-schema-draft-editor.scss +48 -0
  243. package/src/fields/embedded-schema-draft-editor.svelte +24 -16
  244. package/src/fields/field-collapsible.svelte +14 -8
  245. package/src/fields/field-layout.scss +171 -0
  246. package/src/fields/field-layout.svelte +3 -5
  247. package/src/fields/field-renderer.svelte +17 -29
  248. package/src/fields/field-shell.svelte +1 -1
  249. package/src/fields/field-tab-layout.svelte +25 -19
  250. package/src/fields/join/join-field-controller.svelte.ts +513 -0
  251. package/src/fields/join/join-field-values.ts +111 -0
  252. package/src/fields/join/join-field.scss +189 -0
  253. package/src/fields/join/join-field.svelte +164 -332
  254. package/src/fields/live-validation-feedback.svelte +4 -3
  255. package/src/fields/local-field-editor.svelte +2 -2
  256. package/src/fields/nested/block-name-input.svelte +11 -12
  257. package/src/fields/nested/block-picker.scss +128 -0
  258. package/src/fields/nested/block-picker.svelte +81 -0
  259. package/src/fields/nested/block-placeholder.svg +2 -0
  260. package/src/fields/nested/nested-field.scss +331 -0
  261. package/src/fields/nested/nested-field.svelte +265 -245
  262. package/src/fields/nested/sortable-row.svelte +9 -7
  263. package/src/fields/plugin-field.svelte +4 -6
  264. package/src/fields/point/point-field.svelte +51 -38
  265. package/src/fields/primitive-list/primitive-list-field.svelte +63 -19
  266. package/src/fields/primitive-list/primitive-list.scss +109 -0
  267. package/src/fields/radio/radio-field.svelte +2 -1
  268. package/src/fields/relationship/relationship-field-controller.svelte.ts +81 -41
  269. package/src/fields/relationship/relationship-field.svelte +144 -175
  270. package/src/fields/relationship/relationship-lookup-controller.svelte.ts +58 -30
  271. package/src/fields/relationship/relationship-query.ts +2 -10
  272. package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +36 -62
  273. package/src/fields/relationship/relationship-quick-picker.svelte +178 -227
  274. package/src/fields/relationship/relationship-selection.svelte +120 -0
  275. package/src/fields/relationship/relationship-value.ts +46 -7
  276. package/src/fields/relationship/relationship.scss +182 -0
  277. package/src/fields/scalar/code-field.svelte +101 -0
  278. package/src/fields/scalar/date-control-value.ts +13 -0
  279. package/src/fields/scalar/scalar-field.svelte +25 -67
  280. package/src/fields/select/select-field.svelte +132 -94
  281. package/src/fields/text/slug-field.scss +28 -0
  282. package/src/fields/text/slug-field.svelte +19 -15
  283. package/src/fields/text/text-field.svelte +1 -2
  284. package/src/fields/ui/ui-field.svelte +7 -4
  285. package/src/fields/virtual/virtual-field.svelte +2 -5
  286. package/src/index.ts +1 -2
  287. package/src/routing.ts +2 -0
  288. package/src/app/deferred-redirect.svelte +0 -23
  289. package/src/components/code-block/code-block.svelte +0 -107
  290. package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +0 -50
  291. package/src/components/ui/bulk-action-bar/index.ts +0 -1
  292. package/src/components/ui/button/index.ts +0 -8
  293. package/src/components/ui/calendar/calendar-caption.svelte +0 -72
  294. package/src/components/ui/calendar/calendar-cell.svelte +0 -20
  295. package/src/components/ui/calendar/calendar-day.svelte +0 -27
  296. package/src/components/ui/calendar/calendar-grid-row.svelte +0 -13
  297. package/src/components/ui/calendar/calendar-grid.svelte +0 -17
  298. package/src/components/ui/calendar/calendar-head-cell.svelte +0 -20
  299. package/src/components/ui/calendar/calendar-header.svelte +0 -20
  300. package/src/components/ui/calendar/calendar-heading.svelte +0 -17
  301. package/src/components/ui/calendar/calendar-month-select.svelte +0 -45
  302. package/src/components/ui/calendar/calendar-month.svelte +0 -16
  303. package/src/components/ui/calendar/calendar-months.svelte +0 -20
  304. package/src/components/ui/calendar/calendar-nav.svelte +0 -20
  305. package/src/components/ui/calendar/calendar-next-button.svelte +0 -30
  306. package/src/components/ui/calendar/calendar-prev-button.svelte +0 -30
  307. package/src/components/ui/calendar/calendar-year-select.svelte +0 -44
  308. package/src/components/ui/calendar/calendar.svelte +0 -118
  309. package/src/components/ui/calendar/index.ts +0 -1
  310. package/src/components/ui/checkbox/checkbox.svelte +0 -35
  311. package/src/components/ui/checkbox/index.ts +0 -1
  312. package/src/components/ui/dialog/dialog-close.svelte +0 -11
  313. package/src/components/ui/dialog/dialog-content.svelte +0 -52
  314. package/src/components/ui/dialog/dialog-description.svelte +0 -20
  315. package/src/components/ui/dialog/dialog-footer.svelte +0 -20
  316. package/src/components/ui/dialog/dialog-header.svelte +0 -20
  317. package/src/components/ui/dialog/dialog-overlay.svelte +0 -20
  318. package/src/components/ui/dialog/dialog-title.svelte +0 -17
  319. package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
  320. package/src/components/ui/dialog/dialog.svelte +0 -7
  321. package/src/components/ui/dialog/index.ts +0 -8
  322. package/src/components/ui/empty-state/empty-state.svelte +0 -34
  323. package/src/components/ui/empty-state/index.ts +0 -1
  324. package/src/components/ui/input/index.ts +0 -1
  325. package/src/components/ui/pagination/index.ts +0 -1
  326. package/src/components/ui/pagination/pagination.svelte +0 -88
  327. package/src/components/ui/radio-group/radio-group-item.svelte +0 -17
  328. package/src/components/ui/slider/index.ts +0 -3
  329. package/src/components/ui/slider/slider-content.svelte +0 -59
  330. package/src/components/ui/slider/slider.svelte +0 -35
  331. package/src/components/ui/table/index.ts +0 -6
  332. package/src/components/ui/table/table-body.svelte +0 -20
  333. package/src/components/ui/table/table-cell.svelte +0 -23
  334. package/src/components/ui/table/table-head.svelte +0 -23
  335. package/src/components/ui/table/table-header.svelte +0 -20
  336. package/src/components/ui/table/table-row.svelte +0 -23
  337. package/src/components/ui/table/table.svelte +0 -22
  338. package/src/components/ui/textarea/index.ts +0 -1
  339. package/src/components/ui/tooltip/index.ts +0 -4
  340. package/src/components/ui/tooltip/tooltip-content.svelte +0 -50
  341. package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
  342. package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
  343. package/src/components/ui/tooltip/tooltip.svelte +0 -7
  344. package/src/features/auth/account-menu.svelte +0 -159
  345. package/src/features/auth/account-security-controller.svelte.ts +0 -119
  346. package/src/features/collections/collection-list-bulk-actions.svelte +0 -143
  347. package/src/features/collections/collection-list-bulk-editor.svelte +0 -157
  348. package/src/features/collections/collection-list-filter-popover.svelte +0 -238
  349. package/src/features/collections/collection-list-workspace-toolbar.svelte +0 -409
  350. package/src/features/collections/collection-table-skeleton.svelte +0 -44
  351. package/src/features/documents/document-heading.ts +0 -20
  352. package/src/features/documents/upload-document-preview.svelte +0 -525
@@ -0,0 +1,43 @@
1
+ export type CopyFeedbackResult = Readonly<{
2
+ source: string;
3
+ copied: boolean;
4
+ }>;
5
+
6
+ /** Own short-lived clipboard feedback for a component instance. */
7
+ export class CopyFeedback {
8
+ result = $state.raw<CopyFeedbackResult>();
9
+
10
+ #active = true;
11
+ #revision = 0;
12
+ #timer: number | undefined;
13
+
14
+ constructor(private readonly duration = 1_500) {
15
+ $effect(() => () => {
16
+ this.#active = false;
17
+ this.#revision += 1;
18
+ if (this.#timer !== undefined) window.clearTimeout(this.#timer);
19
+ });
20
+ }
21
+
22
+ async copy(source: string) {
23
+ const revision = ++this.#revision;
24
+ if (this.#timer !== undefined) window.clearTimeout(this.#timer);
25
+ this.#timer = undefined;
26
+ this.result = undefined;
27
+
28
+ let copied = true;
29
+ try {
30
+ await navigator.clipboard.writeText(source);
31
+ } catch {
32
+ copied = false;
33
+ }
34
+
35
+ if (!this.#active || revision !== this.#revision) return;
36
+ this.result = { source, copied };
37
+ this.#timer = window.setTimeout(() => {
38
+ if (!this.#active || revision !== this.#revision) return;
39
+ this.result = undefined;
40
+ this.#timer = undefined;
41
+ }, this.duration);
42
+ }
43
+ }
@@ -1,4 +1,4 @@
1
- import { resolveBlockTypes } from "@riducms/protocol";
1
+ import { isRecord, resolveBlockTypes } from "@riducms/protocol";
2
2
  import type {
3
3
  SchemaBlockType,
4
4
  SchemaEmbeddedTree,
@@ -32,7 +32,7 @@ export function embeddedOccurrences(field: SchemaField, value: unknown, path = f
32
32
  let root: unknown = value;
33
33
  let rootPath = path;
34
34
  for (const segment of tree.root) {
35
- root = isEmbeddedRecord(root) ? root[segment] : undefined;
35
+ root = isRecord(root) ? root[segment] : undefined;
36
36
  rootPath += `.${segment}`;
37
37
  }
38
38
  const keys = new Set<string>();
@@ -41,7 +41,7 @@ export function embeddedOccurrences(field: SchemaField, value: unknown, path = f
41
41
  fail(nodePath, "Embedded field traversal budget exceeded", "embedded_budget");
42
42
  return;
43
43
  }
44
- if (!isEmbeddedRecord(node)) {
44
+ if (!isRecord(node)) {
45
45
  fail(nodePath, "Embedded tree node must be an object");
46
46
  return;
47
47
  }
@@ -49,7 +49,7 @@ export function embeddedOccurrences(field: SchemaField, value: unknown, path = f
49
49
  if (branch !== undefined) {
50
50
  const payload = node[branch.payload];
51
51
  const payloadPath = `${nodePath}.${branch.payload}`;
52
- if (!isEmbeddedRecord(payload)) {
52
+ if (!isRecord(payload)) {
53
53
  fail(payloadPath, "Embedded payload must be an object");
54
54
  } else {
55
55
  const block = resolveBlockTypes(branch).find(
@@ -126,7 +126,3 @@ export function transformEmbeddedPayloads(
126
126
  }
127
127
  return value;
128
128
  }
129
-
130
- export function isEmbeddedRecord(value: unknown): value is Record<string, unknown> {
131
- return typeof value === "object" && value !== null && !Array.isArray(value);
132
- }
@@ -22,6 +22,10 @@ export function guardEditorAuthoring(
22
22
  return document;
23
23
  });
24
24
  };
25
+ const canCreateDocument: EditorAuthoring["canCreateDocument"] = (collection) => {
26
+ assertActive();
27
+ return authoring().canCreateDocument(collection);
28
+ };
25
29
  return {
26
30
  get collections() {
27
31
  assertActive();
@@ -35,6 +39,10 @@ export function guardEditorAuthoring(
35
39
  assertActive();
36
40
  return authoring().locale;
37
41
  },
42
+ get canCreateDocument() {
43
+ assertActive();
44
+ return canCreateDocument;
45
+ },
38
46
  get referenceBrowser() {
39
47
  assertActive();
40
48
  return referenceBrowser;
@@ -3,7 +3,7 @@ import { tick } from "svelte";
3
3
  export const fieldIssueRevealEvent = "ridu:reveal-field-issue";
4
4
 
5
5
  /** Open lazy field disclosures and repeated rows before focusing the invalid control. */
6
- export async function focusFieldIssue(path: string) {
6
+ export async function focusFieldIssue(path: string, root: HTMLElement) {
7
7
  const selector = `[data-field-path="${CSS.escape(path)}"]`;
8
8
  let previous: HTMLElement | null = null;
9
9
  // Each reveal can mount the next schema boundary. Bound the search by the
@@ -12,7 +12,7 @@ export async function focusFieldIssue(path: string) {
12
12
  const segments = path.split(".");
13
13
  let container: HTMLElement | null = null;
14
14
  while (segments.length > 0 && container === null) {
15
- container = document.querySelector<HTMLElement>(
15
+ container = root.querySelector<HTMLElement>(
16
16
  `[data-field-path="${CSS.escape(segments.join("."))}"]`
17
17
  );
18
18
  segments.pop();
@@ -21,7 +21,7 @@ export async function focusFieldIssue(path: string) {
21
21
  previous = container;
22
22
  for (
23
23
  let ancestor = container.parentElement;
24
- ancestor !== null;
24
+ ancestor !== null && root.contains(ancestor);
25
25
  ancestor = ancestor.parentElement
26
26
  ) {
27
27
  if (ancestor instanceof HTMLDetailsElement && ancestor.hasAttribute("data-field-collapsible"))
@@ -29,7 +29,7 @@ export async function focusFieldIssue(path: string) {
29
29
  }
30
30
  container.dispatchEvent(new Event(fieldIssueRevealEvent, { bubbles: true }));
31
31
  await tick();
32
- const revealed = document.querySelector<HTMLElement>(selector);
32
+ const revealed = root.querySelector<HTMLElement>(selector);
33
33
  const control = revealed?.querySelector<HTMLElement>(
34
34
  "input:not([disabled]), textarea:not([disabled]), [contenteditable='true'], button:not([disabled]), [tabindex]:not([tabindex='-1'])"
35
35
  );
@@ -1,4 +1,4 @@
1
- import { resolveBlockTypes } from "@riducms/protocol";
1
+ import { isRecord, resolveBlockTypes } from "@riducms/protocol";
2
2
  import type { SchemaField } from "@riducms/protocol";
3
3
  import type { FormController } from "@admin/core/forms/form-controller.svelte";
4
4
  import { scopeRepeatedRowField } from "@admin/fields/nested/scoped-field";
@@ -34,11 +34,11 @@ export function captureFieldOccurrence(form: FormController, target: string): Fi
34
34
  if (field.type === "group") {
35
35
  // An absent optional object may initialize through its visible inputs.
36
36
  // Once present, removing it revokes the child occurrence immediately.
37
- let initialized = record(form.get(current));
37
+ let initialized = isRecord(form.get(current));
38
38
  const objectPath = () => {
39
39
  const current = path();
40
40
  if (current === undefined) return;
41
- if (record(form.get(current))) initialized = true;
41
+ if (isRecord(form.get(current))) initialized = true;
42
42
  else if (initialized) return;
43
43
  return current;
44
44
  };
@@ -50,7 +50,7 @@ export function captureFieldOccurrence(form: FormController, target: string): Fi
50
50
  const index = Number(target.slice(current.length + 1).split(".")[0]);
51
51
  if (!Array.isArray(rows) || !Number.isSafeInteger(index)) continue;
52
52
  const row: unknown = rows[index];
53
- if (!record(row) || typeof row._key !== "string" || !row._key)
53
+ if (!isRecord(row) || typeof row._key !== "string" || !row._key)
54
54
  throw new Error(`Cannot bind ${target}: rows require stable _key values.`);
55
55
  const key = row._key,
56
56
  variant = row.blockType;
@@ -100,6 +100,3 @@ export function captureFieldOccurrence(form: FormController, target: string): Fi
100
100
  throw new Error(`Cannot bind ${target}: no unique live schema field occurrence exists.`);
101
101
  return found;
102
102
  }
103
- function record(value: unknown): value is Record<string, unknown> {
104
- return typeof value === "object" && value !== null && !Array.isArray(value);
105
- }
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "@riducms/protocol";
1
2
  import {
2
3
  LiveValidationController,
3
4
  type LiveValidationTransport,
@@ -29,6 +30,7 @@ import {
29
30
  submissionFormValues,
30
31
  } from "@admin/core/forms/form-schema";
31
32
  import { validateFormValues, unknownBlockIssues } from "@admin/core/forms/form-validation";
33
+ import { readFormPath } from "@admin/core/forms/form-path";
32
34
 
33
35
  export type { DetachedDraftValue, FormValues } from "@admin/core/forms/form-schema";
34
36
 
@@ -263,7 +265,8 @@ export class FormController {
263
265
  path: string,
264
266
  sourcePath: string,
265
267
  derive: (source: unknown) => string,
266
- follows: (current: unknown, source: unknown) => boolean
268
+ follows: (current: unknown, source: unknown) => boolean,
269
+ initial?: { current: unknown; source: unknown }
267
270
  ): DerivedTextBinding {
268
271
  let binding = this.#derivedTextBindings.get(path);
269
272
  if (binding !== undefined && binding.sourcePath !== sourcePath) {
@@ -272,10 +275,11 @@ export class FormController {
272
275
  binding = undefined;
273
276
  }
274
277
  if (binding === undefined) {
275
- const source = this.get(sourcePath);
278
+ const source = initial === undefined ? this.get(sourcePath) : initial.source;
279
+ const current = initial === undefined ? this.get(path) : initial.current;
276
280
  const state: DerivedTextBindingState = {
277
281
  sourcePath,
278
- following: follows(this.get(path), source),
282
+ following: follows(current, source),
279
283
  derive,
280
284
  stop: () => undefined,
281
285
  };
@@ -284,7 +288,10 @@ export class FormController {
284
288
  });
285
289
  this.#derivedTextBindings.set(path, state);
286
290
  binding = state;
287
- if (state.following) this.#setDerivedText(path, state.derive(source));
291
+ if (state.following) {
292
+ const derived = state.derive(source);
293
+ if (String(current ?? "") !== derived) this.set(path, derived);
294
+ }
288
295
  }
289
296
  const active = binding;
290
297
  return {
@@ -302,7 +309,7 @@ export class FormController {
302
309
  get(path: string): unknown {
303
310
  if (this.#readContext !== undefined && !Object.hasOwn(this.values, path.split(".")[0] ?? ""))
304
311
  return this.#readContext.get(path);
305
- return readPath(this.values, path);
312
+ return readFormPath(this.values, path);
306
313
  }
307
314
 
308
315
  /** Shared identity lookups: a mounted header never scans every sibling on its own. */
@@ -628,6 +635,7 @@ export class FormController {
628
635
  this.original = result.original;
629
636
  this.issues = [];
630
637
  this.#submittingIssues = [];
638
+ this.submitting = false;
631
639
  this.revision = ++this.#revision;
632
640
  return { detached: result.detached, restoredFields: 0 };
633
641
  }
@@ -646,6 +654,7 @@ export class FormController {
646
654
  this.original = result.original;
647
655
  this.issues = [];
648
656
  this.#submittingIssues = [];
657
+ this.submitting = false;
649
658
  this.revision = ++this.#revision;
650
659
  return { detached: result.detached, restoredFields: result.restoredFields };
651
660
  }
@@ -830,18 +839,6 @@ function uniqueValidationIssues(issues: readonly ValidationIssue[]) {
830
839
  return [...unique.values()];
831
840
  }
832
841
 
833
- function readPath(values: FormValues, path: string) {
834
- let current: unknown = values;
835
- for (const segment of path.split(".")) {
836
- if (Array.isArray(current)) {
837
- current = current[Number(segment)];
838
- } else if (isRecord(current)) {
839
- current = current[segment];
840
- } else return undefined;
841
- }
842
- return current;
843
- }
844
-
845
842
  function writePath(values: FormValues, path: string, value: unknown) {
846
843
  const segments = path.split(".");
847
844
  let current: FormValues | unknown[] = values;
@@ -869,10 +866,6 @@ function setChild(container: FormValues | unknown[], segment: string, value: unk
869
866
  else container[segment] = value;
870
867
  }
871
868
 
872
- function isRecord(value: unknown): value is FormValues {
873
- return typeof value === "object" && value !== null && !Array.isArray(value);
874
- }
875
-
876
869
  function embeddedOccurrenceKey(occurrence: EmbeddedOccurrence) {
877
870
  return JSON.stringify([
878
871
  occurrence.tree.key,
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "@riducms/protocol";
1
2
  import type { SchemaCollection } from "@riducms/protocol";
2
3
 
3
4
  import { cloneFormValues, type FormValues } from "@admin/core/forms/form-schema";
@@ -52,6 +53,22 @@ export function takeFormDraft(
52
53
  }
53
54
  }
54
55
 
56
+ /** Read a valid recovery checkpoint without consuming it. Startup uses this to
57
+ * finish data-dependent access before the owning form adopts the draft. */
58
+ export function peekFormDraft(
59
+ collectionID: string,
60
+ documentID: string | undefined
61
+ ): FormDraftCheckpoint | undefined {
62
+ try {
63
+ const encoded = sessionStorage.getItem(storageKey(collectionID, documentID));
64
+ if (encoded === null) return undefined;
65
+ const decoded: unknown = JSON.parse(encoded);
66
+ return isCheckpoint(decoded, collectionID, documentID) ? decoded : undefined;
67
+ } catch {
68
+ return undefined;
69
+ }
70
+ }
71
+
55
72
  export function clearFormDraft(collectionID: string, documentID: string | undefined): void {
56
73
  try {
57
74
  sessionStorage.removeItem(storageKey(collectionID, documentID));
@@ -74,7 +91,3 @@ function isCheckpoint(
74
91
  if (value.documentID !== documentID) return false;
75
92
  return isRecord(value.values) && isRecord(value.original) && typeof value.createdAt === "string";
76
93
  }
77
-
78
- function isRecord(value: unknown): value is Record<string, unknown> {
79
- return typeof value === "object" && value !== null && !Array.isArray(value);
80
- }
@@ -1,6 +1,6 @@
1
- import { resolveBlockTypes } from "@riducms/protocol";
1
+ import { isRecord, resolveBlockTypes } from "@riducms/protocol";
2
2
  import type { SchemaField, ValidationIssue } from "@riducms/protocol";
3
- import { embeddedOccurrences, isEmbeddedRecord } from "@admin/core/forms/embedded-fields";
3
+ import { embeddedOccurrences } from "@admin/core/forms/embedded-fields";
4
4
 
5
5
  export interface FieldValueLocation {
6
6
  schema?: SchemaField;
@@ -64,7 +64,7 @@ export function indexFieldValues(fields: readonly SchemaField[], values: Record<
64
64
  path: string,
65
65
  token: readonly string[]
66
66
  ) => {
67
- if (!isEmbeddedRecord(value)) return;
67
+ if (!isRecord(value)) return;
68
68
  for (const child of children)
69
69
  visit(child, value[child.name], `${path}.${child.name}`, [...token, child.id]);
70
70
  };
@@ -74,12 +74,11 @@ export function indexFieldValues(fields: readonly SchemaField[], values: Record<
74
74
  if ((field.type === "array" || field.type === "blocks") && Array.isArray(value)) {
75
75
  const keys = new Map<string, number>();
76
76
  for (const row of value) {
77
- if (isEmbeddedRecord(row) && typeof row._key === "string")
77
+ if (isRecord(row) && typeof row._key === "string")
78
78
  keys.set(row._key, (keys.get(row._key) ?? 0) + 1);
79
79
  }
80
80
  for (const [i, row] of value.entries()) {
81
- if (!isEmbeddedRecord(row) || typeof row._key !== "string" || keys.get(row._key) !== 1)
82
- continue;
81
+ if (!isRecord(row) || typeof row._key !== "string" || keys.get(row._key) !== 1) continue;
83
82
  const block = resolveBlockTypes(field.blocks).find(
84
83
  (candidate) => candidate.slug === row.blockType
85
84
  );
@@ -109,6 +108,12 @@ export function indexFieldValues(fields: readonly SchemaField[], values: Record<
109
108
  return locations;
110
109
  }
111
110
 
111
+ // An index below a schema-owned location has no stable identity of its own.
112
+ // After the container changes, neither issues nor access may follow that index.
113
+ export function canRebaseIndexedSuffix(suffix: string, before: unknown, after: unknown) {
114
+ return !/\.\d+(?:\.|$)/.test(suffix) || JSON.stringify(before) === JSON.stringify(after);
115
+ }
116
+
112
117
  function correlate(
113
118
  before: FieldValueLocation[],
114
119
  after: FieldValueLocation[],
@@ -137,12 +142,7 @@ function correlate(
137
142
  const target = current.get(source.token);
138
143
  if (target === undefined) return [];
139
144
  const suffix = issue.path.slice(source.path.length);
140
- // An envelope index without a schema-owned identity is ambiguous after edits.
141
- if (
142
- /\.\d+(?:\.|$)/.test(suffix) &&
143
- JSON.stringify(source.value) !== JSON.stringify(target.value)
144
- )
145
- return [];
145
+ if (!canRebaseIndexedSuffix(suffix, source.value, target.value)) return [];
146
146
  if (JSON.stringify(at(source.value, suffix)) !== JSON.stringify(at(target.value, suffix)))
147
147
  return [];
148
148
  return [{ ...issue, path: target.path + suffix }];
@@ -152,7 +152,7 @@ function correlate(
152
152
  function at(value: unknown, suffix: string) {
153
153
  for (const segment of suffix.split(".").filter(Boolean)) {
154
154
  if (Array.isArray(value)) value = value[Number(segment)];
155
- else if (isEmbeddedRecord(value)) value = value[segment];
155
+ else if (isRecord(value)) value = value[segment];
156
156
  else return undefined;
157
157
  }
158
158
  return value;
@@ -0,0 +1,17 @@
1
+ import { isRecord } from "@riducms/protocol";
2
+
3
+ // Form paths address concrete row indexes as well as object fields. Document
4
+ // display paths deliberately stop at arrays; keep those semantics separate.
5
+ export function readFormPath(values: unknown, path: string): unknown {
6
+ let current = values;
7
+ for (const segment of path.split(".")) {
8
+ if (Array.isArray(current)) current = current[Number(segment)];
9
+ else if (isRecord(current)) current = current[segment];
10
+ else return undefined;
11
+ }
12
+ return current;
13
+ }
14
+
15
+ export function joinFormPath(parent: string, child: string) {
16
+ return parent === "" ? child : `${parent}.${child}`;
17
+ }
@@ -1,5 +1,7 @@
1
- import { resolveBlockTypes } from "@riducms/protocol";
1
+ import { isRecord, resolveBlockTypes } from "@riducms/protocol";
2
2
  import { embeddedOccurrences, transformEmbeddedPayloads } from "@admin/core/forms/embedded-fields";
3
+ import { joinFormPath, readFormPath } from "@admin/core/forms/form-path";
4
+ import { schemaFieldDefault } from "@admin/core/schema/field-default";
3
5
  import type { SchemaField } from "@riducms/protocol";
4
6
 
5
7
  export type FormValues = Record<string, unknown>;
@@ -52,7 +54,7 @@ export function shouldSubmitLocalizedPath(
52
54
  return (
53
55
  source === undefined ||
54
56
  source === locale ||
55
- !deepEqual(readPath(values, path), readPath(original, provenancePath))
57
+ !deepEqual(readFormPath(values, path), readFormPath(original, provenancePath))
56
58
  );
57
59
  }
58
60
 
@@ -103,7 +105,7 @@ function submissionRecord(
103
105
  const values = isRecord(input) ? input : {};
104
106
  const submitted: FormValues = {};
105
107
  for (const field of fields) {
106
- const path = joinPath(prefix, field.name);
108
+ const path = joinFormPath(prefix, field.name);
107
109
  if (
108
110
  field.category === "presentation" ||
109
111
  !include(path, field.path) ||
@@ -195,18 +197,6 @@ function localizationProvenancePath(
195
197
  return resolved.join(".");
196
198
  }
197
199
 
198
- function readPath(values: unknown, path: string) {
199
- let current = values;
200
- for (const segment of path.split(".")) {
201
- current = Array.isArray(current)
202
- ? current[Number(segment)]
203
- : isRecord(current)
204
- ? current[segment]
205
- : undefined;
206
- }
207
- return current;
208
- }
209
-
210
200
  export function reconcileFormSchema(
211
201
  state: FormSchemaState,
212
202
  previousFields: readonly SchemaField[],
@@ -360,7 +350,7 @@ function reconcileFieldValue(
360
350
  currentValue,
361
351
  originalValue,
362
352
  detached,
363
- joinPath(parentPath, next.name),
353
+ joinFormPath(parentPath, next.name),
364
354
  initializeDefaults
365
355
  );
366
356
  return {
@@ -406,7 +396,7 @@ function reconcileFieldValue(
406
396
  ) {
407
397
  const reconcilePayloads = (value: unknown, before: unknown, report: DetachedDraftValue[]) => {
408
398
  const copy = cloneFormValue(value);
409
- const fieldPath = joinPath(parentPath, next.name);
399
+ const fieldPath = joinFormPath(parentPath, next.name);
410
400
  const old = embeddedOccurrences(previous, before, fieldPath);
411
401
  const currentOld = embeddedOccurrences(previous, value, fieldPath);
412
402
  const candidates = embeddedOccurrences(next, copy, fieldPath);
@@ -482,7 +472,7 @@ function reconcileArrayValue(
482
472
  const originalRows = Array.isArray(originalValue) ? originalValue : [];
483
473
  const previousChildren = previous.nested?.fields ?? [];
484
474
  const nextChildren = next.nested?.fields ?? [];
485
- const rowPath = joinPath(parentPath, next.name);
475
+ const rowPath = joinFormPath(parentPath, next.name);
486
476
 
487
477
  return {
488
478
  currentPresent,
@@ -541,7 +531,7 @@ function reconcileBlocksValue(
541
531
  const nextBlocks = new Map(
542
532
  (resolveBlockTypes(next.blocks) ?? []).map((block) => [block.slug, block])
543
533
  );
544
- const rowPath = joinPath(parentPath, next.name);
534
+ const rowPath = joinFormPath(parentPath, next.name);
545
535
  const keptCurrent: unknown[] = [];
546
536
 
547
537
  for (const [index, row] of currentRows.entries()) {
@@ -625,19 +615,6 @@ function compatibleFields(previous: SchemaField, next: SchemaField) {
625
615
  return true;
626
616
  }
627
617
 
628
- function fieldDefault(field: SchemaField) {
629
- if (field.type === "select" && field.select?.hasMany === true) {
630
- if ((field.select.defaultValues?.length ?? 0) === 0) return { present: false };
631
- return { present: true, value: field.select.defaultValues };
632
- }
633
- if (field.default === undefined) return { present: false };
634
- if (field.type === "text-list" || field.type === "number-list")
635
- return { present: true, value: JSON.parse(field.default) };
636
- if (field.type === "number") return { present: true, value: Number(field.default) };
637
- if (field.type === "checkbox") return { present: true, value: field.default === "true" };
638
- return { present: true, value: field.default };
639
- }
640
-
641
618
  function initialFieldValue(
642
619
  field: SchemaField,
643
620
  value?: unknown,
@@ -665,7 +642,7 @@ function initialFieldValue(
665
642
  }
666
643
  return { present: true, value };
667
644
  }
668
- const direct = fieldDefault(field);
645
+ const direct = schemaFieldDefault(field);
669
646
  if (direct.present) return direct;
670
647
  if (field.type === "group") {
671
648
  const nested = initialFormValues(field.nested?.fields ?? []);
@@ -692,7 +669,7 @@ function detachedValue(
692
669
  return {
693
670
  fieldId: field.id,
694
671
  label: field.admin.label,
695
- path: joinPath(parentPath, field.name),
672
+ path: joinFormPath(parentPath, field.name),
696
673
  reason,
697
674
  value: cloneFormValue(value),
698
675
  };
@@ -711,18 +688,10 @@ function copyReservedValue(source: FormValues, target: FormValues, name: string)
711
688
  if (Object.hasOwn(source, name)) target[name] = cloneFormValue(source[name]);
712
689
  }
713
690
 
714
- function joinPath(parent: string, child: string) {
715
- return parent === "" ? child : `${parent}.${child}`;
716
- }
717
-
718
691
  function deepEqual(left: unknown, right: unknown) {
719
692
  return JSON.stringify(left) === JSON.stringify(right);
720
693
  }
721
694
 
722
- function isRecord(value: unknown): value is FormValues {
723
- return typeof value === "object" && value !== null && !Array.isArray(value);
724
- }
725
-
726
695
  export function cloneFormValues(values: FormValues): FormValues {
727
696
  return cloneFormValue(values) as FormValues;
728
697
  }
@@ -745,7 +714,7 @@ function embeddedProvenancePath(
745
714
  prefix = ""
746
715
  ): string | undefined {
747
716
  for (const field of fields) {
748
- const fieldPath = joinPath(prefix, field.name);
717
+ const fieldPath = joinFormPath(prefix, field.name);
749
718
  if (!path.startsWith(`${fieldPath}.`)) continue;
750
719
  const value = current[field.name];
751
720
  const before = original[field.name];
@@ -1,10 +1,11 @@
1
- import { resolveBlockTypes } from "@riducms/protocol";
1
+ import { isRecord, resolveBlockTypes } from "@riducms/protocol";
2
2
  import { embeddedOccurrences } from "@admin/core/forms/embedded-fields";
3
3
  import type { AdminI18n } from "@riducms/plugin";
4
4
  import { createAdminI18n } from "@riducms/translations";
5
5
  import type { SchemaField, ValidationIssue } from "@riducms/protocol";
6
6
 
7
7
  import { initialFormValues, type FormValues } from "@admin/core/forms/form-schema";
8
+ import { joinFormPath } from "@admin/core/forms/form-path";
8
9
 
9
10
  export interface FormValidationOptions {
10
11
  requireMissing: boolean;
@@ -66,13 +67,8 @@ function resolveFieldLabel(
66
67
  const [index, ...childSegments] = remaining;
67
68
  const rowLabels = isIndex(index)
68
69
  ? [
69
- ...labels,
70
- translate(
71
- i18n,
72
- "fields:rowNumber",
73
- { number: Number(index) + 1 },
74
- `Row ${Number(index) + 1}`
75
- ),
70
+ ...parents,
71
+ `${localizedFieldLabel(field, i18n)} ${i18n?.formatNumber(Number(index) + 1) ?? Number(index) + 1}`,
76
72
  ]
77
73
  : labels;
78
74
  return resolveFieldLabel(
@@ -121,7 +117,7 @@ function validateFields(
121
117
  ) {
122
118
  for (const field of fields) {
123
119
  if (field.category === "presentation") continue;
124
- const path = joinPath(prefix, field.name);
120
+ const path = joinFormPath(prefix, field.name);
125
121
  if (!include(path, field.path)) continue;
126
122
  const exists = Object.hasOwn(values, field.name);
127
123
  const value = values[field.name];
@@ -182,11 +178,9 @@ function validateFields(
182
178
  case "text":
183
179
  case "code":
184
180
  case "textarea":
185
- validateString(field, value, path, issues, i18n);
186
- break;
187
181
  case "email":
188
182
  case "date":
189
- validateOptionalString(field, value, path, issues, i18n);
183
+ validateString(field, value, path, issues, i18n);
190
184
  break;
191
185
  case "number":
192
186
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -351,31 +345,6 @@ function validateString(
351
345
  }
352
346
  }
353
347
 
354
- function validateOptionalString(
355
- field: SchemaField,
356
- value: unknown,
357
- path: string,
358
- issues: ValidationIssue[],
359
- i18n?: AdminI18n
360
- ) {
361
- if (typeof value !== "string") {
362
- issues.push(
363
- issue(
364
- "invalid_type",
365
- path,
366
- translate(
367
- i18n,
368
- "errors:string",
369
- { label: localizedFieldLabel(field, i18n) },
370
- `${localizedFieldLabel(field, i18n)} must be a string`
371
- )
372
- )
373
- );
374
- } else if (field.required && value.length === 0) {
375
- issues.push(requiredIssue(field, path, i18n));
376
- }
377
- }
378
-
379
348
  function validateSelect(
380
349
  field: SchemaField,
381
350
  value: unknown,
@@ -837,14 +806,6 @@ function issue(code: string, path: string, message: string): ValidationIssue {
837
806
  return { code, path, message };
838
807
  }
839
808
 
840
- function joinPath(prefix: string, name: string) {
841
- return prefix === "" ? name : `${prefix}.${name}`;
842
- }
843
-
844
- function isRecord(value: unknown): value is Record<string, unknown> {
845
- return typeof value === "object" && value !== null && !Array.isArray(value);
846
- }
847
-
848
809
  function translate(
849
810
  i18n: AdminI18n | undefined,
850
811
  key: Parameters<AdminI18n["t"]>[0],
@@ -868,7 +829,7 @@ export function unknownBlockIssues(
868
829
  const issues: ValidationIssue[] = [];
869
830
  for (const field of fields) {
870
831
  const value = values[field.name];
871
- const path = joinPath(prefix, field.name);
832
+ const path = joinFormPath(prefix, field.name);
872
833
  if (field.type === "group" && isRecord(value))
873
834
  issues.push(...unknownBlockIssues(field.nested?.fields ?? [], value, i18n, path));
874
835
  if (field.type === "plugin") {
@@ -25,15 +25,19 @@ export function guardPluginAuthoring(
25
25
  binding.assertActive();
26
26
  return host.locale;
27
27
  },
28
+ canCreateDocument(collection) {
29
+ binding.assertActive();
30
+ return host.canCreateDocument(collection);
31
+ },
28
32
  referenceBrowser: (anchor, props) => {
29
33
  binding.assertActive();
30
34
  const overrides = {
31
35
  get readOnly() {
32
36
  return binding.readOnly || props.readOnly;
33
37
  },
34
- onCommit: async (ids: string[]) => {
38
+ onCommit: async (ids: string[], collectionSlug: string) => {
35
39
  binding.assertEditable();
36
- const result = await props.onCommit([...ids]);
40
+ const result = await props.onCommit([...ids], collectionSlug);
37
41
  binding.assertEditable();
38
42
  return result;
39
43
  },