@michaelthielemann/kestrel 4.1.3 → 5.0.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 (340) hide show
  1. package/README.md +26 -201
  2. package/dist/boot.d.ts +45 -0
  3. package/dist/boot.js +274 -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 +157 -0
  15. package/dist/defineConfig.js +41 -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/cron.d.ts +10 -0
  43. package/dist/triggers/cron.js +44 -0
  44. package/dist/triggers/cronMatch.d.ts +10 -0
  45. package/dist/triggers/cronMatch.js +68 -0
  46. package/dist/triggers/http.d.ts +58 -0
  47. package/dist/triggers/http.js +292 -0
  48. package/package.json +25 -151
  49. package/NOTICE +0 -12
  50. package/kestrel.config.ts +0 -28
  51. package/layers/access/nuxt.config.ts +0 -5
  52. package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
  53. package/layers/access/server/middleware/access-guard.ts +0 -32
  54. package/layers/access/server/types.d.ts +0 -7
  55. package/layers/admin/app/components/AdminAccount.vue +0 -212
  56. package/layers/admin/app/components/AdminNav.vue +0 -44
  57. package/layers/admin/app/components/BlockFields.vue +0 -67
  58. package/layers/admin/app/components/BlockPreview.vue +0 -413
  59. package/layers/admin/app/components/BlockTree.vue +0 -433
  60. package/layers/admin/app/components/BlocksBody.vue +0 -163
  61. package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
  62. package/layers/admin/app/components/CollectionEditor.vue +0 -324
  63. package/layers/admin/app/components/CollectionList.vue +0 -429
  64. package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
  65. package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
  66. package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
  67. package/layers/admin/app/components/CollectionListPager.vue +0 -70
  68. package/layers/admin/app/components/CollectionListTable.vue +0 -347
  69. package/layers/admin/app/components/EditorStatus.vue +0 -211
  70. package/layers/admin/app/components/EditorUnsupported.vue +0 -28
  71. package/layers/admin/app/components/FieldsBody.vue +0 -23
  72. package/layers/admin/app/components/LocaleBar.vue +0 -107
  73. package/layers/admin/app/components/PageFields.vue +0 -129
  74. package/layers/admin/app/components/PageFieldsPane.vue +0 -39
  75. package/layers/admin/app/components/SeoFields.vue +0 -210
  76. package/layers/admin/app/components/SingletonEditor.vue +0 -93
  77. package/layers/admin/app/composables/useAuth.ts +0 -68
  78. package/layers/admin/app/composables/useBlockTree.ts +0 -95
  79. package/layers/admin/app/composables/useBlocks.ts +0 -7
  80. package/layers/admin/app/composables/useCachedResource.ts +0 -35
  81. package/layers/admin/app/composables/useCollectionOps.ts +0 -102
  82. package/layers/admin/app/composables/useCollections.ts +0 -6
  83. package/layers/admin/app/composables/useContentLocales.ts +0 -23
  84. package/layers/admin/app/composables/useEditForm.ts +0 -402
  85. package/layers/admin/app/composables/useLinkResolver.ts +0 -40
  86. package/layers/admin/app/composables/useListBatchActions.ts +0 -78
  87. package/layers/admin/app/composables/useListColumns.ts +0 -53
  88. package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
  89. package/layers/admin/app/composables/useListRows.ts +0 -75
  90. package/layers/admin/app/composables/useListSelection.ts +0 -28
  91. package/layers/admin/app/composables/useListUrlState.ts +0 -53
  92. package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
  93. package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
  94. package/layers/admin/app/composables/usePublishStatus.ts +0 -107
  95. package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
  96. package/layers/admin/app/composables/useTheme.ts +0 -36
  97. package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
  98. package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
  99. package/layers/admin/app/layouts/admin.vue +0 -266
  100. package/layers/admin/app/middleware/admin-auth.ts +0 -4
  101. package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
  102. package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
  103. package/layers/admin/app/pages/admin/index.vue +0 -118
  104. package/layers/admin/app/pages/admin/login.vue +0 -68
  105. package/layers/admin/app/pages/admin/media.vue +0 -28
  106. package/layers/admin/app/pages/admin/references.vue +0 -146
  107. package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
  108. package/layers/admin/app/plugins/reauth.client.ts +0 -20
  109. package/layers/admin/app/utils/admin-style-guard.ts +0 -20
  110. package/layers/admin/app/utils/block-tree.ts +0 -214
  111. package/layers/admin/app/utils/collection-ops.ts +0 -69
  112. package/layers/admin/app/utils/dead-refs.ts +0 -30
  113. package/layers/admin/app/utils/edit-form.ts +0 -267
  114. package/layers/admin/app/utils/editor-expose.ts +0 -36
  115. package/layers/admin/app/utils/editor-form-context.ts +0 -82
  116. package/layers/admin/app/utils/editor-registry.ts +0 -20
  117. package/layers/admin/app/utils/field-keys.ts +0 -11
  118. package/layers/admin/app/utils/list-cell.ts +0 -46
  119. package/layers/admin/app/utils/list-columns.ts +0 -142
  120. package/layers/admin/app/utils/list-query.ts +0 -96
  121. package/layers/admin/app/utils/nav-active.ts +0 -9
  122. package/layers/admin/app/utils/populate-blocks.ts +0 -288
  123. package/layers/admin/app/utils/preview-channel.ts +0 -62
  124. package/layers/admin/app/utils/preview-viewport.ts +0 -80
  125. package/layers/admin/app/utils/reauth.ts +0 -67
  126. package/layers/admin/app/utils/record-title.ts +0 -15
  127. package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
  128. package/layers/admin/app/utils/safe-redirect.ts +0 -12
  129. package/layers/admin/nuxt.config.ts +0 -20
  130. package/layers/auth/nuxt.config.ts +0 -1
  131. package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
  132. package/layers/collections/nuxt.config.ts +0 -1
  133. package/layers/collections/server/plugins/01.register.ts +0 -29
  134. package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
  135. package/layers/core/app/composables/layouts.ts +0 -5
  136. package/layers/core/app/utils/field-factories.ts +0 -129
  137. package/layers/core/app/utils/layouts.ts +0 -38
  138. package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
  139. package/layers/core/modules/auto-discovery/index.ts +0 -112
  140. package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
  141. package/layers/core/modules/auto-discovery/scan.ts +0 -170
  142. package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
  143. package/layers/core/modules/component-namespace/index.ts +0 -44
  144. package/layers/core/modules/component-namespace/shared.ts +0 -24
  145. package/layers/core/modules/kestrel/app-shell.ts +0 -55
  146. package/layers/core/modules/kestrel/auto-imports.ts +0 -44
  147. package/layers/core/modules/kestrel/index.ts +0 -144
  148. package/layers/core/modules/plugin-order/index.ts +0 -69
  149. package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
  150. package/layers/core/nuxt.config.ts +0 -10
  151. package/layers/core/server/api/[...path].ts +0 -98
  152. package/layers/core/server/plugins/00.config.ts +0 -12
  153. package/layers/core/server/plugins/00.migrate.ts +0 -12
  154. package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
  155. package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
  156. package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
  157. package/layers/core/server/plugins/02.schema-sync.ts +0 -58
  158. package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
  159. package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
  160. package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
  161. package/layers/core/server/tasks/db/migrate-module.ts +0 -83
  162. package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
  163. package/layers/core/server/tasks/db/migrate.ts +0 -50
  164. package/layers/core/server/utils/server-config.ts +0 -81
  165. package/layers/fields/nuxt.config.ts +0 -1
  166. package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
  167. package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
  168. package/layers/media/app/components/KestrelImg.vue +0 -94
  169. package/layers/media/app/components/MediaConflictDialog.vue +0 -35
  170. package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
  171. package/layers/media/app/components/MediaGrid.vue +0 -113
  172. package/layers/media/app/components/MediaImage.vue +0 -70
  173. package/layers/media/app/components/MediaLibrary.vue +0 -456
  174. package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
  175. package/layers/media/app/components/MediaPathBar.vue +0 -163
  176. package/layers/media/app/components/MediaPicker.vue +0 -19
  177. package/layers/media/app/components/MediaRenameDialog.vue +0 -32
  178. package/layers/media/app/components/MediaTable.vue +0 -75
  179. package/layers/media/app/components/MediaThumb.vue +0 -25
  180. package/layers/media/app/components/MediaToolbar.vue +0 -90
  181. package/layers/media/app/components/MediaUploadDialog.vue +0 -87
  182. package/layers/media/app/components/MediaViewer.vue +0 -137
  183. package/layers/media/app/components/field/Media.vue +0 -263
  184. package/layers/media/app/composables/useBlurUp.ts +0 -55
  185. package/layers/media/app/composables/useMediaClipboard.ts +0 -14
  186. package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
  187. package/layers/media/app/composables/useMediaDnd.ts +0 -56
  188. package/layers/media/app/composables/useMediaLibrary.ts +0 -152
  189. package/layers/media/app/composables/useMediaOps.ts +0 -50
  190. package/layers/media/app/composables/useMediaRelocate.ts +0 -39
  191. package/layers/media/app/composables/useMediaResolver.ts +0 -50
  192. package/layers/media/app/composables/useMediaUpload.ts +0 -93
  193. package/layers/media/app/composables/useMediaVariant.ts +0 -34
  194. package/layers/media/app/plugins/field-media.client.ts +0 -6
  195. package/layers/media/app/utils/ai-disclosure.ts +0 -19
  196. package/layers/media/app/utils/dnd.ts +0 -69
  197. package/layers/media/app/utils/library.ts +0 -81
  198. package/layers/media/app/utils/ops.ts +0 -60
  199. package/layers/media/app/utils/picture-model.ts +0 -125
  200. package/layers/media/app/utils/thumbhash.ts +0 -19
  201. package/layers/media/app/utils/variant-name.ts +0 -20
  202. package/layers/media/nuxt.config.ts +0 -7
  203. package/layers/media/server/middleware/ondemand-variants.ts +0 -38
  204. package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
  205. package/layers/media/server/plugins/02.register-media.ts +0 -22
  206. package/layers/media/server/plugins/04.variant-capture.ts +0 -27
  207. package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
  208. package/layers/media/server/tasks/media/backfill.ts +0 -17
  209. package/layers/public/app/app.vue +0 -5
  210. package/layers/public/app/components/BlockRenderer.vue +0 -56
  211. package/layers/public/app/components/KestrelLink.vue +0 -26
  212. package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
  213. package/layers/public/app/composables/public-page.ts +0 -24
  214. package/layers/public/app/error.vue +0 -12
  215. package/layers/public/app/layouts/default.vue +0 -5
  216. package/layers/public/app/pages/[...slug].vue +0 -239
  217. package/layers/public/app/pages/__kestrel/preview.vue +0 -46
  218. package/layers/public/app/utils/block-edit-context.ts +0 -16
  219. package/layers/public/app/utils/json-ld.ts +0 -139
  220. package/layers/public/app/utils/link-href.ts +0 -46
  221. package/layers/public/app/utils/page-head.ts +0 -84
  222. package/layers/public/app/utils/page-layout.ts +0 -18
  223. package/layers/public/app/utils/preview-protocol.ts +0 -117
  224. package/layers/public/app/utils/public-route.ts +0 -46
  225. package/layers/public/app/utils/site-head.ts +0 -43
  226. package/layers/public/modules/deploy-output/index.ts +0 -80
  227. package/layers/public/modules/prerender-routes/discover.ts +0 -37
  228. package/layers/public/modules/prerender-routes/index.ts +0 -88
  229. package/layers/public/modules/prerender-routes/routes.ts +0 -16
  230. package/layers/public/modules/prune-media/index.ts +0 -48
  231. package/layers/public/modules/prune-media/prune-media.ts +0 -81
  232. package/layers/public/nuxt.config.ts +0 -14
  233. package/layers/public/server/delivery-live/serve.ts +0 -111
  234. package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
  235. package/layers/public/server/pipelines/index.ts +0 -23
  236. package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
  237. package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
  238. package/layers/public/server/plugins/02.register-links.ts +0 -16
  239. package/layers/public/server/plugins/03.redirects.ts +0 -55
  240. package/layers/public/server/plugins/05.plan-publish.ts +0 -5
  241. package/layers/public/server/plugins/zz.publish.ts +0 -127
  242. package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
  243. package/layers/public/server/routes/llms.txt.get.ts +0 -66
  244. package/layers/public/server/routes/redirects.json.get.ts +0 -58
  245. package/layers/public/server/routes/robots.txt.get.ts +0 -11
  246. package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
  247. package/layers/public/server/tasks/publish/run.ts +0 -32
  248. package/layers/public/server/utils/publish/render-live.ts +0 -26
  249. package/layers/ui/app/assets/scss/_base.scss +0 -16
  250. package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
  251. package/layers/ui/app/assets/scss/_mixins.scss +0 -50
  252. package/layers/ui/app/assets/scss/_reset.scss +0 -65
  253. package/layers/ui/app/assets/scss/_tokens.scss +0 -183
  254. package/layers/ui/app/assets/scss/main.scss +0 -4
  255. package/layers/ui/app/components/field/Boolean.vue +0 -30
  256. package/layers/ui/app/components/field/Choice.vue +0 -73
  257. package/layers/ui/app/components/field/Datetime.vue +0 -98
  258. package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
  259. package/layers/ui/app/components/field/Json.vue +0 -62
  260. package/layers/ui/app/components/field/Layout.vue +0 -138
  261. package/layers/ui/app/components/field/Link.vue +0 -112
  262. package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
  263. package/layers/ui/app/components/field/Number.vue +0 -30
  264. package/layers/ui/app/components/field/Relation.vue +0 -49
  265. package/layers/ui/app/components/field/Renderer.vue +0 -30
  266. package/layers/ui/app/components/field/Repeater.vue +0 -460
  267. package/layers/ui/app/components/field/Richtext.vue +0 -26
  268. package/layers/ui/app/components/field/Slug.vue +0 -65
  269. package/layers/ui/app/components/field/Text.vue +0 -35
  270. package/layers/ui/app/components/field/Unsupported.vue +0 -34
  271. package/layers/ui/app/components/ui/Alert.vue +0 -50
  272. package/layers/ui/app/components/ui/Brand.vue +0 -22
  273. package/layers/ui/app/components/ui/Button.vue +0 -116
  274. package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
  275. package/layers/ui/app/components/ui/Checkbox.vue +0 -53
  276. package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
  277. package/layers/ui/app/components/ui/Combobox.vue +0 -345
  278. package/layers/ui/app/components/ui/DatePicker.vue +0 -92
  279. package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
  280. package/layers/ui/app/components/ui/Dialog.vue +0 -107
  281. package/layers/ui/app/components/ui/EmptyState.vue +0 -46
  282. package/layers/ui/app/components/ui/Field.vue +0 -58
  283. package/layers/ui/app/components/ui/Fieldset.vue +0 -65
  284. package/layers/ui/app/components/ui/Icon.vue +0 -44
  285. package/layers/ui/app/components/ui/Menu.vue +0 -54
  286. package/layers/ui/app/components/ui/NumberInput.vue +0 -175
  287. package/layers/ui/app/components/ui/Richtext.vue +0 -141
  288. package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
  289. package/layers/ui/app/components/ui/Select.vue +0 -52
  290. package/layers/ui/app/components/ui/TextInput.vue +0 -111
  291. package/layers/ui/app/components/ui/Textarea.vue +0 -39
  292. package/layers/ui/app/components/ui/TimeInput.vue +0 -18
  293. package/layers/ui/app/components/ui/Toasts.vue +0 -103
  294. package/layers/ui/app/components/ui/Tooltip.vue +0 -45
  295. package/layers/ui/app/composables/useAdminLang.ts +0 -8
  296. package/layers/ui/app/composables/useDragReorder.ts +0 -41
  297. package/layers/ui/app/composables/useEchoGuard.ts +0 -28
  298. package/layers/ui/app/composables/useLinkField.ts +0 -70
  299. package/layers/ui/app/composables/useRecordOptions.ts +0 -84
  300. package/layers/ui/app/composables/useRepeater.ts +0 -99
  301. package/layers/ui/app/composables/useT.ts +0 -32
  302. package/layers/ui/app/composables/useToast.ts +0 -46
  303. package/layers/ui/app/i18n/de.ts +0 -432
  304. package/layers/ui/app/i18n/en.ts +0 -434
  305. package/layers/ui/app/utils/boolean-field.ts +0 -10
  306. package/layers/ui/app/utils/field-component.ts +0 -22
  307. package/layers/ui/app/utils/field-empty.ts +0 -21
  308. package/layers/ui/app/utils/field-registry.ts +0 -36
  309. package/layers/ui/app/utils/humanize.ts +0 -14
  310. package/layers/ui/app/utils/icons.ts +0 -143
  311. package/layers/ui/app/utils/iso-date.ts +0 -18
  312. package/layers/ui/app/utils/localized.ts +0 -13
  313. package/layers/ui/app/utils/record-context.ts +0 -15
  314. package/layers/ui/app/utils/reorder.ts +0 -8
  315. package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
  316. package/layers/ui/app/utils/useFieldA11y.ts +0 -40
  317. package/layers/ui/nuxt.config.ts +0 -9
  318. package/nuxt.config.ts +0 -108
  319. package/scripts/consumer-template-ci.mjs +0 -160
  320. package/scripts/copy-create-payload.mjs +0 -49
  321. package/scripts/coverage-all.mjs +0 -163
  322. package/scripts/dashboard.mjs +0 -89
  323. package/scripts/hash-password.mjs +0 -29
  324. package/scripts/kestrel.mjs +0 -240
  325. package/scripts/lib/cli.mjs +0 -129
  326. package/scripts/lib/coverage-model.mjs +0 -51
  327. package/scripts/lib/password.mjs +0 -19
  328. package/scripts/lib/scaffold.mjs +0 -205
  329. package/scripts/port-graph.mjs +0 -350
  330. package/scripts/typecheck.mjs +0 -62
  331. package/templates/starter/README.md +0 -65
  332. package/templates/starter/_env.example +0 -17
  333. package/templates/starter/_gitignore +0 -26
  334. package/templates/starter/_package.json +0 -22
  335. package/templates/starter/app/app.vue +0 -7
  336. package/templates/starter/app/blocks/Prose.vue +0 -13
  337. package/templates/starter/app/layouts/default.vue +0 -6
  338. package/templates/starter/nuxt.config.ts +0 -23
  339. package/templates/starter/pnpm-workspace.yaml +0 -8
  340. 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>