@michaelthielemann/kestrel 4.1.3 → 5.0.2

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