@michaelthielemann/kestrel 4.1.3 → 5.0.2

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 (342) hide show
  1. package/README.md +26 -201
  2. package/dist/boot.d.ts +45 -0
  3. package/dist/boot.js +284 -0
  4. package/dist/cast.d.ts +3 -0
  5. package/dist/cast.js +6 -0
  6. package/dist/catalogue.d.ts +11 -0
  7. package/dist/catalogue.js +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +55 -0
  10. package/dist/context.d.ts +62 -0
  11. package/dist/context.js +61 -0
  12. package/dist/dataflow.d.ts +3 -0
  13. package/dist/dataflow.js +65 -0
  14. package/dist/defineConfig.d.ts +162 -0
  15. package/dist/defineConfig.js +42 -0
  16. package/dist/defineContract.d.ts +12 -0
  17. package/dist/defineContract.js +16 -0
  18. package/dist/defineModule.d.ts +75 -0
  19. package/dist/defineModule.js +9 -0
  20. package/dist/definePipeline.d.ts +7 -0
  21. package/dist/definePipeline.js +11 -0
  22. package/dist/errors.d.ts +26 -0
  23. package/dist/errors.js +48 -0
  24. package/dist/index.d.ts +19 -0
  25. package/dist/index.js +14 -0
  26. package/dist/load.d.ts +7 -0
  27. package/dist/load.js +79 -0
  28. package/dist/logger.d.ts +18 -0
  29. package/dist/logger.js +36 -0
  30. package/dist/registry.d.ts +14 -0
  31. package/dist/registry.js +87 -0
  32. package/dist/result.d.ts +19 -0
  33. package/dist/result.js +19 -0
  34. package/dist/runner.d.ts +29 -0
  35. package/dist/runner.js +133 -0
  36. package/dist/schema.d.ts +7 -0
  37. package/dist/schema.js +142 -0
  38. package/dist/sort.d.ts +2 -0
  39. package/dist/sort.js +50 -0
  40. package/dist/testing/runPipeline.d.ts +9 -0
  41. package/dist/testing/runPipeline.js +22 -0
  42. package/dist/triggers/allowlist.d.ts +9 -0
  43. package/dist/triggers/allowlist.js +49 -0
  44. package/dist/triggers/cron.d.ts +10 -0
  45. package/dist/triggers/cron.js +44 -0
  46. package/dist/triggers/cronMatch.d.ts +10 -0
  47. package/dist/triggers/cronMatch.js +68 -0
  48. package/dist/triggers/http.d.ts +59 -0
  49. package/dist/triggers/http.js +300 -0
  50. package/package.json +25 -151
  51. package/NOTICE +0 -12
  52. package/kestrel.config.ts +0 -28
  53. package/layers/access/nuxt.config.ts +0 -5
  54. package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
  55. package/layers/access/server/middleware/access-guard.ts +0 -32
  56. package/layers/access/server/types.d.ts +0 -7
  57. package/layers/admin/app/components/AdminAccount.vue +0 -212
  58. package/layers/admin/app/components/AdminNav.vue +0 -44
  59. package/layers/admin/app/components/BlockFields.vue +0 -67
  60. package/layers/admin/app/components/BlockPreview.vue +0 -413
  61. package/layers/admin/app/components/BlockTree.vue +0 -433
  62. package/layers/admin/app/components/BlocksBody.vue +0 -163
  63. package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
  64. package/layers/admin/app/components/CollectionEditor.vue +0 -324
  65. package/layers/admin/app/components/CollectionList.vue +0 -429
  66. package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
  67. package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
  68. package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
  69. package/layers/admin/app/components/CollectionListPager.vue +0 -70
  70. package/layers/admin/app/components/CollectionListTable.vue +0 -347
  71. package/layers/admin/app/components/EditorStatus.vue +0 -211
  72. package/layers/admin/app/components/EditorUnsupported.vue +0 -28
  73. package/layers/admin/app/components/FieldsBody.vue +0 -23
  74. package/layers/admin/app/components/LocaleBar.vue +0 -107
  75. package/layers/admin/app/components/PageFields.vue +0 -129
  76. package/layers/admin/app/components/PageFieldsPane.vue +0 -39
  77. package/layers/admin/app/components/SeoFields.vue +0 -210
  78. package/layers/admin/app/components/SingletonEditor.vue +0 -93
  79. package/layers/admin/app/composables/useAuth.ts +0 -68
  80. package/layers/admin/app/composables/useBlockTree.ts +0 -95
  81. package/layers/admin/app/composables/useBlocks.ts +0 -7
  82. package/layers/admin/app/composables/useCachedResource.ts +0 -35
  83. package/layers/admin/app/composables/useCollectionOps.ts +0 -102
  84. package/layers/admin/app/composables/useCollections.ts +0 -6
  85. package/layers/admin/app/composables/useContentLocales.ts +0 -23
  86. package/layers/admin/app/composables/useEditForm.ts +0 -402
  87. package/layers/admin/app/composables/useLinkResolver.ts +0 -40
  88. package/layers/admin/app/composables/useListBatchActions.ts +0 -78
  89. package/layers/admin/app/composables/useListColumns.ts +0 -53
  90. package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
  91. package/layers/admin/app/composables/useListRows.ts +0 -75
  92. package/layers/admin/app/composables/useListSelection.ts +0 -28
  93. package/layers/admin/app/composables/useListUrlState.ts +0 -53
  94. package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
  95. package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
  96. package/layers/admin/app/composables/usePublishStatus.ts +0 -107
  97. package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
  98. package/layers/admin/app/composables/useTheme.ts +0 -36
  99. package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
  100. package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
  101. package/layers/admin/app/layouts/admin.vue +0 -266
  102. package/layers/admin/app/middleware/admin-auth.ts +0 -4
  103. package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
  104. package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
  105. package/layers/admin/app/pages/admin/index.vue +0 -118
  106. package/layers/admin/app/pages/admin/login.vue +0 -68
  107. package/layers/admin/app/pages/admin/media.vue +0 -28
  108. package/layers/admin/app/pages/admin/references.vue +0 -146
  109. package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
  110. package/layers/admin/app/plugins/reauth.client.ts +0 -20
  111. package/layers/admin/app/utils/admin-style-guard.ts +0 -20
  112. package/layers/admin/app/utils/block-tree.ts +0 -214
  113. package/layers/admin/app/utils/collection-ops.ts +0 -69
  114. package/layers/admin/app/utils/dead-refs.ts +0 -30
  115. package/layers/admin/app/utils/edit-form.ts +0 -267
  116. package/layers/admin/app/utils/editor-expose.ts +0 -36
  117. package/layers/admin/app/utils/editor-form-context.ts +0 -82
  118. package/layers/admin/app/utils/editor-registry.ts +0 -20
  119. package/layers/admin/app/utils/field-keys.ts +0 -11
  120. package/layers/admin/app/utils/list-cell.ts +0 -46
  121. package/layers/admin/app/utils/list-columns.ts +0 -142
  122. package/layers/admin/app/utils/list-query.ts +0 -96
  123. package/layers/admin/app/utils/nav-active.ts +0 -9
  124. package/layers/admin/app/utils/populate-blocks.ts +0 -288
  125. package/layers/admin/app/utils/preview-channel.ts +0 -62
  126. package/layers/admin/app/utils/preview-viewport.ts +0 -80
  127. package/layers/admin/app/utils/reauth.ts +0 -67
  128. package/layers/admin/app/utils/record-title.ts +0 -15
  129. package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
  130. package/layers/admin/app/utils/safe-redirect.ts +0 -12
  131. package/layers/admin/nuxt.config.ts +0 -20
  132. package/layers/auth/nuxt.config.ts +0 -1
  133. package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
  134. package/layers/collections/nuxt.config.ts +0 -1
  135. package/layers/collections/server/plugins/01.register.ts +0 -29
  136. package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
  137. package/layers/core/app/composables/layouts.ts +0 -5
  138. package/layers/core/app/utils/field-factories.ts +0 -129
  139. package/layers/core/app/utils/layouts.ts +0 -38
  140. package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
  141. package/layers/core/modules/auto-discovery/index.ts +0 -112
  142. package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
  143. package/layers/core/modules/auto-discovery/scan.ts +0 -170
  144. package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
  145. package/layers/core/modules/component-namespace/index.ts +0 -44
  146. package/layers/core/modules/component-namespace/shared.ts +0 -24
  147. package/layers/core/modules/kestrel/app-shell.ts +0 -55
  148. package/layers/core/modules/kestrel/auto-imports.ts +0 -44
  149. package/layers/core/modules/kestrel/index.ts +0 -144
  150. package/layers/core/modules/plugin-order/index.ts +0 -69
  151. package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
  152. package/layers/core/nuxt.config.ts +0 -10
  153. package/layers/core/server/api/[...path].ts +0 -98
  154. package/layers/core/server/plugins/00.config.ts +0 -12
  155. package/layers/core/server/plugins/00.migrate.ts +0 -12
  156. package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
  157. package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
  158. package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
  159. package/layers/core/server/plugins/02.schema-sync.ts +0 -58
  160. package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
  161. package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
  162. package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
  163. package/layers/core/server/tasks/db/migrate-module.ts +0 -83
  164. package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
  165. package/layers/core/server/tasks/db/migrate.ts +0 -50
  166. package/layers/core/server/utils/server-config.ts +0 -81
  167. package/layers/fields/nuxt.config.ts +0 -1
  168. package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
  169. package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
  170. package/layers/media/app/components/KestrelImg.vue +0 -94
  171. package/layers/media/app/components/MediaConflictDialog.vue +0 -35
  172. package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
  173. package/layers/media/app/components/MediaGrid.vue +0 -113
  174. package/layers/media/app/components/MediaImage.vue +0 -70
  175. package/layers/media/app/components/MediaLibrary.vue +0 -456
  176. package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
  177. package/layers/media/app/components/MediaPathBar.vue +0 -163
  178. package/layers/media/app/components/MediaPicker.vue +0 -19
  179. package/layers/media/app/components/MediaRenameDialog.vue +0 -32
  180. package/layers/media/app/components/MediaTable.vue +0 -75
  181. package/layers/media/app/components/MediaThumb.vue +0 -25
  182. package/layers/media/app/components/MediaToolbar.vue +0 -90
  183. package/layers/media/app/components/MediaUploadDialog.vue +0 -87
  184. package/layers/media/app/components/MediaViewer.vue +0 -137
  185. package/layers/media/app/components/field/Media.vue +0 -263
  186. package/layers/media/app/composables/useBlurUp.ts +0 -55
  187. package/layers/media/app/composables/useMediaClipboard.ts +0 -14
  188. package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
  189. package/layers/media/app/composables/useMediaDnd.ts +0 -56
  190. package/layers/media/app/composables/useMediaLibrary.ts +0 -152
  191. package/layers/media/app/composables/useMediaOps.ts +0 -50
  192. package/layers/media/app/composables/useMediaRelocate.ts +0 -39
  193. package/layers/media/app/composables/useMediaResolver.ts +0 -50
  194. package/layers/media/app/composables/useMediaUpload.ts +0 -93
  195. package/layers/media/app/composables/useMediaVariant.ts +0 -34
  196. package/layers/media/app/plugins/field-media.client.ts +0 -6
  197. package/layers/media/app/utils/ai-disclosure.ts +0 -19
  198. package/layers/media/app/utils/dnd.ts +0 -69
  199. package/layers/media/app/utils/library.ts +0 -81
  200. package/layers/media/app/utils/ops.ts +0 -60
  201. package/layers/media/app/utils/picture-model.ts +0 -125
  202. package/layers/media/app/utils/thumbhash.ts +0 -19
  203. package/layers/media/app/utils/variant-name.ts +0 -20
  204. package/layers/media/nuxt.config.ts +0 -7
  205. package/layers/media/server/middleware/ondemand-variants.ts +0 -38
  206. package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
  207. package/layers/media/server/plugins/02.register-media.ts +0 -22
  208. package/layers/media/server/plugins/04.variant-capture.ts +0 -27
  209. package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
  210. package/layers/media/server/tasks/media/backfill.ts +0 -17
  211. package/layers/public/app/app.vue +0 -5
  212. package/layers/public/app/components/BlockRenderer.vue +0 -56
  213. package/layers/public/app/components/KestrelLink.vue +0 -26
  214. package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
  215. package/layers/public/app/composables/public-page.ts +0 -24
  216. package/layers/public/app/error.vue +0 -12
  217. package/layers/public/app/layouts/default.vue +0 -5
  218. package/layers/public/app/pages/[...slug].vue +0 -239
  219. package/layers/public/app/pages/__kestrel/preview.vue +0 -46
  220. package/layers/public/app/utils/block-edit-context.ts +0 -16
  221. package/layers/public/app/utils/json-ld.ts +0 -139
  222. package/layers/public/app/utils/link-href.ts +0 -46
  223. package/layers/public/app/utils/page-head.ts +0 -84
  224. package/layers/public/app/utils/page-layout.ts +0 -18
  225. package/layers/public/app/utils/preview-protocol.ts +0 -117
  226. package/layers/public/app/utils/public-route.ts +0 -46
  227. package/layers/public/app/utils/site-head.ts +0 -43
  228. package/layers/public/modules/deploy-output/index.ts +0 -80
  229. package/layers/public/modules/prerender-routes/discover.ts +0 -37
  230. package/layers/public/modules/prerender-routes/index.ts +0 -88
  231. package/layers/public/modules/prerender-routes/routes.ts +0 -16
  232. package/layers/public/modules/prune-media/index.ts +0 -48
  233. package/layers/public/modules/prune-media/prune-media.ts +0 -81
  234. package/layers/public/nuxt.config.ts +0 -14
  235. package/layers/public/server/delivery-live/serve.ts +0 -111
  236. package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
  237. package/layers/public/server/pipelines/index.ts +0 -23
  238. package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
  239. package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
  240. package/layers/public/server/plugins/02.register-links.ts +0 -16
  241. package/layers/public/server/plugins/03.redirects.ts +0 -55
  242. package/layers/public/server/plugins/05.plan-publish.ts +0 -5
  243. package/layers/public/server/plugins/zz.publish.ts +0 -127
  244. package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
  245. package/layers/public/server/routes/llms.txt.get.ts +0 -66
  246. package/layers/public/server/routes/redirects.json.get.ts +0 -58
  247. package/layers/public/server/routes/robots.txt.get.ts +0 -11
  248. package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
  249. package/layers/public/server/tasks/publish/run.ts +0 -32
  250. package/layers/public/server/utils/publish/render-live.ts +0 -26
  251. package/layers/ui/app/assets/scss/_base.scss +0 -16
  252. package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
  253. package/layers/ui/app/assets/scss/_mixins.scss +0 -50
  254. package/layers/ui/app/assets/scss/_reset.scss +0 -65
  255. package/layers/ui/app/assets/scss/_tokens.scss +0 -183
  256. package/layers/ui/app/assets/scss/main.scss +0 -4
  257. package/layers/ui/app/components/field/Boolean.vue +0 -30
  258. package/layers/ui/app/components/field/Choice.vue +0 -73
  259. package/layers/ui/app/components/field/Datetime.vue +0 -98
  260. package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
  261. package/layers/ui/app/components/field/Json.vue +0 -62
  262. package/layers/ui/app/components/field/Layout.vue +0 -138
  263. package/layers/ui/app/components/field/Link.vue +0 -112
  264. package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
  265. package/layers/ui/app/components/field/Number.vue +0 -30
  266. package/layers/ui/app/components/field/Relation.vue +0 -49
  267. package/layers/ui/app/components/field/Renderer.vue +0 -30
  268. package/layers/ui/app/components/field/Repeater.vue +0 -460
  269. package/layers/ui/app/components/field/Richtext.vue +0 -26
  270. package/layers/ui/app/components/field/Slug.vue +0 -65
  271. package/layers/ui/app/components/field/Text.vue +0 -35
  272. package/layers/ui/app/components/field/Unsupported.vue +0 -34
  273. package/layers/ui/app/components/ui/Alert.vue +0 -50
  274. package/layers/ui/app/components/ui/Brand.vue +0 -22
  275. package/layers/ui/app/components/ui/Button.vue +0 -116
  276. package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
  277. package/layers/ui/app/components/ui/Checkbox.vue +0 -53
  278. package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
  279. package/layers/ui/app/components/ui/Combobox.vue +0 -345
  280. package/layers/ui/app/components/ui/DatePicker.vue +0 -92
  281. package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
  282. package/layers/ui/app/components/ui/Dialog.vue +0 -107
  283. package/layers/ui/app/components/ui/EmptyState.vue +0 -46
  284. package/layers/ui/app/components/ui/Field.vue +0 -58
  285. package/layers/ui/app/components/ui/Fieldset.vue +0 -65
  286. package/layers/ui/app/components/ui/Icon.vue +0 -44
  287. package/layers/ui/app/components/ui/Menu.vue +0 -54
  288. package/layers/ui/app/components/ui/NumberInput.vue +0 -175
  289. package/layers/ui/app/components/ui/Richtext.vue +0 -141
  290. package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
  291. package/layers/ui/app/components/ui/Select.vue +0 -52
  292. package/layers/ui/app/components/ui/TextInput.vue +0 -111
  293. package/layers/ui/app/components/ui/Textarea.vue +0 -39
  294. package/layers/ui/app/components/ui/TimeInput.vue +0 -18
  295. package/layers/ui/app/components/ui/Toasts.vue +0 -103
  296. package/layers/ui/app/components/ui/Tooltip.vue +0 -45
  297. package/layers/ui/app/composables/useAdminLang.ts +0 -8
  298. package/layers/ui/app/composables/useDragReorder.ts +0 -41
  299. package/layers/ui/app/composables/useEchoGuard.ts +0 -28
  300. package/layers/ui/app/composables/useLinkField.ts +0 -70
  301. package/layers/ui/app/composables/useRecordOptions.ts +0 -84
  302. package/layers/ui/app/composables/useRepeater.ts +0 -99
  303. package/layers/ui/app/composables/useT.ts +0 -32
  304. package/layers/ui/app/composables/useToast.ts +0 -46
  305. package/layers/ui/app/i18n/de.ts +0 -432
  306. package/layers/ui/app/i18n/en.ts +0 -434
  307. package/layers/ui/app/utils/boolean-field.ts +0 -10
  308. package/layers/ui/app/utils/field-component.ts +0 -22
  309. package/layers/ui/app/utils/field-empty.ts +0 -21
  310. package/layers/ui/app/utils/field-registry.ts +0 -36
  311. package/layers/ui/app/utils/humanize.ts +0 -14
  312. package/layers/ui/app/utils/icons.ts +0 -143
  313. package/layers/ui/app/utils/iso-date.ts +0 -18
  314. package/layers/ui/app/utils/localized.ts +0 -13
  315. package/layers/ui/app/utils/record-context.ts +0 -15
  316. package/layers/ui/app/utils/reorder.ts +0 -8
  317. package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
  318. package/layers/ui/app/utils/useFieldA11y.ts +0 -40
  319. package/layers/ui/nuxt.config.ts +0 -9
  320. package/nuxt.config.ts +0 -108
  321. package/scripts/consumer-template-ci.mjs +0 -160
  322. package/scripts/copy-create-payload.mjs +0 -49
  323. package/scripts/coverage-all.mjs +0 -163
  324. package/scripts/dashboard.mjs +0 -89
  325. package/scripts/hash-password.mjs +0 -29
  326. package/scripts/kestrel.mjs +0 -240
  327. package/scripts/lib/cli.mjs +0 -129
  328. package/scripts/lib/coverage-model.mjs +0 -51
  329. package/scripts/lib/password.mjs +0 -19
  330. package/scripts/lib/scaffold.mjs +0 -205
  331. package/scripts/port-graph.mjs +0 -350
  332. package/scripts/typecheck.mjs +0 -62
  333. package/templates/starter/README.md +0 -65
  334. package/templates/starter/_env.example +0 -17
  335. package/templates/starter/_gitignore +0 -26
  336. package/templates/starter/_package.json +0 -22
  337. package/templates/starter/app/app.vue +0 -7
  338. package/templates/starter/app/blocks/Prose.vue +0 -13
  339. package/templates/starter/app/layouts/default.vue +0 -6
  340. package/templates/starter/nuxt.config.ts +0 -23
  341. package/templates/starter/pnpm-workspace.yaml +0 -8
  342. package/templates/starter/tsconfig.json +0 -3
