@michaelthielemann/kestrel 4.1.2 → 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,92 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import UiIcon from './Icon.vue'
4
- import {
5
- DatePickerRoot, DatePickerField, DatePickerInput, DatePickerTrigger,
6
- DatePickerContent, DatePickerArrow, DatePickerCalendar,
7
- DatePickerHeader, DatePickerPrev, DatePickerHeading, DatePickerNext,
8
- DatePickerGrid, DatePickerGridHead, DatePickerGridBody, DatePickerGridRow,
9
- DatePickerHeadCell, DatePickerCell, DatePickerCellTrigger,
10
- } from 'reka-ui'
11
- import type { DateValue } from '@internationalized/date'
12
- import { isoToDate, dateToIso, type IsoPrecision } from '../../utils/iso-date'
13
-
14
- const { t } = useT()
15
-
16
- const props = withDefaults(
17
- defineProps<{
18
- precision?: IsoPrecision
19
- disabled?: boolean
20
- locale?: string
21
- // a11y: the segmented control has no single labelable <input>, so the field name is carried as an
22
- // aria-label on the role=group field, with the error/hint + invalid/required state wired alongside.
23
- ariaLabel?: string
24
- describedby?: string
25
- invalid?: boolean
26
- required?: boolean
27
- }>(),
28
- { precision: 'datetime', disabled: false },
29
- )
30
- const model = defineModel<string | null>()
31
-
32
- // Match the native inputs, which format to the browser locale. Overridable for a future admin-locale setting.
33
- const displayLocale = computed(() => props.locale ?? (typeof navigator !== 'undefined' ? navigator.language : 'en'))
34
- const granularity = computed(() => (props.precision === 'date' ? 'day' : 'minute'))
35
- const value = computed<DateValue | undefined>({
36
- get: () => isoToDate(model.value, props.precision),
37
- set: (v) => { model.value = dateToIso(v) },
38
- })
39
- </script>
40
-
41
- <template>
42
- <DatePickerRoot v-model="value" :granularity="granularity" :disabled="disabled" :locale="displayLocale">
43
- <DatePickerField
44
- v-slot="{ segments }"
45
- class="ui-datepicker__field"
46
- role="group"
47
- :aria-label="ariaLabel"
48
- :aria-describedby="describedby"
49
- :aria-invalid="invalid || undefined"
50
- :aria-required="required || undefined"
51
- >
52
- <DatePickerInput
53
- v-for="item in segments"
54
- :key="item.part"
55
- :part="item.part"
56
- class="ui-datepicker__segment"
57
- >{{ item.value }}</DatePickerInput>
58
- <DatePickerTrigger class="ui-datepicker__trigger" :aria-label="t('datePicker.openCalendar')"><UiIcon name="calendar" :size="16" /></DatePickerTrigger>
59
- </DatePickerField>
60
-
61
- <DatePickerContent class="ui-datepicker__content" :side-offset="6">
62
- <DatePickerArrow class="ui-datepicker__arrow" />
63
- <DatePickerCalendar v-slot="{ weekDays, grid }" class="ui-datepicker__calendar">
64
- <DatePickerHeader class="ui-datepicker__header">
65
- <DatePickerPrev class="ui-datepicker__nav" :aria-label="t('datePicker.previousMonth')">‹</DatePickerPrev>
66
- <DatePickerHeading class="ui-datepicker__heading" />
67
- <DatePickerNext class="ui-datepicker__nav" :aria-label="t('datePicker.nextMonth')">›</DatePickerNext>
68
- </DatePickerHeader>
69
- <DatePickerGrid v-for="month in grid" :key="month.value.toString()" class="ui-datepicker__grid">
70
- <DatePickerGridHead>
71
- <DatePickerGridRow class="ui-datepicker__row">
72
- <DatePickerHeadCell v-for="day in weekDays" :key="day" class="ui-datepicker__weekday">{{ day }}</DatePickerHeadCell>
73
- </DatePickerGridRow>
74
- </DatePickerGridHead>
75
- <DatePickerGridBody>
76
- <DatePickerGridRow v-for="(weekDates, i) in month.rows" :key="`w${i}`" class="ui-datepicker__row">
77
- <DatePickerCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
78
- <DatePickerCellTrigger :day="weekDate" :month="month.value" class="ui-datepicker__cell" />
79
- </DatePickerCell>
80
- </DatePickerGridRow>
81
- </DatePickerGridBody>
82
- </DatePickerGrid>
83
- </DatePickerCalendar>
84
- </DatePickerContent>
85
- </DatePickerRoot>
86
- </template>
87
-
88
- <style lang="scss">
89
- // Shared, global (non-scoped) datepicker chrome — the calendar is teleported to <body>.
90
- @use '../../assets/scss/datepicker';
91
- @include datepicker.ui-datepicker;
92
- </style>
@@ -1,100 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import UiIcon from './Icon.vue'
4
- import {
5
- DateRangePickerRoot, DateRangePickerField, DateRangePickerInput, DateRangePickerTrigger,
6
- DateRangePickerContent, DateRangePickerArrow, DateRangePickerCalendar,
7
- DateRangePickerHeader, DateRangePickerPrev, DateRangePickerHeading, DateRangePickerNext,
8
- DateRangePickerGrid, DateRangePickerGridHead, DateRangePickerGridBody, DateRangePickerGridRow,
9
- DateRangePickerHeadCell, DateRangePickerCell, DateRangePickerCellTrigger,
10
- } from 'reka-ui'
11
- import type { DateValue } from '@internationalized/date'
12
- import { isoToDate, dateToIso, type IsoPrecision } from '../../utils/iso-date'
13
-
14
- const { t } = useT()
15
- const props = withDefaults(
16
- defineProps<{
17
- precision?: IsoPrecision
18
- disabled?: boolean
19
- locale?: string
20
- // a11y: see DatePicker — the field name rides as an aria-label on the role=group field.
21
- ariaLabel?: string
22
- describedby?: string
23
- invalid?: boolean
24
- required?: boolean
25
- }>(),
26
- { precision: 'datetime', disabled: false },
27
- )
28
- const model = defineModel<{ start: string; end: string } | null>()
29
-
30
- const displayLocale = computed(() => props.locale ?? (typeof navigator !== 'undefined' ? navigator.language : 'en'))
31
- const granularity = computed(() => (props.precision === 'date' ? 'day' : 'minute'))
32
- const value = computed<{ start: DateValue | undefined; end: DateValue | undefined }>({
33
- get: () => ({
34
- start: isoToDate(model.value?.start, props.precision),
35
- end: isoToDate(model.value?.end, props.precision),
36
- }),
37
- set: (v) => {
38
- const start = dateToIso(v.start)
39
- const end = dateToIso(v.end)
40
- // Preserve a partial selection (Reka commits start before end) as empty strings.
41
- model.value = start || end ? { start: start ?? '', end: end ?? '' } : null
42
- },
43
- })
44
- </script>
45
-
46
- <template>
47
- <DateRangePickerRoot v-model="value" :granularity="granularity" :disabled="disabled" :locale="displayLocale">
48
- <DateRangePickerField
49
- v-slot="{ segments }"
50
- class="ui-datepicker__field"
51
- role="group"
52
- :aria-label="ariaLabel"
53
- :aria-describedby="describedby"
54
- :aria-invalid="invalid || undefined"
55
- :aria-required="required || undefined"
56
- >
57
- <template v-for="item in segments.start" :key="`s-${item.part}`">
58
- <DateRangePickerInput :part="item.part" type="start" class="ui-datepicker__segment">{{ item.value }}</DateRangePickerInput>
59
- </template>
60
- <span class="ui-datepicker__sep" aria-hidden="true">–</span>
61
- <template v-for="item in segments.end" :key="`e-${item.part}`">
62
- <DateRangePickerInput :part="item.part" type="end" class="ui-datepicker__segment">{{ item.value }}</DateRangePickerInput>
63
- </template>
64
- <DateRangePickerTrigger class="ui-datepicker__trigger" :aria-label="t('dateRange.openCalendar')"><UiIcon name="calendar" :size="16" /></DateRangePickerTrigger>
65
- </DateRangePickerField>
66
-
67
- <DateRangePickerContent class="ui-datepicker__content" :side-offset="6">
68
- <DateRangePickerArrow class="ui-datepicker__arrow" />
69
- <DateRangePickerCalendar v-slot="{ weekDays, grid }" class="ui-datepicker__calendar">
70
- <DateRangePickerHeader class="ui-datepicker__header">
71
- <DateRangePickerPrev class="ui-datepicker__nav" :aria-label="t('dateRange.previousMonth')">‹</DateRangePickerPrev>
72
- <DateRangePickerHeading class="ui-datepicker__heading" />
73
- <DateRangePickerNext class="ui-datepicker__nav" :aria-label="t('dateRange.nextMonth')">›</DateRangePickerNext>
74
- </DateRangePickerHeader>
75
- <DateRangePickerGrid v-for="month in grid" :key="month.value.toString()" class="ui-datepicker__grid">
76
- <DateRangePickerGridHead>
77
- <DateRangePickerGridRow class="ui-datepicker__row">
78
- <DateRangePickerHeadCell v-for="day in weekDays" :key="day" class="ui-datepicker__weekday">{{ day }}</DateRangePickerHeadCell>
79
- </DateRangePickerGridRow>
80
- </DateRangePickerGridHead>
81
- <DateRangePickerGridBody>
82
- <DateRangePickerGridRow v-for="(weekDates, i) in month.rows" :key="`w${i}`" class="ui-datepicker__row">
83
- <DateRangePickerCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
84
- <DateRangePickerCellTrigger :day="weekDate" :month="month.value" class="ui-datepicker__cell" />
85
- </DateRangePickerCell>
86
- </DateRangePickerGridRow>
87
- </DateRangePickerGridBody>
88
- </DateRangePickerGrid>
89
- </DateRangePickerCalendar>
90
- </DateRangePickerContent>
91
- </DateRangePickerRoot>
92
- </template>
93
-
94
- <style lang="scss">
95
- // Shares the global (non-scoped) datepicker chrome — the calendar is teleported to <body>, so a
96
- // `scoped` block would silently drop these styles. Included explicitly rather than borrowing them
97
- // from DatePicker being present on the page.
98
- @use '../../assets/scss/datepicker';
99
- @include datepicker.ui-datepicker;
100
- </style>
@@ -1,107 +0,0 @@
1
- <script setup lang="ts">
2
- import { DialogRoot, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose } from 'reka-ui'
3
- import UiIcon from './Icon.vue'
4
-
5
- const props = defineProps<{ open: boolean; title: string; description?: string; size?: 'md' | 'lg' | 'xl' }>()
6
- const emit = defineEmits<{ 'update:open': [boolean] }>()
7
- const { t } = useT()
8
-
9
- // Rendering in place means dialog inputs are form-associated with any ancestor form (the record
10
- // editor): Enter in a text-entry input would implicitly submit it — saving the half-edited record
11
- // from inside a picker. Swallow that default here; Enter on buttons (and click-like inputs) keeps
12
- // its activation behavior, and a dialog can still opt into Enter via its own @keydown.enter handler.
13
- const CLICK_INPUT_TYPES = new Set(['button', 'submit', 'reset', 'checkbox', 'radio', 'file'])
14
- function onKeydown(e: KeyboardEvent) {
15
- if (e.key !== 'Enter' || e.isComposing) return
16
- const el = e.target
17
- if (el instanceof HTMLInputElement && !CLICK_INPUT_TYPES.has(el.type)) e.preventDefault()
18
- }
19
- </script>
20
-
21
- <template>
22
- <!-- Reka Dialog, rendered IN PLACE (no DialogPortal). Reka provides the focus trap, scroll lock,
23
- Escape/outside-click dismissal, ARIA wiring and focus restore; rendering in place (rather than
24
- teleporting) keeps the dialog synchronously DOM-testable and needs no force-mount hack. The
25
- admin's flat layouts don't need a portal to escape clipping — add DialogPortal here if that
26
- ever changes. -->
27
- <DialogRoot :open="props.open" @update:open="emit('update:open', $event)">
28
- <DialogOverlay class="ui-dialog__overlay" />
29
- <DialogContent
30
- class="ui-dialog__content"
31
- :class="`ui-dialog__content--${size ?? 'md'}`"
32
- v-bind="description ? {} : { 'aria-describedby': undefined }"
33
- @keydown="onKeydown"
34
- >
35
- <header class="ui-dialog__header">
36
- <DialogTitle class="ui-dialog__title">{{ title }}</DialogTitle>
37
- <DialogClose data-test="dialog-close" class="ui-dialog__close" :aria-label="t('common.close')"><UiIcon name="x" :size="16" /></DialogClose>
38
- </header>
39
- <DialogDescription v-if="description" class="ui-dialog__desc">{{ description }}</DialogDescription>
40
- <div class="ui-dialog__body"><slot /></div>
41
- <footer v-if="$slots.footer" class="ui-dialog__footer"><slot name="footer" /></footer>
42
- </DialogContent>
43
- </DialogRoot>
44
- </template>
45
-
46
- <style lang="scss" scoped>
47
- .ui-dialog__overlay {
48
- position: fixed;
49
- inset: 0;
50
- z-index: var(--z-dialog);
51
- background: var(--color-scrim);
52
- }
53
- .ui-dialog__content {
54
- position: fixed;
55
- top: 50%;
56
- left: 50%;
57
- transform: translate(-50%, -50%);
58
- z-index: var(--z-dialog);
59
- max-height: calc(100svh - var(--space-6));
60
- overflow: auto;
61
- background: var(--color-surface);
62
- border: 1px solid var(--color-border);
63
- border-radius: var(--radius-lg);
64
- box-shadow: var(--shadow-lg);
65
- padding: var(--space-4);
66
-
67
- &:focus,
68
- &:focus-visible { outline: none; }
69
- }
70
- .ui-dialog__content--md { width: min(32rem, calc(100vw - 2rem)); }
71
- .ui-dialog__content--lg { width: min(64rem, calc(100vw - 2rem)); }
72
- .ui-dialog__content--xl { width: min(80rem, calc(100vw - 2rem)); }
73
- .ui-dialog__header {
74
- display: flex;
75
- align-items: center;
76
- justify-content: space-between;
77
- gap: var(--space-2);
78
- margin-bottom: var(--space-3);
79
- }
80
- .ui-dialog__title {
81
- font-size: var(--text-lg);
82
- font-weight: var(--weight-bold);
83
- margin: 0;
84
- }
85
- .ui-dialog__desc {
86
- margin: 0 0 var(--space-3);
87
- color: var(--color-text-muted);
88
- }
89
- .ui-dialog__close {
90
- background: none;
91
- border: 0;
92
- font-size: var(--text-xl);
93
- line-height: 1;
94
- cursor: pointer;
95
- color: var(--color-text-muted);
96
- border-radius: var(--radius-sm);
97
-
98
- &:hover { background: var(--color-hover); color: var(--color-text); }
99
- &:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
100
- }
101
- .ui-dialog__footer {
102
- display: flex;
103
- justify-content: flex-end;
104
- gap: var(--space-2);
105
- margin-top: var(--space-4);
106
- }
107
- </style>
@@ -1,46 +0,0 @@
1
- <script setup lang="ts">
2
- import type { IconName } from '../../utils/icons'
3
-
4
- defineProps<{ icon?: IconName; title: string; description?: string }>()
5
- </script>
6
-
7
- <template>
8
- <div class="ui-empty">
9
- <KestrelUiIcon v-if="icon" :name="icon" size="1.5rem" class="ui-empty__icon" />
10
- <p class="ui-empty__title">{{ title }}</p>
11
- <p v-if="description" class="ui-empty__desc">{{ description }}</p>
12
- <div v-if="$slots.action" class="ui-empty__action"><slot name="action" /></div>
13
- </div>
14
- </template>
15
-
16
- <style lang="scss">
17
- .ui-empty {
18
- display: flex;
19
- flex-direction: column;
20
- align-items: center;
21
- justify-content: center;
22
- gap: var(--space-2);
23
- padding: var(--space-6) var(--space-4);
24
- text-align: center;
25
- border: 1px solid var(--color-border);
26
- border-radius: var(--radius-lg);
27
- background: var(--color-surface);
28
-
29
- &__icon {
30
- margin-bottom: var(--space-1);
31
- color: var(--color-text-subtle);
32
- }
33
- &__title {
34
- font-weight: var(--weight-medium);
35
- color: var(--color-text);
36
- }
37
- &__desc {
38
- font-size: var(--text-sm);
39
- color: var(--color-text-muted);
40
- max-width: 40ch;
41
- }
42
- &__action {
43
- margin-top: var(--space-2);
44
- }
45
- }
46
- </style>
@@ -1,58 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { useFieldA11y } from '../../utils/useFieldA11y'
4
-
5
- const props = defineProps<{
6
- label?: string
7
- hint?: string
8
- error?: string | null
9
- required?: boolean
10
- id?: string
11
- }>()
12
-
13
- const { fieldId, hintId, errId, describedby, ariaInvalid } = useFieldA11y(props)
14
-
15
- const slotProps = computed(() => ({
16
- id: fieldId.value,
17
- 'aria-invalid': ariaInvalid.value,
18
- 'aria-describedby': describedby.value,
19
- required: props.required,
20
- }))
21
- </script>
22
-
23
- <template>
24
- <div class="ui-field" :data-invalid="error ? 'true' : undefined">
25
- <!-- eslint-disable-next-line vuejs-accessibility/label-has-for -- the matching control is the default slot's content, invisible to static analysis across the slot boundary -->
26
- <label v-if="label" :for="fieldId" class="ui-field__label">
27
- {{ label }}<span v-if="required" class="ui-field__required" aria-hidden="true">*</span>
28
- </label>
29
- <slot v-bind="slotProps" />
30
- <p v-if="hint" :id="hintId" class="ui-field__hint">{{ hint }}</p>
31
- <p v-if="error" :id="errId" class="ui-field__error" role="alert">{{ error }}</p>
32
- </div>
33
- </template>
34
-
35
- <style lang="scss">
36
- .ui-field {
37
- display: flex;
38
- flex-direction: column;
39
- gap: var(--space-1);
40
-
41
- &__label {
42
- font-size: var(--text-sm);
43
- font-weight: var(--weight-medium);
44
- }
45
- &__required {
46
- color: var(--color-danger);
47
- margin-inline-start: var(--space-1);
48
- }
49
- &__hint {
50
- font-size: var(--text-sm);
51
- color: var(--color-text-muted);
52
- }
53
- &__error {
54
- font-size: var(--text-sm);
55
- color: var(--color-danger);
56
- }
57
- }
58
- </style>
@@ -1,65 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { useFieldA11y } from '../../utils/useFieldA11y'
4
-
5
- const props = defineProps<{
6
- label?: string
7
- hint?: string
8
- error?: string | null
9
- required?: boolean
10
- id?: string
11
- }>()
12
-
13
- // No `fieldId`: a group has no single control to point a label `for` at (see below).
14
- const { hintId, errId, describedby, ariaInvalid, ariaRequired } = useFieldA11y(props)
15
-
16
- // No `id` — a group has no single control to point a label `for` at; the
17
- // <legend> labels the group natively. Consumers spread these onto the group root.
18
- const slotProps = computed(() => ({
19
- 'aria-invalid': ariaInvalid.value,
20
- 'aria-describedby': describedby.value,
21
- 'aria-required': ariaRequired.value,
22
- required: props.required,
23
- }))
24
- </script>
25
-
26
- <template>
27
- <fieldset class="ui-fieldset" :data-invalid="error ? 'true' : undefined">
28
- <legend v-if="label" class="ui-fieldset__legend">
29
- {{ label }}<span v-if="required" class="ui-fieldset__required" aria-hidden="true">*</span>
30
- </legend>
31
- <slot v-bind="slotProps" />
32
- <p v-if="hint" :id="hintId" class="ui-fieldset__hint">{{ hint }}</p>
33
- <p v-if="error" :id="errId" class="ui-fieldset__error" role="alert">{{ error }}</p>
34
- </fieldset>
35
- </template>
36
-
37
- <style lang="scss">
38
- .ui-fieldset {
39
- display: flex;
40
- flex-direction: column;
41
- gap: var(--space-1);
42
- min-inline-size: 0;
43
- margin: 0;
44
- padding: 0;
45
- border: 0;
46
-
47
- &__legend {
48
- padding: 0;
49
- font-size: var(--text-sm);
50
- font-weight: var(--weight-medium);
51
- }
52
- &__required {
53
- color: var(--color-danger);
54
- margin-inline-start: var(--space-1);
55
- }
56
- &__hint {
57
- font-size: var(--text-sm);
58
- color: var(--color-text-muted);
59
- }
60
- &__error {
61
- font-size: var(--text-sm);
62
- color: var(--color-danger);
63
- }
64
- }
65
- </style>
@@ -1,44 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, useAttrs } from 'vue'
3
- import { isIconName, resolveIconBody, type IconSpec } from '../../utils/icons'
4
-
5
- const props = withDefaults(defineProps<{ name: IconSpec; size?: string | number; label?: string }>(), { size: '1em' })
6
- const attrs = useAttrs()
7
-
8
- const dim = computed(() => (typeof props.size === 'number' ? `${props.size}px` : props.size))
9
- const body = computed(() => resolveIconBody(props.name))
10
- // `data-icon` exposes the resolved registry key (or `custom` for raw SVG) for styling/tests.
11
- const dataIcon = computed(() => (isIconName(props.name) ? props.name : 'custom'))
12
- // A caller can give the icon an accessible name via fallthrough attrs (role/aria-label) instead of the
13
- // `label` prop — either way it must not stay `aria-hidden`, or the name is invisible to AT.
14
- const hasA11yName = computed(() => props.label != null || attrs.role != null || attrs['aria-label'] != null || attrs['aria-labelledby'] != null)
15
- </script>
16
-
17
- <template>
18
- <!-- v-html body is either a static registry icon or developer-supplied SVG allowlist-sanitised by
19
- resolveIconBody → sanitizeIconSvg (no script-capable elements/attributes survive). -->
20
- <!-- eslint-disable-next-line vue/no-v-html -- sanitized via resolveIconBody -> sanitizeIconSvg (allowlist-only tags/attrs) -->
21
- <svg v-html="body"
22
- class="ui-icon"
23
- :data-icon="dataIcon"
24
- :width="dim"
25
- :height="dim"
26
- viewBox="0 0 24 24"
27
- fill="none"
28
- stroke="currentColor"
29
- stroke-width="2"
30
- stroke-linecap="round"
31
- stroke-linejoin="round"
32
- :role="label ? 'img' : undefined"
33
- :aria-label="label || undefined"
34
- :aria-hidden="hasA11yName ? undefined : 'true'"
35
- />
36
- </template>
37
-
38
- <style lang="scss">
39
- .ui-icon {
40
- display: inline-block;
41
- vertical-align: middle;
42
- flex-shrink: 0;
43
- }
44
- </style>
@@ -1,54 +0,0 @@
1
- <script setup lang="ts">
2
- import { ContextMenuRoot, ContextMenuTrigger, ContextMenuPortal, ContextMenuContent, ContextMenuItem } from 'reka-ui'
3
-
4
- export interface MenuItem { label: string; value: string; danger?: boolean; disabled?: boolean }
5
- defineProps<{ items: MenuItem[] }>()
6
- const emit = defineEmits<{ select: [string] }>()
7
- </script>
8
-
9
- <template>
10
- <ContextMenuRoot>
11
- <ContextMenuTrigger as-child>
12
- <slot />
13
- </ContextMenuTrigger>
14
- <ContextMenuPortal>
15
- <ContextMenuContent class="ui-menu" :collision-padding="8">
16
- <ContextMenuItem
17
- v-for="it in items"
18
- :key="it.value"
19
- :disabled="it.disabled"
20
- class="ui-menu__item"
21
- :class="{ 'ui-menu__item--danger': it.danger }"
22
- @select="emit('select', it.value)"
23
- >{{ it.label }}</ContextMenuItem>
24
- </ContextMenuContent>
25
- </ContextMenuPortal>
26
- </ContextMenuRoot>
27
- </template>
28
-
29
- <style lang="scss">
30
- // Not scoped: Reka teleports the menu content to <body>, where a scoped data-v selector would no
31
- // longer match — so the surface background/border silently dropped out (Image: a backgroundless,
32
- // hard-to-read context menu). The other portaled overlays (Combobox/DatePicker/Select) are global too.
33
- .ui-menu {
34
- min-width: 11rem;
35
- background: var(--color-surface);
36
- border: 1px solid var(--color-border);
37
- border-radius: var(--radius-md);
38
- box-shadow: var(--shadow-md);
39
- padding: var(--space-1);
40
- z-index: var(--z-dropdown);
41
- }
42
- .ui-menu__item {
43
- display: block;
44
- width: 100%;
45
- padding: var(--space-1) var(--space-3);
46
- border-radius: var(--radius-sm);
47
- font-size: var(--text-sm);
48
- cursor: pointer;
49
- user-select: none;
50
- &[data-highlighted] { background: var(--color-hover); outline: none; }
51
- &[data-disabled] { color: var(--color-text-muted); cursor: default; }
52
- &--danger { color: var(--color-danger); }
53
- }
54
- </style>