@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,116 +0,0 @@
1
- <script setup lang="ts">
2
- import type { IconName } from '../../utils/icons'
3
-
4
- withDefaults(
5
- defineProps<{
6
- variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
7
- size?: 'sm' | 'md' | 'lg'
8
- type?: 'button' | 'submit' | 'reset'
9
- disabled?: boolean
10
- loading?: boolean
11
- icon?: IconName
12
- }>(),
13
- { variant: 'secondary', size: 'md', type: 'button', disabled: false, loading: false },
14
- )
15
- </script>
16
-
17
- <template>
18
- <button
19
- :type="type"
20
- class="ui-button"
21
- :class="[`ui-button--${variant}`, `ui-button--${size}`]"
22
- :disabled="disabled || loading"
23
- :aria-busy="loading || undefined"
24
- >
25
- <span v-if="loading" class="ui-button__spinner" aria-hidden="true" />
26
- <KestrelUiIcon v-else-if="icon" :name="icon" class="ui-button__icon" />
27
- <span v-if="$slots.default" class="ui-button__label"><slot /></span>
28
- </button>
29
- </template>
30
-
31
- <style lang="scss">
32
- @use '../../assets/scss/mixins';
33
-
34
- .ui-button {
35
- @include mixins.focus-ring;
36
- display: inline-flex;
37
- align-items: center;
38
- justify-content: center;
39
- gap: var(--space-2);
40
- border: 1px solid transparent;
41
- border-radius: var(--radius-md);
42
- font-weight: var(--weight-medium);
43
- line-height: 1;
44
- transition:
45
- background-color var(--motion-fast) var(--ease-standard),
46
- border-color var(--motion-fast) var(--ease-standard);
47
-
48
- &:disabled {
49
- opacity: 0.6;
50
- cursor: not-allowed;
51
- }
52
-
53
- &--sm {
54
- padding: var(--space-1) var(--space-2);
55
- font-size: var(--text-sm);
56
- }
57
- &--md {
58
- padding: var(--space-2) var(--space-4);
59
- font-size: var(--text-base);
60
- }
61
- &--lg {
62
- padding: var(--space-3) var(--space-5);
63
- font-size: var(--text-lg);
64
- }
65
-
66
- &--primary {
67
- background: var(--color-primary);
68
- color: var(--color-on-primary);
69
-
70
- &:hover:not(:disabled) {
71
- background: var(--color-primary-hover);
72
- }
73
- }
74
- &--secondary {
75
- background: var(--color-surface);
76
- color: var(--color-text);
77
- border-color: var(--color-border);
78
-
79
- &:hover:not(:disabled) {
80
- background: var(--color-surface-2);
81
- border-color: var(--color-border-strong);
82
- }
83
- }
84
- &--ghost {
85
- background: transparent;
86
- color: var(--color-text);
87
-
88
- &:hover:not(:disabled) {
89
- background: var(--color-hover);
90
- }
91
- }
92
- &--danger {
93
- background: var(--color-danger-solid);
94
- color: var(--color-on-danger);
95
-
96
- &:hover:not(:disabled) {
97
- filter: brightness(0.93);
98
- }
99
- }
100
-
101
- &__spinner {
102
- width: 1em;
103
- height: 1em;
104
- border: 2px solid currentColor;
105
- border-top-color: transparent;
106
- border-radius: var(--radius-full);
107
- animation: ui-button-spin var(--motion-base) linear infinite;
108
- }
109
- }
110
-
111
- @keyframes ui-button-spin {
112
- to {
113
- transform: rotate(360deg);
114
- }
115
- }
116
- </style>
@@ -1,96 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { ToggleGroupRoot, ToggleGroupItem } from 'reka-ui'
4
- import UiIcon from './Icon.vue'
5
- import type { IconName } from '../../utils/icons'
6
-
7
- const props = withDefaults(
8
- defineProps<{
9
- options: { label: string; value: string; icon?: IconName }[]
10
- multiple?: boolean
11
- disabled?: boolean
12
- }>(),
13
- { multiple: false, disabled: false },
14
- )
15
-
16
- const model = defineModel<string | string[] | null>()
17
-
18
- const current = computed(() =>
19
- props.multiple ? (Array.isArray(model.value) ? model.value : []) : (model.value ?? undefined),
20
- )
21
-
22
- // reka's ToggleGroupRoot emits the broad `AcceptableValue`; an `unknown` param satisfies that contract and
23
- // we coerce back to our `string | string[]` model here.
24
- function onUpdate(v: unknown) {
25
- if (props.multiple) model.value = Array.isArray(v) ? (v as string[]) : []
26
- else model.value = v ? (v as string) : null
27
- }
28
- </script>
29
-
30
- <template>
31
- <ToggleGroupRoot
32
- :model-value="current"
33
- :type="multiple ? 'multiple' : 'single'"
34
- :disabled="disabled"
35
- class="ui-btngroup"
36
- @update:model-value="onUpdate"
37
- >
38
- <ToggleGroupItem
39
- v-for="o in options"
40
- :key="o.value"
41
- :value="o.value"
42
- :disabled="disabled"
43
- class="ui-btngroup__item"
44
- >
45
- <UiIcon v-if="o.icon" :name="o.icon" :size="15" />
46
- {{ o.label }}
47
- </ToggleGroupItem>
48
- </ToggleGroupRoot>
49
- </template>
50
-
51
- <style lang="scss">
52
- @use '../../assets/scss/mixins';
53
-
54
- .ui-btngroup {
55
- display: inline-flex;
56
- // Wrap is required: this is a shared primitive (field/Choice renders it with arbitrary options), so a
57
- // long set must break onto multiple rows rather than overflow/clip. A 2-item toggle never wraps anyway.
58
- flex-wrap: wrap;
59
- border: 1px solid var(--color-control-border, var(--color-border));
60
- border-radius: var(--radius-md);
61
- overflow: hidden;
62
- width: fit-content;
63
-
64
- &__item {
65
- @include mixins.focus-ring;
66
- display: inline-flex;
67
- align-items: center;
68
- gap: var(--space-1);
69
- padding: var(--space-2) var(--space-3);
70
- border: 0;
71
- border-inline-start: 1px solid var(--color-control-border, var(--color-border));
72
- background: var(--color-surface);
73
- color: var(--color-text);
74
- font-size: var(--text-sm);
75
- cursor: pointer;
76
-
77
- &:first-child {
78
- border-inline-start: 0;
79
- }
80
- &:hover {
81
- background: var(--color-hover);
82
- }
83
- // Neutral fill + an indigo label so the active segment reads at a glance (the fill alone is too
84
- // close to the hover wash to rely on). Fallback for use outside the themed admin shell.
85
- &[data-state='on'] {
86
- background: var(--color-active, var(--color-surface-2));
87
- color: var(--color-primary-on-fill, var(--color-primary));
88
- font-weight: var(--weight-medium);
89
- }
90
- &[data-disabled] {
91
- opacity: 0.6;
92
- cursor: not-allowed;
93
- }
94
- }
95
- }
96
- </style>
@@ -1,53 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, watch, onMounted } from 'vue'
3
-
4
- const props = withDefaults(
5
- defineProps<{
6
- disabled?: boolean
7
- /** Tri-state marker for a "some but not all" selection (e.g. a select-all header). */
8
- indeterminate?: boolean
9
- }>(),
10
- { disabled: false, indeterminate: false },
11
- )
12
-
13
- // Defaulted so the emitted value is a plain `boolean`: an undefined-able model would force every
14
- // handler to coerce, and an unbound checkbox is unchecked anyway.
15
- const model = defineModel<boolean>({ default: false })
16
-
17
- // `indeterminate` is a DOM PROPERTY with no HTML attribute, so it can't be bound in the template and must
18
- // be assigned imperatively: once on mount (the element now exists) and again whenever the prop changes.
19
- const input = ref<HTMLInputElement | null>(null)
20
- function syncIndeterminate() {
21
- if (input.value) input.value.indeterminate = props.indeterminate
22
- }
23
- onMounted(syncIndeterminate)
24
- watch(() => props.indeterminate, syncIndeterminate)
25
- </script>
26
-
27
- <template>
28
- <!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label -- id/label land via UiField's attrs fallthrough (single root element), invisible to static analysis -->
29
- <input
30
- ref="input"
31
- v-model="model"
32
- type="checkbox"
33
- :disabled="disabled"
34
- class="ui-checkbox"
35
- >
36
- </template>
37
-
38
- <style lang="scss">
39
- @use '../../assets/scss/mixins';
40
-
41
- .ui-checkbox {
42
- @include mixins.focus-ring;
43
- width: 1.25rem;
44
- height: 1.25rem;
45
- accent-color: var(--color-primary);
46
- cursor: pointer;
47
-
48
- &:disabled {
49
- opacity: 0.6;
50
- cursor: not-allowed;
51
- }
52
- }
53
- </style>
@@ -1,51 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import UiCheckbox from './Checkbox.vue'
4
-
5
- withDefaults(
6
- defineProps<{
7
- options: { label: string; value: string }[]
8
- disabled?: boolean
9
- }>(),
10
- { disabled: false },
11
- )
12
-
13
- const model = defineModel<string[]>()
14
- const selected = computed(() => model.value ?? [])
15
-
16
- function toggle(value: string, checked: boolean) {
17
- const set = new Set(selected.value)
18
- if (checked) set.add(value)
19
- else set.delete(value)
20
- model.value = [...set]
21
- }
22
- </script>
23
-
24
- <template>
25
- <div class="ui-checkbox-group" role="group">
26
- <!-- eslint-disable-next-line vuejs-accessibility/label-has-for -- native wrapping label around a custom UiCheckbox; no `for`/`id` pair needed, invisible to static analysis -->
27
- <label v-for="o in options" :key="o.value" class="ui-checkbox-group__item">
28
- <UiCheckbox
29
- :model-value="selected.includes(o.value)"
30
- :disabled="disabled"
31
- @update:model-value="(c) => toggle(o.value, c)"
32
- />
33
- <span>{{ o.label }}</span>
34
- </label>
35
- </div>
36
- </template>
37
-
38
- <style lang="scss">
39
- .ui-checkbox-group {
40
- display: flex;
41
- flex-direction: column;
42
- gap: var(--space-2);
43
-
44
- &__item {
45
- display: flex;
46
- align-items: center;
47
- gap: var(--space-2);
48
- cursor: pointer;
49
- }
50
- }
51
- </style>
@@ -1,345 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref, watch, nextTick } from 'vue'
3
- import UiIcon from './Icon.vue'
4
- import {
5
- ComboboxRoot, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxCancel,
6
- ComboboxContent, ComboboxViewport, ComboboxItem, ComboboxItemIndicator,
7
- } from 'reka-ui'
8
- import type { FieldOption } from '../../utils/field-component'
9
- import { reorder } from '../../utils/reorder'
10
-
11
- const props = withDefaults(
12
- defineProps<{
13
- options: FieldOption[]
14
- selected: FieldOption[]
15
- multiple?: boolean
16
- loading?: boolean
17
- placeholder?: string
18
- disabled?: boolean
19
- inputId?: string
20
- invalid?: boolean
21
- describedby?: string
22
- required?: boolean
23
- }>(),
24
- { multiple: false, loading: false, disabled: false, invalid: false, required: false },
25
- )
26
-
27
- const model = defineModel<number | number[] | null>()
28
- const emit = defineEmits<{ search: [term: string] }>()
29
-
30
- const query = ref('')
31
-
32
- // Reka's root model: an array for multiple, the value (or undefined) for single.
33
- const rootModel = computed<number | number[] | undefined>({
34
- get: () => (props.multiple
35
- ? (Array.isArray(model.value) ? model.value : [])
36
- : (typeof model.value === 'number' ? model.value : undefined)),
37
- set: (v) => { model.value = props.multiple ? ((v as number[]) ?? []) : (typeof v === 'number' ? v : null) },
38
- })
39
-
40
- function labelFor(id: number): string {
41
- return props.selected.find((s) => s.value === id)?.label
42
- ?? props.options.find((o) => o.value === id)?.label
43
- ?? `#${id}`
44
- }
45
- const displayValue = (id: unknown) => (typeof id === 'number' ? labelFor(id) : '')
46
-
47
- // In single mode Reka resyncs the input text to the selected label on select/blur;
48
- // that programmatic update must not be treated as a user search.
49
- const currentLabel = computed(() => (!props.multiple && typeof model.value === 'number' ? labelFor(model.value) : null))
50
-
51
- // A selected label can resolve asynchronously (id known, label fetched later) with no model change,
52
- // which Reka's display-value callback won't re-evaluate — mirror it into the single-mode input.
53
- watch(currentLabel, (lbl) => { if (lbl != null) query.value = lbl })
54
-
55
- function onInput(term: string) {
56
- query.value = term
57
- if (term === currentLabel.value) return
58
- emit('search', term)
59
- }
60
- // Focusing an empty box loads the first page of records, so options appear without typing.
61
- function onFocus() {
62
- if (!query.value.trim()) emit('search', '')
63
- }
64
- function removeChip(id: number) {
65
- if (Array.isArray(model.value)) model.value = model.value.filter((v) => v !== id)
66
- }
67
-
68
- const { t } = useT()
69
-
70
- const chips = ref<HTMLElement | null>(null)
71
- const liveMessage = ref('')
72
-
73
- function announceMove(label: string, to: number) {
74
- liveMessage.value = t('combobox.moved', { label, pos: to + 1, total: props.selected.length })
75
- }
76
-
77
- // The chip index IS the model index here, so commit reorders the model directly.
78
- const { dragIndex, overIndex, onDragStart, onDragEnter, onDragLeave, onDrop, onDragEnd } = useDragReorder({
79
- disabled: () => props.disabled || !Array.isArray(model.value),
80
- commit: (from, to) => {
81
- // Re-stated here rather than relying on the `disabled` predicate above: only the local check narrows
82
- // the model to the multi-value array `reorder` returns (and that `model.value` accepts back).
83
- if (!Array.isArray(model.value)) return
84
- const label = props.selected[from]?.label ?? ''
85
- model.value = reorder(model.value, from, to)
86
- nextTick(() => announceMove(label, to))
87
- },
88
- })
89
-
90
- function moveChip(from: number, to: number) {
91
- if (props.disabled || !Array.isArray(model.value) || to < 0 || to >= model.value.length) return
92
- const dir = to < from ? 'earlier' : 'later'
93
- const label = props.selected[from]?.label ?? ''
94
- model.value = reorder(model.value, from, to)
95
- // Keep keyboard focus on the moved chip: keyed reuse preserves it for the pressed button, but when that
96
- // button becomes disabled at a boundary the browser drops focus to <body> — redirect to the remove button.
97
- nextTick(() => {
98
- announceMove(label, to)
99
- const chip = chips.value?.children[to] as HTMLElement | undefined
100
- if (!chip) return
101
- const moveBtns = chip.querySelectorAll<HTMLButtonElement>('.ui-combobox__chip-move')
102
- const dirBtn = dir === 'earlier' ? moveBtns[0] : moveBtns[1]
103
- ;(dirBtn && !dirBtn.disabled ? dirBtn : chip.querySelector<HTMLButtonElement>('.ui-combobox__chip-remove'))?.focus()
104
- })
105
- }
106
- </script>
107
-
108
- <template>
109
- <ComboboxRoot
110
- v-model="rootModel"
111
- :multiple="multiple"
112
- :disabled="disabled"
113
- ignore-filter
114
- reset-model-value-on-clear
115
- :open-on-click="true"
116
- :open-on-focus="true"
117
- class="ui-combobox"
118
- >
119
- <ComboboxAnchor class="ui-combobox__anchor" :data-multiple="multiple || undefined" :aria-invalid="invalid || undefined">
120
- <!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions, vuejs-accessibility/no-redundant-roles -- @dragleave is a mouse-only progressive enhancement (the chip-move buttons below give the same reorder fully keyboard access); role="list" is NOT redundant here, `list-style: none` below strips the implicit list semantics in WebKit, see the "WebKit list-semantics fix" test -->
121
- <ul v-if="multiple && selected.length" ref="chips" class="ui-combobox__chips" role="list" @dragleave="onDragLeave">
122
- <!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -- drag handlers are a mouse-only progressive enhancement; the chip-move buttons below give the same reorder fully keyboard access -->
123
- <li
124
- v-for="(s, i) in selected"
125
- :key="s.value"
126
- class="ui-combobox__chip"
127
- :class="{ 'ui-combobox__chip--over': overIndex === i && dragIndex !== i }"
128
- :draggable="!disabled"
129
- @dragstart="onDragStart(i, $event)"
130
- @dragenter="onDragEnter(i)"
131
- @dragover.prevent="onDragEnter(i)"
132
- @drop.prevent="onDrop(i)"
133
- @dragend="onDragEnd"
134
- >
135
- <button
136
- type="button"
137
- draggable="false"
138
- class="ui-combobox__chip-move"
139
- :aria-label="t('combobox.move_earlier', { label: s.label })"
140
- :disabled="disabled || i === 0"
141
- @click="moveChip(i, i - 1)"
142
- ><UiIcon name="chevron-left" :size="14" /></button>
143
- <span>{{ s.label }}</span>
144
- <button
145
- type="button"
146
- draggable="false"
147
- class="ui-combobox__chip-move"
148
- :aria-label="t('combobox.move_later', { label: s.label })"
149
- :disabled="disabled || i === selected.length - 1"
150
- @click="moveChip(i, i + 1)"
151
- ><UiIcon name="chevron-right" :size="14" /></button>
152
- <button
153
- type="button"
154
- draggable="false"
155
- class="ui-combobox__chip-remove"
156
- :aria-label="t('combobox.remove', { label: s.label })"
157
- :disabled="disabled"
158
- @click="removeChip(s.value)"
159
- ><UiIcon name="x" :size="14" /></button>
160
- </li>
161
- </ul>
162
- <ComboboxInput
163
- :id="inputId"
164
- class="ui-combobox__input"
165
- :model-value="query"
166
- :placeholder="placeholder"
167
- :display-value="multiple ? undefined : displayValue"
168
- :disabled="disabled"
169
- :aria-invalid="invalid || undefined"
170
- :aria-describedby="describedby"
171
- :required="required || undefined"
172
- @update:model-value="onInput"
173
- @focus="onFocus"
174
- />
175
- <ComboboxCancel v-if="!multiple" class="ui-combobox__icon" :aria-label="t('combobox.clear')"><UiIcon name="x" :size="14" /></ComboboxCancel>
176
- <ComboboxTrigger class="ui-combobox__icon" :aria-label="t('combobox.toggle_options')"><UiIcon name="chevron-down" :size="14" /></ComboboxTrigger>
177
- </ComboboxAnchor>
178
-
179
- <ComboboxContent class="ui-combobox__content" :side-offset="4">
180
- <ComboboxViewport class="ui-combobox__viewport">
181
- <p v-if="loading" class="ui-combobox__note">{{ t('combobox.loading') }}</p>
182
- <p v-else-if="!options.length" class="ui-combobox__note">{{ t('combobox.no_results') }}</p>
183
- <ComboboxItem
184
- v-for="o in options"
185
- :key="o.value"
186
- :value="o.value"
187
- class="ui-combobox__item"
188
- >
189
- <span>{{ o.label }}</span>
190
- <ComboboxItemIndicator class="ui-combobox__check"><UiIcon name="check" :size="14" /></ComboboxItemIndicator>
191
- </ComboboxItem>
192
- </ComboboxViewport>
193
- </ComboboxContent>
194
-
195
- <span class="ui-combobox__live" aria-live="polite">{{ liveMessage }}</span>
196
- </ComboboxRoot>
197
- </template>
198
-
199
- <style lang="scss">
200
- @use '../../assets/scss/mixins';
201
-
202
- .ui-combobox {
203
- &__anchor {
204
- display: flex;
205
- flex-wrap: wrap;
206
- align-items: center;
207
- gap: var(--space-2);
208
- padding: var(--space-1) var(--space-2);
209
- border: 1px solid var(--color-control-border, var(--color-border));
210
- border-radius: var(--radius-md);
211
- background: var(--color-surface);
212
-
213
- &:focus-within { outline: 2px solid var(--color-focus); outline-offset: -2px; }
214
- &[aria-invalid='true'] { border-color: var(--color-danger); }
215
- }
216
- &__chips {
217
- display: flex;
218
- flex-wrap: wrap;
219
- gap: var(--space-1);
220
- list-style: none;
221
- margin: 0;
222
- padding: 0;
223
- }
224
- &__chip {
225
- display: inline-flex;
226
- align-items: center;
227
- gap: var(--space-1);
228
- padding: 0 var(--space-1) 0 var(--space-2);
229
- background: var(--color-bg);
230
- border: 1px solid var(--color-border);
231
- border-radius: var(--radius-sm);
232
- font-size: var(--text-sm);
233
- }
234
- &__chip-remove {
235
- @include mixins.focus-ring;
236
- display: inline-flex;
237
- align-items: center;
238
- justify-content: center;
239
- height: 1.5rem;
240
- padding: 0;
241
- border: 0;
242
- background: transparent;
243
- cursor: pointer;
244
- color: var(--color-text-muted);
245
- font-size: var(--text-base);
246
- line-height: 1;
247
- }
248
- &__chip-move {
249
- @include mixins.focus-ring;
250
- display: inline-flex;
251
- align-items: center;
252
- justify-content: center;
253
- height: 1.5rem;
254
- border: 0;
255
- background: transparent;
256
- cursor: pointer;
257
- color: var(--color-text-muted);
258
- font-size: var(--text-base);
259
- line-height: 1;
260
- // Collapsed + transparent when idle so the chip stays compact, but NOT display/visibility:none —
261
- // the button must remain focusable so keyboard users can Tab to it (which then reveals it via
262
- // :focus-within below). Revealed on hover/focus, and always shown where there is no hover (touch).
263
- opacity: 0;
264
- max-width: 0;
265
- padding: 0;
266
- overflow: hidden;
267
- // animate only size, not opacity, so the focus ring shows immediately on keyboard focus
268
- transition: max-width 0.1s, padding 0.1s;
269
-
270
- &:disabled { cursor: default; } // stays collapsed (the reveal selector excludes :disabled)
271
- }
272
- &__live { @include mixins.sr-only; }
273
- &__chip {
274
- &:hover,
275
- &:focus-within {
276
- .ui-combobox__chip-move:not(:disabled) {
277
- opacity: 1;
278
- max-width: 1.5rem;
279
- padding: 0 var(--space-1);
280
- }
281
- }
282
- }
283
- @media (hover: none) {
284
- &__chip-move:not(:disabled) {
285
- opacity: 1;
286
- max-width: 1.5rem;
287
- padding: 0 var(--space-1);
288
- }
289
- }
290
- &__chip--over {
291
- outline: 2px solid var(--color-primary);
292
- outline-offset: -2px;
293
- }
294
- &__input {
295
- flex: 1;
296
- min-width: 6rem;
297
- border: 0;
298
- outline: none;
299
- background: transparent;
300
- color: var(--color-text);
301
- font-size: var(--text-base);
302
- padding: var(--space-1) 0;
303
- }
304
- &__icon {
305
- @include mixins.focus-ring;
306
- border: 0;
307
- background: transparent;
308
- cursor: pointer;
309
- color: var(--color-text-muted);
310
- }
311
- &__content {
312
- z-index: var(--z-dropdown);
313
- width: var(--reka-combobox-trigger-width);
314
- max-height: 16rem;
315
- overflow: hidden;
316
- background: var(--color-surface);
317
- border: 1px solid var(--color-border);
318
- border-radius: var(--radius-md);
319
- box-shadow: var(--shadow-md);
320
- }
321
- &__viewport {
322
- max-height: 16rem;
323
- overflow-y: auto;
324
- padding: var(--space-1);
325
- }
326
- &__note {
327
- padding: var(--space-2) var(--space-3);
328
- color: var(--color-text-muted);
329
- font-size: var(--text-sm);
330
- }
331
- &__item {
332
- display: flex;
333
- align-items: center;
334
- justify-content: space-between;
335
- gap: var(--space-2);
336
- padding: var(--space-2) var(--space-3);
337
- border-radius: var(--radius-sm);
338
- cursor: pointer;
339
-
340
- &[data-highlighted] { background: var(--color-hover); outline: none; }
341
- &[data-state='checked'] { font-weight: var(--weight-medium); }
342
- }
343
- &__check { color: var(--color-primary); }
344
- }
345
- </style>