@ditojs/admin 2.93.0 → 2.95.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 (47) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +1672 -1522
  3. package/dist/dito-admin.umd.js +6 -6
  4. package/package.json +6 -6
  5. package/src/components/DitoAccount.vue +7 -1
  6. package/src/components/DitoCreateButton.vue +6 -2
  7. package/src/components/DitoDialog.vue +1 -1
  8. package/src/components/DitoErrors.vue +1 -0
  9. package/src/components/DitoForm.vue +1 -0
  10. package/src/components/DitoFormInner.vue +5 -0
  11. package/src/components/DitoHeader.vue +1 -1
  12. package/src/components/DitoLabel.vue +6 -1
  13. package/src/components/DitoMenu.vue +1 -0
  14. package/src/components/DitoNavigation.vue +1 -1
  15. package/src/components/DitoNotifications.vue +1 -1
  16. package/src/components/DitoPane.vue +3 -0
  17. package/src/components/DitoPanel.vue +2 -0
  18. package/src/components/DitoSchema.vue +1 -0
  19. package/src/components/DitoScopes.vue +1 -0
  20. package/src/components/DitoSpinner.vue +2 -0
  21. package/src/components/DitoTableHead.vue +10 -0
  22. package/src/components/DitoTabs.vue +18 -2
  23. package/src/components/DitoTrail.vue +2 -1
  24. package/src/components/DitoTreeItem.vue +22 -3
  25. package/src/mixins/DitoMixin.js +4 -2
  26. package/src/mixins/PulldownMixin.js +9 -0
  27. package/src/types/DitoTypeCheckboxes.vue +2 -0
  28. package/src/types/DitoTypeList.vue +2 -0
  29. package/src/types/DitoTypeMultiselect.vue +2 -0
  30. package/src/types/DitoTypeRadio.vue +2 -0
  31. package/src/types/DitoTypeUpload.vue +4 -4
  32. package/types/index.d.ts +74 -11
  33. package/types/tests/admin.test-d.ts +0 -27
  34. package/types/tests/component-buttons.test-d.ts +0 -44
  35. package/types/tests/component-list.test-d.ts +0 -159
  36. package/types/tests/component-misc.test-d.ts +0 -137
  37. package/types/tests/component-object.test-d.ts +0 -69
  38. package/types/tests/component-section.test-d.ts +0 -174
  39. package/types/tests/component-select.test-d.ts +0 -107
  40. package/types/tests/components.test-d.ts +0 -81
  41. package/types/tests/context.test-d.ts +0 -31
  42. package/types/tests/fixtures.ts +0 -24
  43. package/types/tests/form.test-d.ts +0 -109
  44. package/types/tests/instance.test-d.ts +0 -20
  45. package/types/tests/schema-features.test-d.ts +0 -402
  46. package/types/tests/variance.test-d.ts +0 -125
  47. package/types/tests/view.test-d.ts +0 -146
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.93.0",
3
+ "version": "2.95.0",
4
4
  "type": "module",
5
5
  "description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/main/packages/admin",
@@ -40,8 +40,8 @@
40
40
  "not ie_mob > 0"
41
41
  ],
42
42
  "dependencies": {
43
- "@ditojs/ui": "^2.93.0",
44
- "@ditojs/utils": "^2.93.0"
43
+ "@ditojs/ui": "^2.95.0",
44
+ "@ditojs/utils": "^2.95.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@kyvg/vue3-notification": "^3.4.2",
@@ -89,9 +89,9 @@
89
89
  "vue-upload-component": "^3.1.17"
90
90
  },
91
91
  "devDependencies": {
92
- "@ditojs/build": "^2.93.0",
92
+ "@ditojs/build": "^2.95.0",
93
93
  "typescript": "^6.0.2",
94
- "vite": "^8.0.3"
94
+ "vite": "^8.0.8"
95
95
  },
96
- "gitHead": "360bd6a84c617b7f9b3750c50865380be3dbde8e"
96
+ "gitHead": "0cd76446659c5d30c18df8091bedb20cd84fb330"
97
97
  }