@@ -1,138 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import FieldRenderer from './Renderer.vue'
4
- import FieldDeadRefNote from './DeadRefNote.vue'
5
- import { isFieldVisible } from '@michaelthielemann/kestrel-core/client'
6
- import type { FieldDef, LayoutNode, LayoutTrack, Localized } from '@michaelthielemann/kestrel-core'
7
- import { resolveLocalized } from '../../utils/localized'
8
- // The ONE shared field-list renderer, reused by every field-list surface (collection page fields, block
9
- // fields, repeater sub-fields). A FRAGMENT (no wrapper root) so its rows/groups drop straight into the
10
- // caller's existing gapped flex column — the per-context vertical rhythm is preserved. Imports are
11
- // explicit (not auto-imported) because this component also renders inside Repeater, which is exercised
12
- // by the auto-import-free `dom` test runner.
13
- const props = defineProps<{
14
- layout?: LayoutNode[]
15
- fields: Record<string, FieldDef>
16
- values: Record<string, unknown>
17
- errors?: Record<string, string>
18
- /** Field keys holding a stale reference — shown as a non-blocking note under the field. Omit to disable. */
19
- deadFields?: Set<string>
20
- locale: string
21
- disabled?: boolean
22
- }>()
23
- const emit = defineEmits<{ update: [name: string, value: unknown] }>()
24
- const { lang } = useT()
25
-
26
- // No author layout → the historical one-field-per-row flow, expressed as single-field full-width rows, so
27
- // both cases flow through a single render path.
28
- const nodes = computed<LayoutNode[]>(() =>
29
- props.layout ?? Object.keys(props.fields).map((f) => ({ kind: 'row', fields: [f], tracks: [1] })),
30
- )
31
-
32
- // A render-ready row: only the cells that are present + visible, with the grid columns rebuilt from just
33
- // their tracks. A field hidden by its `condition` must collapse its grid TRACK — otherwise the resolved
34
- // row would still reserve it and the surviving cells auto-place into the wrong columns. This is the ONE
35
- // place that turns the engine's neutral tracks (weight numbers / length strings) into CSS-grid columns.
36
- interface RenderRow { fields: string[]; cols: string }
37
- type RenderNode = { kind: 'row'; row: RenderRow } | { kind: 'group'; label: Localized; rows: RenderRow[] }
38
-
39
- const isVisible = (name: string) => !!props.fields[name] && isFieldVisible(props.fields[name]!, props.values)
40
- const trackToCss = (t: LayoutTrack): string => (typeof t === 'number' ? `${t}fr` : t)
41
-
42
- function pruneRow(fields: string[], tracks: LayoutTrack[] = []): RenderRow {
43
- const kept: string[] = []
44
- const cols: string[] = []
45
- fields.forEach((name, i) => {
46
- if (isVisible(name)) { kept.push(name); cols.push(trackToCss(tracks[i] ?? 1)) }
47
- })
48
- return { fields: kept, cols: cols.join(' ') || '1fr' }
49
- }
50
-
51
- const renderNodes = computed<RenderNode[]>(() =>
52
- nodes.value.map((node) =>
53
- node.kind === 'group'
54
- ? { kind: 'group', label: node.label, rows: node.rows.map((r) => pruneRow(r.fields, r.tracks)) }
55
- : { kind: 'row', row: pruneRow(node.fields, node.tracks) },
56
- ),
57
- )
58
- </script>
59
-
60
- <template>
61
- <template v-for="(node, i) in renderNodes" :key="i">
62
- <!-- Named group: a labelled fieldset wrapping its stacked rows (one level deep). -->
63
- <fieldset v-if="node.kind === 'group'" class="ui-field-group">
64
- <legend class="ui-field-group__legend">{{ resolveLocalized(node.label, lang) }}</legend>
65
- <div
66
- v-for="(row, r) in node.rows"
67
- :key="r"
68
- class="ui-field-row"
69
- :style="{ '--ui-field-cols': row.cols }"
70
- >
71
- <div v-for="fname in row.fields" :key="fname" class="ui-field-cell">
72
- <FieldRenderer
73
- :field="fields[fname]!"
74
- :name="fname"
75
- :locale="locale"
76
- :disabled="disabled"
77
- :error="errors?.[fname] || null"
78
- :model-value="values[fname]"
79
- @update:model-value="(v) => emit('update', fname, v)"
80
- />
81
- <FieldDeadRefNote v-if="deadFields" :show="deadFields.has(fname)" />
82
- </div>
83
- </div>
84
- </fieldset>
85
-
86
- <!-- Plain row: the same grid + cell markup as a group's row, just without the fieldset wrapper. -->
87
- <div v-else class="ui-field-row" :style="{ '--ui-field-cols': node.row.cols }">
88
- <div v-for="fname in node.row.fields" :key="fname" class="ui-field-cell">
89
- <FieldRenderer
90
- :field="fields[fname]!"
91
- :name="fname"
92
- :locale="locale"
93
- :disabled="disabled"
94
- :error="errors?.[fname] || null"
95
- :model-value="values[fname]"
96
- @update:model-value="(v) => emit('update', fname, v)"
97
- />
98
- <FieldDeadRefNote v-if="deadFields" :show="deadFields.has(fname)" />
99
- </div>
100
- </div>
101
- </template>
102
- </template>
103
-
104
- <style lang="scss">
105
- // Grid track set from the CUSTOM PROPERTY (not grid-template-columns directly) so the responsive collapse
106
- // below — a plain stylesheet rule — wins over the inline value.
107
- .ui-field-row {
108
- display: grid;
109
- grid-template-columns: var(--ui-field-cols, 1fr);
110
- gap: var(--space-3);
111
- align-items: start;
112
- min-inline-size: 0;
113
- }
114
- .ui-field-cell {
115
- display: flex;
116
- flex-direction: column;
117
- gap: var(--space-1); // separates a field from its stale-reference note; inert for a single-child cell
118
- min-inline-size: 0; // let inputs shrink inside a grid track (the repeater idiom)
119
- }
120
- .ui-field-group {
121
- margin: 0;
122
- padding: 0;
123
- border: 0;
124
- display: flex;
125
- flex-direction: column;
126
- gap: var(--space-3);
127
- }
128
- .ui-field-group__legend {
129
- padding: 0;
130
- font-size: var(--text-sm);
131
- font-weight: var(--weight-medium);
132
- color: var(--color-text-muted);
133
- }
134
- // Responsive collapse to a single column (the repo's existing breakpoint). Container queries are a follow-up.
135
- @media (max-width: 48rem) {
136
- .ui-field-row { grid-template-columns: 1fr; }
137
- }
138
- </style>
@@ -1,112 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import UiField from '../ui/Field.vue'
4
- import UiButtonGroup from '../ui/ButtonGroup.vue'
5
- import UiTextInput from '../ui/TextInput.vue'
6
- import LinkInternalPicker from './LinkInternalPicker.vue'
7
- import { useLinkField } from '../../composables/useLinkField'
8
- import type { FieldComponentProps } from '../../utils/field-component'
9
- import type { FieldOf, LinkType, LinkValue } from '@michaelthielemann/kestrel-core'
10
-
11
- const ALL: LinkType[] = ['external', 'email', 'tel', 'internal']
12
- const TYPE_KEYS: Record<LinkType, string> = {
13
- internal: 'field.link.type_internal',
14
- external: 'field.link.type_url',
15
- email: 'field.link.type_email',
16
- tel: 'field.link.type_phone',
17
- }
18
-
19
- const { t } = useT()
20
- const props = defineProps<FieldComponentProps>()
21
- const model = defineModel<LinkValue | null>()
22
-
23
- const required = computed(() => !!props.field.required)
24
- const allowed = computed<LinkType[]>(() =>
25
- props.field.type === 'link' ? ((props.field as FieldOf<'link'>).options?.types ?? ALL) : ALL,
26
- )
27
- const typeOptions = computed(() => allowed.value.map((ltype) => ({ value: ltype, label: t(TYPE_KEYS[ltype]) })))
28
- const internalCollections = computed(() =>
29
- props.field.type === 'link' ? (props.field as FieldOf<'link'>).options?.collections : undefined,
30
- )
31
-
32
- const { currentType, typeModel, url, email, tel, label, collection, recordId, hash } = useLinkField(model, allowed)
33
- </script>
34
-
35
- <template>
36
- <UiField :id="id" :label="name" :error="error" :required="required">
37
- <template #default="f">
38
- <UiButtonGroup
39
- v-if="allowed.length > 1"
40
- v-model="typeModel"
41
- :options="typeOptions"
42
- :disabled="disabled"
43
- :aria-label="t('field.link.link_type')"
44
- />
45
-
46
- <template v-if="currentType === 'external'">
47
- <UiTextInput
48
- v-model="url"
49
- type="url"
50
- :disabled="disabled"
51
- :placeholder="t('field.link.url_placeholder')"
52
- v-bind="f"
53
- />
54
- </template>
55
- <template v-else-if="currentType === 'email'">
56
- <UiTextInput
57
- v-model="email"
58
- type="email"
59
- :disabled="disabled"
60
- v-bind="f"
61
- />
62
- </template>
63
- <template v-else-if="currentType === 'tel'">
64
- <UiTextInput
65
- v-model="tel"
66
- type="tel"
67
- :disabled="disabled"
68
- v-bind="f"
69
- />
70
- </template>
71
- <template v-else>
72
- <LinkInternalPicker
73
- v-model:collection="collection"
74
- v-model:record-id="recordId"
75
- :collections="internalCollections"
76
- :locale="locale"
77
- :disabled="disabled"
78
- :input-id="f.id"
79
- :invalid="f['aria-invalid'] === 'true'"
80
- :describedby="f['aria-describedby']"
81
- :required="f.required"
82
- />
83
- <UiTextInput
84
- v-model="hash"
85
- class="ui-link__hash"
86
- :disabled="disabled"
87
- :placeholder="t('field.link.hash_placeholder')"
88
- :aria-label="t('field.link.hash_placeholder')"
89
- />
90
- </template>
91
-
92
- <span class="ui-link__label-row">
93
- <UiTextInput
94
- v-model="label"
95
- :disabled="disabled"
96
- :placeholder="t('field.link.label_placeholder')"
97
- :aria-label="t('field.link.label_placeholder')"
98
- />
99
- </span>
100
- </template>
101
- </UiField>
102
- </template>
103
-
104
- <style lang="scss">
105
- .ui-link__label-row {
106
- display: block;
107
- margin-top: var(--space-1);
108
- }
109
- .ui-link__hash {
110
- margin-top: var(--space-1);
111
- }
112
- </style>
@@ -1,95 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, onMounted, toRef } from 'vue'
3
- import UiSelect from '../ui/Select.vue'
4
- import UiCombobox from '../ui/Combobox.vue'
5
- import { useRecordOptions } from '../../composables/useRecordOptions'
6
- import { resolveLocalized } from '../../utils/localized'
7
- import type { SerializedCollection } from '@michaelthielemann/kestrel-core'
8
-
9
- const { t, lang } = useT()
10
-
11
- const props = defineProps<{
12
- collections?: string[]
13
- locale: string
14
- disabled?: boolean
15
- inputId?: string
16
- invalid?: boolean
17
- describedby?: string
18
- required?: boolean
19
- }>()
20
-
21
- const collection = defineModel<string | null>('collection')
22
- const recordId = defineModel<number | null>('recordId')
23
-
24
- interface CollectionOption { label: string; value: string }
25
- const collectionOptions = ref<CollectionOption[]>([])
26
- const showSelect = ref(false) // stays hidden until the fetch resolves → no flash, no empty select
27
- const loadError = ref<string | null>(null)
28
-
29
- onMounted(async () => {
30
- try {
31
- const res = await $fetch<{ data: SerializedCollection[] }>('/api/collections')
32
- const filtered = res.data.filter((c) => !props.collections || props.collections.includes(c.name))
33
- collectionOptions.value = filtered.map((c) => ({
34
- value: c.name,
35
- label: resolveLocalized(c.label?.plural, lang.value) ?? resolveLocalized(c.label?.singular, lang.value) ?? c.name,
36
- }))
37
- if (collectionOptions.value.length === 1) collection.value ||= collectionOptions.value[0]!.value
38
- showSelect.value = collectionOptions.value.length > 1
39
- } catch {
40
- loadError.value = t('field.linkPicker.loadError')
41
- }
42
- })
43
-
44
- // Reset only on a user pick — a programmatic {collection, id} reseed (record loader) sets both
45
- // together and must not have its freshly-set id clobbered.
46
- function onPickCollection(value: string | null | undefined) {
47
- collection.value = value ?? null
48
- recordId.value = null
49
- }
50
-
51
- const coll = computed(() => collection.value ?? '')
52
- const ids = computed(() => (recordId.value != null ? [recordId.value] : []))
53
- const { options, selected, loading, onSearch } = useRecordOptions(coll, ids, toRef(props, 'locale'))
54
- </script>
55
-
56
- <template>
57
- <div class="ui-link-internal">
58
- <p v-if="loadError" class="ui-link-internal__error">{{ loadError }}</p>
59
- <UiSelect
60
- v-if="showSelect"
61
- :model-value="collection"
62
- :options="collectionOptions"
63
- :disabled="disabled"
64
- :placeholder="t('field.linkPicker.chooseCollection')"
65
- :aria-label="t('field.linkPicker.collectionLabel')"
66
- :aria-invalid="invalid || undefined"
67
- :aria-describedby="describedby"
68
- @update:model-value="onPickCollection"
69
- />
70
- <UiCombobox
71
- v-model="recordId"
72
- :options="options"
73
- :selected="selected"
74
- :loading="loading"
75
- :multiple="false"
76
- :disabled="disabled || !collection"
77
- :input-id="inputId"
78
- :invalid="invalid"
79
- :describedby="describedby"
80
- :required="required"
81
- :placeholder="t('field.linkPicker.searchRecords')"
82
- @search="onSearch"
83
- />
84
- </div>
85
- </template>
86
-
87
- <style lang="scss">
88
- // The internal arm stacks two block controls (collection select + record combobox) plus an error line.
89
- // Matches the `--space-1` rhythm the hash/label rows already use.
90
- .ui-link-internal {
91
- display: flex;
92
- flex-direction: column;
93
- gap: var(--space-1);
94
- }
95
- </style>
@@ -1,30 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import UiField from '../ui/Field.vue'
4
- import UiNumberInput from '../ui/NumberInput.vue'
5
- import { fieldConstraints } from '@michaelthielemann/kestrel-fields/client'
6
- import type { FieldComponentProps } from '../../utils/field-component'
7
- import type { FieldOf } from '@michaelthielemann/kestrel-core'
8
-
9
- const props = defineProps<FieldComponentProps>()
10
- const model = defineModel<number | null>()
11
- const c = computed(() => fieldConstraints(props.field))
12
- // Display-only unit suffix (e.g. 'rem'); the stored value stays a bare number.
13
- const unit = computed(() => (props.field.type === 'number' ? (props.field as FieldOf<'number'>).options?.unit : undefined))
14
- </script>
15
-
16
- <template>
17
- <UiField :id="id" :label="name" :error="error" :required="c.required">
18
- <template #default="f">
19
- <UiNumberInput
20
- v-model="model"
21
- :min="c.min"
22
- :max="c.max"
23
- :step="c.step"
24
- :suffix="unit"
25
- :disabled="disabled"
26
- v-bind="f"
27
- />
28
- </template>
29
- </UiField>
30
- </template>
@@ -1,49 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import UiField from '../ui/Field.vue'
4
- import UiCombobox from '../ui/Combobox.vue'
5
- import { useRecordOptions } from '../../composables/useRecordOptions'
6
- import type { FieldComponentProps } from '../../utils/field-component'
7
- import type { FieldOf } from '@michaelthielemann/kestrel-core'
8
-
9
- const props = defineProps<FieldComponentProps>()
10
- const model = defineModel<number | number[] | null>()
11
-
12
- const cfg = computed(() => {
13
- if (props.field.type !== 'relation') return { collection: '', many: false, label: undefined as string | undefined }
14
- const r = (props.field as FieldOf<'relation'>).relation
15
- return { collection: r.collection, many: !!r.many, label: r.labelField }
16
- })
17
- const required = computed(() => !!props.field.required)
18
-
19
- const ids = computed<number[]>(() =>
20
- cfg.value.many ? (Array.isArray(model.value) ? model.value : []) : (typeof model.value === 'number' ? [model.value] : []))
21
-
22
- const collection = computed(() => cfg.value.collection)
23
- const locale = computed(() => props.locale)
24
- const labelField = computed(() => cfg.value.label)
25
-
26
- const { options, selected, loading, onSearch } = useRecordOptions(collection, ids, locale, labelField)
27
- const { t } = useT()
28
- </script>
29
-
30
- <template>
31
- <UiField :id="id" :label="name" :error="error" :required="required">
32
- <template #default="f">
33
- <UiCombobox
34
- v-model="model"
35
- :options="options"
36
- :selected="selected"
37
- :multiple="cfg.many"
38
- :loading="loading"
39
- :disabled="disabled"
40
- :placeholder="t('field.relation.placeholder')"
41
- :input-id="f.id"
42
- :invalid="f['aria-invalid'] === 'true'"
43
- :describedby="f['aria-describedby']"
44
- :required="f.required"
45
- @search="onSearch"
46
- />
47
- </template>
48
- </UiField>
49
- </template>
@@ -1,30 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { resolveFieldComponent } from '../../utils/field-registry'
4
- import { resolveLocalized } from '../../utils/localized'
5
- import { humanizeFieldName } from '../../utils/humanize'
6
- import FieldUnsupported from './Unsupported.vue'
7
- import type { FieldComponentProps } from '../../utils/field-component'
8
-
9
- const props = defineProps<FieldComponentProps>()
10
- const model = defineModel<unknown>()
11
- const { lang } = useT()
12
- const component = computed(() => resolveFieldComponent(props.field.type) ?? FieldUnsupported)
13
- // The widget renders `name` as its display label. Prefer the def's (localized) label, else humanize the
14
- // key (`siteName` → "Site Name") so the editor label matches the collection-list header. The field's wire
15
- // key is owned by the parent, so passing the human label down as `name` is safe.
16
- const label = computed(() => resolveLocalized(props.field.label, lang.value) ?? humanizeFieldName(props.name))
17
- </script>
18
-
19
- <template>
20
- <component
21
- :is="component"
22
- :id="id"
23
- v-model="model"
24
- :field="field"
25
- :name="label"
26
- :locale="locale"
27
- :error="error"
28
- :disabled="disabled"
29
- />
30
- </template>