@@ -1,14 +1,20 @@
1
1
  <template lang="pug">
2
2
  .dito-account
3
3
  a(
4
+ role="button"
5
+ v-bind="pulldownTriggerAttributes"
4
6
  @mousedown.stop="onPulldownMouseDown()"
5
7
  )
6
8
  span {{ user.username }}
7
- ul.dito-pulldown(:class="{ 'dito-pulldown--open': pulldown.open }")
9
+ ul.dito-pulldown(
10
+ role="menu"
11
+ :class="{ 'dito-pulldown--open': pulldown.open }"
12
+ )
8
13
  li(
9
14
  v-for="(label, value) of items"
10
15
  )
11
16
  a.dito-pulldown__item(
17
+ role="menuitem"
12
18
  @mousedown.stop="onPulldownMouseDown(value)"
13
19
  @mouseup="onPulldownMouseUp(value)"
14
20
  ) {{ label }}
@@ -13,15 +13,19 @@
13
13
  button.dito-button(
14
14
  type="button"
15
15
  :disabled="disabled"
16
- v-bind="getButtonAttributes(verb)"
16
+ v-bind="{ ...pulldownTriggerAttributes, ...getButtonAttributes(verb) }"
17
17
  @mousedown.stop="onPulldownMouseDown()"
18
18
  ) {{ text }}
19
- ul.dito-pulldown(:class="{ 'dito-pulldown--open': pulldown.open }")
19
+ ul.dito-pulldown(
20
+ role="menu"
21
+ :class="{ 'dito-pulldown--open': pulldown.open }"
22
+ )
20
23
  li(
21
24
  v-for="(form, type) in creatableForms"
22
25
  v-show="shouldShowSchema(form)"
23
26
  )
24
27
  a.dito-pulldown__item(
28
+ role="menuitem"
25
29
  :class=`{
26
30
  'dito-pulldown__item--disabled': shouldDisableSchema(form)
27
31
  }`
@@ -2,7 +2,7 @@
2
2
  .dito-dialog(
3
3
  ref="dialog"
4
4
  role="dialog"
5
- aria-expanded="true"
5
+ :aria-label="schema.label || 'Dialog'"
6
6
  aria-modal="true"
7
7
  :style="{ '--width': settings.width ? `${settings.width}px` : null }"
8
8
  @mouseup="onMouseUp"
@@ -1,6 +1,7 @@
1
1
  <template lang="pug">
2
2
  .dito-errors(
3
3
  v-if="errors"
4
+ role="alert"
4
5
  )
5
6
  ul
6
7
  li(
@@ -15,6 +15,7 @@
15
15
  DitoFormInner(
16
16
  v-show="isActiveRoute"
17
17
  :nested="isNestedRoute"
18
+ :label="itemLabel"
18
19
  )
19
20
  //- Prevent implicit submission of the form, for example when typing enter
20
21
  //- in an input field.
@@ -6,6 +6,7 @@ div(
6
6
  slot
7
7
  form.dito-scroll-parent(
8
8
  v-else
9
+ :aria-label="label"
9
10
  @submit.prevent
10
11
  )
11
12
  slot
@@ -20,6 +21,10 @@ export default DitoComponent.component('DitoFormInner', {
20
21
  nested: {
21
22
  type: Boolean,
22
23
  default: false
24
+ },
25
+ label: {
26
+ type: String,
27
+ default: null
23
28
  }
24
29
  }
25
30
  })
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- nav.dito-header
2
+ nav.dito-header(:aria-busy="isLoading")
3
3
  DitoTrail
4
4
  DitoSpinner(
5
5
  v-if="isLoading"
@@ -69,7 +69,12 @@ export default DitoComponent.component('DitoLabel', {
69
69
  },
70
70
 
71
71
  attributes() {
72
- return this.collapsible ? { onClick: this.onClick } : {}
72
+ return this.collapsible
73
+ ? {
74
+ 'onClick': this.onClick,
75
+ 'aria-expanded': !this.collapsed
76
+ }
77
+ : {}
73
78
  },
74
79
 
75
80
  isActive() {
@@ -12,6 +12,7 @@ ul.dito-menu(
12
12
  a.dito-menu__link(
13
13
  :href="getItemHref(item)"
14
14
  :class="{ 'dito-menu__link--active': isActiveItem(item) }"
15
+ :aria-current="isActiveItem(item) ? 'page' : null"
15
16
  @click.prevent.stop="onClickItem(item)"
16
17
  ) {{ getLabel(item) }}
17
18
  DitoMenu.dito-menu__sub(
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- nav.dito-navigation.dito-scroll-parent
2
+ nav.dito-navigation.dito-scroll-parent(aria-label="Main navigation")
3
3
  h1
4
4
  RouterLink.dito-link(to="/") {{ appState.title }}
5
5
  DitoMenu.dito-scroll(:items="views")
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- .dito-notifications
2
+ .dito-notifications(aria-live="polite")
3
3
  .dito-header
4
4
  span
5
5
  .dito-notifications__inner
@@ -2,7 +2,10 @@
2
2
  <template lang="pug">
3
3
  .dito-pane(
4
4
  v-if="isPopulated && componentSchemas.length > 0"
5
+ :id="tab ? `${dataPath}-tabpanel-${tab}` : null"
5
6
  v-resize="onResizePane"
7
+ :role="tab ? 'tabpanel' : null"
8
+ :aria-labelledby="tab ? `${dataPath}-tab-${tab}` : null"
6
9
  :class="classes"
7
10
  )
8
11
  template(
@@ -4,6 +4,8 @@ component.dito-panel(
4
4
  v-if="shouldRenderSchema(panelSchema)"
5
5
  v-show="visible && (!panelTabComponent || panelTabComponent.visible)"
6
6
  :is="panelTag"
7
+ role="region"
8
+ :aria-label="getLabel(schema)"
7
9
  @submit.prevent
8
10
  )
9
11
  DitoSchema.dito-panel__schema(
@@ -41,6 +41,7 @@ slot(name="prepend")
41
41
  v-if="opened"
42
42
  v-model="selectedTab"
43
43
  :tabs="tabs"
44
+ :dataPath="dataPath"
44
45
  )
45
46
  DitoClipboard(
46
47
  v-if="clipboard"
@@ -10,6 +10,7 @@
10
10
  button.dito-button(
11
11
  type="button"
12
12
  :class="{ 'dito-button--selected': scope.name === query.scope }"
13
+ :aria-pressed="scope.name === query.scope"
13
14
  :title="scope.hint || getLabel(scope)"
14
15
  @click="navigate"
15
16
  ) {{ getLabel(scope) }}
@@ -1,6 +1,8 @@
1
1
  <template lang="pug">
2
2
  .dito-spinner(
3
3
  v-show="loading"
4
+ role="status"
5
+ aria-label="Loading"
4
6
  :style="{ '--color': color, '--size': size, '--margin': margin }"
5
7
  )
6
8
  .dito-spinner__pulse.dito-spinner__pulse1
@@ -6,6 +6,7 @@ thead.dito-table-head
6
6
  )
7
7
  th(
8
8
  v-if="shouldRenderSchema(column)"
9
+ scope="col"
9
10
  :class="getColumnClass(column)"
10
11
  )
11
12
  RouterLink(
@@ -17,6 +18,7 @@ thead.dito-table-head
17
18
  button.dito-button(
18
19
  type="button"
19
20
  :class="getSortClass(column)"
21
+ :aria-sort="getSortState(column)"
20
22
  @click="navigate"
21
23
  )
22
24
  .dito-button__order-arrows
@@ -64,6 +66,14 @@ export default DitoComponent.component('DitoTableHead', {
64
66
  : null
65
67
  },
66
68
 
69
+ getSortState(column) {
70
+ const states = { asc: 'ascending', desc: 'descending' }
71
+ return (
72
+ states[this.sort.name === column.name && this.sort.order] ||
73
+ 'none'
74
+ )
75
+ },
76
+
67
77
  getSortLink(column) {
68
78
  // Toggle order if the same column is clicked again.
69
79
  const order =
@@ -1,11 +1,16 @@
1
1
  <template lang="pug">
2
- .dito-tabs
2
+ .dito-tabs(role="tablist")
3
3
  template(
4
4
  v-for="(tabSchema, key) in tabs"
5
5
  )
6
6
  a.dito-tabs__link(
7
7
  v-if="shouldRenderSchema(tabSchema)"
8
+ :id="getTabId(key)"
8
9
  :key="key"
10
+ role="tab"
11
+ :aria-selected="modelValue === key"
12
+ :aria-controls="getPanelId(key)"
13
+ :tabindex="modelValue === key ? 0 : -1"
9
14
  :class="{ 'dito-tabs__link--active': modelValue === key }"
10
15
  @click="$emit('update:modelValue', key)"
11
16
  ) {{ getLabel(tabSchema, key) }}
@@ -19,7 +24,18 @@ export default DitoComponent.component('DitoTabs', {
19
24
  emits: ['update:modelValue'],
20
25
  props: {
21
26
  tabs: { type: Object, default: null },
22
- modelValue: { type: String, default: null }
27
+ modelValue: { type: String, default: null },
28
+ dataPath: { type: String, default: '' }
29
+ },
30
+
31
+ methods: {
32
+ getTabId(key) {
33
+ return `${this.dataPath}-tab-${key}`
34
+ },
35
+
36
+ getPanelId(key) {
37
+ return `${this.dataPath}-tabpanel-${key}`
38
+ }
23
39
  }
24
40
  })
25
41
  </script>
@@ -1,11 +1,12 @@
1
1
  <template lang="pug">
2
- .dito-trail
2
+ .dito-trail(aria-label="Breadcrumb")
3
3
  ul
4
4
  li(
5
5
  v-for="component in trail"
6
6
  )
7
7
  a.dito-trail__link(
8
8
  :class="{ 'dito-trail__link--active': component.path === $route.path }"
9
+ :aria-current="component.path === $route.path ? 'page' : null"
9
10
  :href="getComponentHref(component)"
10
11
  @click.prevent.stop="onClickComponent(component)"
11
12
  )
@@ -13,7 +13,12 @@
13
13
  )
14
14
  .dito-tree-branch(
15
15
  v-if="numEntries"
16
+ role="button"
17
+ tabindex="0"
18
+ :aria-label="ariaLabel"
19
+ :aria-expanded="opened"
16
20
  @click.stop="opened = !opened"
21
+ @keydown.enter.space.prevent="opened = !opened"
17
22
  )
18
23
  .dito-chevron(
19
24
  v-if="numEntries"
@@ -105,6 +110,7 @@ import SortableMixin from '../mixins/SortableMixin.js'
105
110
  import { appendDataPath } from '../utils/data.js'
106
111
  import { getSchemaAccessor } from '../utils/accessor.js'
107
112
  import { getNamedSchemas, hasFormSchema } from '../utils/schema.js'
113
+ import { stripHtml } from '@ditojs/utils'
108
114
 
109
115
  // @vue/component
110
116
  export default DitoComponent.component('DitoTreeItem', {
@@ -222,6 +228,10 @@ export default DitoComponent.component('DitoTreeItem', {
222
228
  return []
223
229
  },
224
230
 
231
+ ariaLabel() {
232
+ return this.label ? stripHtml(this.label) : null
233
+ },
234
+
225
235
  details() {
226
236
  const { numChildren } = this
227
237
  return (
@@ -311,8 +321,14 @@ export default DitoComponent.component('DitoTreeItem', {
311
321
  --chevron-indent: #{$chevron-indent};
312
322
 
313
323
  overflow: hidden;
324
+ padding: 0 $input-padding-hor;
325
+ margin: 0 (-$input-padding-hor);
314
326
 
315
327
  > .dito-tree-header {
328
+ // Extend header into item padding so active background reaches the edge:
329
+ margin: 0 (-$input-padding-hor);
330
+ padding: 0 $input-padding-hor;
331
+
316
332
  > .dito-tree-branch,
317
333
  > .dito-tree-leaf {
318
334
  // Use `--level` CSS variable to calculated the accumulated indent
@@ -346,8 +362,13 @@ export default DitoComponent.component('DitoTreeItem', {
346
362
  display: flex;
347
363
  align-items: baseline;
348
364
  gap: 0.25em;
349
- overflow: hidden;
365
+ min-width: 0;
350
366
  white-space: nowrap;
367
+
368
+ > :first-child {
369
+ overflow: hidden;
370
+ text-overflow: ellipsis;
371
+ }
351
372
  }
352
373
 
353
374
  .dito-tree-info {
@@ -385,8 +406,6 @@ export default DitoComponent.component('DitoTreeItem', {
385
406
  &--active {
386
407
  > .dito-tree-header {
387
408
  background: $color-active;
388
- padding: 0 $input-padding-hor;
389
- margin: 0 (-$input-padding-hor);
390
409
 
391
410
  > .dito-tree-branch {
392
411
  > .dito-chevron::before {
@@ -277,9 +277,11 @@ export default {
277
277
  },
278
278
 
279
279
  getButtonAttributes(verb) {
280
+ const label = labelize(verb)
280
281
  return {
281
- class: `dito-button--${verb}`,
282
- title: labelize(verb)
282
+ 'class': `dito-button--${verb}`,
283
+ 'title': label,
284
+ 'aria-label': label
283
285
  }
284
286
  },
285
287
 
@@ -27,6 +27,15 @@ export default {
27
27
  }
28
28
  },
29
29
 
30
+ computed: {
31
+ pulldownTriggerAttributes() {
32
+ return {
33
+ 'aria-haspopup': 'menu',
34
+ 'aria-expanded': this.pulldown.open
35
+ }
36
+ }
37
+ },
38
+
30
39
  methods: {
31
40
  onPulldownMouseDown(value = null) {
32
41
  if (value === null) {
@@ -1,6 +1,8 @@
1
1
  <template lang="pug">
2
2
  ul.dito-checkboxes(
3
3
  :id="dataPath"
4
+ role="group"
5
+ :aria-label="label"
4
6
  :class="`dito-layout--${schema.layout || 'vertical'}`"
5
7
  )
6
8
  li(
@@ -2,6 +2,8 @@
2
2
  .dito-list(
3
3
  v-if="isReady"
4
4
  :id="dataPath"
5
+ role="region"
6
+ :aria-label="label || labelize(schema.name)"
5
7
  )
6
8
  .dito-list__header(
7
9
  v-if="scopes || hasPagination"
@@ -10,8 +10,10 @@
10
10
  :parentContext="context"
11
11
  )
12
12
  VueMultiselect(
13
+ :id="dataPath"
13
14
  ref="element"
14
15
  v-model="selectedOptions"
16
+ :aria-label="label"
15
17
  :class="multiselectClasses"
16
18
  :showLabels="false"
17
19
  :placeholder="placeholder"
@@ -1,6 +1,8 @@
1
1
  <template lang="pug">
2
2
  ul.dito-radio-buttons(
3
3
  :id="dataPath"
4
+ role="radiogroup"
5
+ :aria-label="label"
4
6
  :class="`dito-layout--${schema.layout || 'vertical'}`"
5
7
  )
6
8
  li(
@@ -24,13 +24,13 @@
24
24
  //- Styling comes from `DitoTableHead`
25
25
  thead.dito-table-head
26
26
  tr
27
- th
27
+ th(scope="col")
28
28
  span File
29
- th
29
+ th(scope="col")
30
30
  span Size
31
- th
31
+ th(scope="col")
32
32
  span Status
33
- th
33
+ th(scope="col")
34
34
  span
35
35
  DitoDraggable(
36
36
  v-model="files"
package/types/index.d.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  TimeFormat
9
9
  } from '@ditojs/utils'
10
10
  import { RequireAtLeastOne, SetOptional } from 'type-fest'
11
- import { Component as VueComponent } from 'vue'
11
+ import { Component as VueComponent, ComponentPublicInstance } from 'vue'
12
12
  import { Router as VueRouter } from 'vue-router'
13
13
 
14
14
  declare global {
@@ -695,7 +695,19 @@ export interface SchemaSourceMixin<$Item> {
695
695
  *
696
696
  * @defaultValue `false`
697
697
  */
698
- creatable?: OrItemAccessor<$Item, {}, boolean | { label: string }>
698
+ creatable?: OrItemAccessor<
699
+ $Item,
700
+ {},
701
+ | boolean
702
+ | {
703
+ label?: string
704
+ /**
705
+ * Returns additional query parameters to include in the
706
+ * URL when navigating to the create form.
707
+ */
708
+ query?: (context: DitoContext) => Record<string, any>
709
+ }
710
+ >
699
711
  /**
700
712
  * Whether to add edit buttons next to the list items.
701
713
  *
@@ -788,11 +800,14 @@ export type SchemaOptions<$Item, $Option = any> =
788
800
  */
789
801
  groupBy?: keyof $Option
790
802
  /**
791
- * Custom equality function for comparing options.
803
+ * Custom equality function for matching a value to an
804
+ * option. Receives a context with `value` and `option`.
792
805
  */
793
806
  equals?: (
794
- a: $Option,
795
- b: $Option
807
+ context: DitoContext & {
808
+ value: any
809
+ option: $Option
810
+ }
796
811
  ) => boolean
797
812
  /**
798
813
  * Relative path to resolve options from data
@@ -1640,6 +1655,12 @@ export interface CheckboxesSchema<$Item = any>
1640
1655
  * The type of the component.
1641
1656
  */
1642
1657
  type: 'checkboxes'
1658
+ /**
1659
+ * Whether multiple values can be selected.
1660
+ *
1661
+ * @defaultValue `true`
1662
+ */
1663
+ multiple?: boolean
1643
1664
  /**
1644
1665
  * @defaultValue `'vertical'`
1645
1666
  */
@@ -1918,7 +1939,7 @@ export type DitoContext<$Item = any> = {
1918
1939
  /**
1919
1940
  * The current search term in select components.
1920
1941
  */
1921
- searchTerm: string | undefined
1942
+ searchTerm: string
1922
1943
  /**
1923
1944
  * Whether a button request is currently running.
1924
1945
  */
@@ -1976,9 +1997,22 @@ export type DitoContext<$Item = any> = {
1976
1997
  getResourceUrl(resource: Resource): string
1977
1998
  /** Displays a notification to the user. */
1978
1999
  notify(options: {
2000
+ /** Notification style. @defaultValue `'info'` */
1979
2001
  type?: LiteralUnion<'warning' | 'error' | 'info' | 'success'>
2002
+ /** Heading text. Defaults to a label matching `type`. */
1980
2003
  title?: string
2004
+ /** Body text, rendered as HTML paragraphs. */
1981
2005
  text: OrArrayOf<string>
2006
+ /**
2007
+ * The original error object. When `type` is `'error'`,
2008
+ * this is logged to the console alongside the message.
2009
+ */
2010
+ error?: unknown
2011
+ /**
2012
+ * Display duration in milliseconds. When omitted, calculated
2013
+ * automatically from content length. Use `0` for sticky.
2014
+ */
2015
+ duration?: number
1982
2016
  }): void
1983
2017
  }
1984
2018
 
@@ -1997,7 +2031,9 @@ export type DitoComponentInstance<
1997
2031
  $Members extends Record<string, any> = {}
1998
2032
  > = DitoComponentInstanceBase<$Item> & $Members
1999
2033
 
2000
- export interface DitoComponentInstanceBase<$Item = any> extends EmitterMixin {
2034
+ export interface DitoComponentInstanceBase<$Item = any>
2035
+ extends ComponentPublicInstance,
2036
+ EmitterMixin {
2001
2037
  // -- Data access (ValueMixin, ContextMixin, TypeMixin) --
2002
2038
 
2003
2039
  /** The current value of the component (getter/setter). */
@@ -2968,6 +3004,31 @@ export type SourceComponent<$Item = any> =
2968
3004
  | TreeListSchema<$Item>
2969
3005
  | TreeObjectSchema<$Item>
2970
3006
 
3007
+ /**
3008
+ * Field components that don't have an options mixin (excludes
3009
+ * select, multiselect, and source components like list/object/tree).
3010
+ */
3011
+ type NonOptionFieldComponent<$Item = any> = Exclude<
3012
+ NonSectionComponent<$Item>,
3013
+ SelectSchema<$Item> | MultiselectSchema<$Item> | SourceComponent<$Item>
3014
+ >
3015
+
3016
+ /**
3017
+ * Select/multiselect with a typed `$Option` for option callbacks.
3018
+ *
3019
+ * Note: This is intentionally an intersection type rather than
3020
+ * `SelectSchema<$Item, $Option> | MultiselectSchema<$Item, $Option>`,
3021
+ * because using the full interfaces in a union breaks TypeScript's
3022
+ * contextual typing for callbacks in the `Components` mapped type.
3023
+ */
3024
+ type OptionComponent<$Item, $Option> = BaseSchema<$Item> &
3025
+ SchemaOptionsMixin<$Item, $Option> &
3026
+ SchemaAffixMixin<$Item> &
3027
+ Pick<
3028
+ MultiselectSchema,
3029
+ 'multiple' | 'searchable' | 'stayOpen' | 'taggable'
3030
+ > & { type: 'select' | 'multiselect' }
3031
+
2971
3032
  /**
2972
3033
  * Strips properties with `never` values from a type.
2973
3034
  */
@@ -3047,14 +3108,16 @@ export type Components<$Item = any> = 0 extends 1 & $Item
3047
3108
  : {
3048
3109
  [K in keyof $Item]?: [$Item[K]] extends [never]
3049
3110
  ? NonSectionComponent<$Item> | SectionSchema<$Item>
3050
- : $Item[K] extends (infer E)[]
3111
+ : NonNullable<$Item[K]> extends (infer E)[]
3051
3112
  ? E extends Record<string, any>
3052
3113
  ? NonSectionComponent<E>
3053
3114
  : NonSectionComponent<$Item>
3054
- : $Item[K] extends Record<string, any>
3115
+ : NonNullable<$Item[K]> extends Record<string, any>
3055
3116
  ?
3056
- | NonSectionComponent<$Item>
3057
- | SectionSchema<$Item, $Item[K]>
3117
+ | NonOptionFieldComponent<$Item>
3118
+ | OptionComponent<$Item, NonNullable<$Item[K]>>
3119
+ | SourceComponent<NonNullable<$Item[K]>>
3120
+ | SectionSchema<$Item, NonNullable<$Item[K]>>
3058
3121
  :
3059
3122
  | NonSectionComponent<$Item>
3060
3123
  | SectionSchema<$Item>
@@ -1,27 +0,0 @@
1
- import { assertType, describe, it } from 'vitest'
2
- import type { default as DitoAdmin, View } from '../index.d.ts'
3
- import type { Entry } from './fixtures.ts'
4
-
5
- describe('DitoAdmin', () => {
6
- it('constructor accepts element and views option', () => {
7
- assertType<ConstructorParameters<typeof DitoAdmin>>([
8
- document.body,
9
- {
10
- views: {
11
- entries: {
12
- type: 'view',
13
- component: {
14
- type: 'list',
15
- form: {
16
- type: 'form',
17
- components: {
18
- title: { type: 'text' }
19
- }
20
- }
21
- }
22
- } satisfies View<Entry>
23
- }
24
- }
25
- ])
26
- })
27
- })