@ditojs/admin 2.92.0 → 2.94.1

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 (50) hide show
  1. package/dist/dito-admin.es.js +1668 -1518
  2. package/dist/dito-admin.umd.js +6 -6
  3. package/package.json +14 -15
  4. package/src/components/DitoAccount.vue +7 -1
  5. package/src/components/DitoCreateButton.vue +6 -2
  6. package/src/components/DitoDialog.vue +1 -1
  7. package/src/components/DitoErrors.vue +1 -0
  8. package/src/components/DitoForm.vue +1 -0
  9. package/src/components/DitoFormInner.vue +5 -0
  10. package/src/components/DitoHeader.vue +1 -1
  11. package/src/components/DitoLabel.vue +6 -1
  12. package/src/components/DitoMenu.vue +1 -0
  13. package/src/components/DitoNavigation.vue +1 -1
  14. package/src/components/DitoNotifications.vue +1 -1
  15. package/src/components/DitoPane.vue +3 -0
  16. package/src/components/DitoPanel.vue +2 -0
  17. package/src/components/DitoSchema.vue +1 -0
  18. package/src/components/DitoScopes.vue +1 -0
  19. package/src/components/DitoSpinner.vue +2 -0
  20. package/src/components/DitoTableHead.vue +10 -0
  21. package/src/components/DitoTabs.vue +18 -2
  22. package/src/components/DitoTrail.vue +2 -1
  23. package/src/components/DitoTreeItem.vue +10 -0
  24. package/src/mixins/DitoMixin.js +4 -2
  25. package/src/mixins/PulldownMixin.js +9 -0
  26. package/src/mixins/SortableMixin.js +3 -1
  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/src/utils/ui/dist/dito-ui.es.js +2729 -0
  33. package/src/utils/ui/dist/dito-ui.umd.js +2744 -0
  34. package/src/utils/ui/dist/style.css +1139 -0
  35. package/types/index.d.ts +74 -11
  36. package/types/tests/admin.test-d.ts +0 -27
  37. package/types/tests/component-buttons.test-d.ts +0 -44
  38. package/types/tests/component-list.test-d.ts +0 -159
  39. package/types/tests/component-misc.test-d.ts +0 -137
  40. package/types/tests/component-object.test-d.ts +0 -69
  41. package/types/tests/component-section.test-d.ts +0 -174
  42. package/types/tests/component-select.test-d.ts +0 -107
  43. package/types/tests/components.test-d.ts +0 -81
  44. package/types/tests/context.test-d.ts +0 -31
  45. package/types/tests/fixtures.ts +0 -24
  46. package/types/tests/form.test-d.ts +0 -109
  47. package/types/tests/instance.test-d.ts +0 -20
  48. package/types/tests/schema-features.test-d.ts +0 -402
  49. package/types/tests/variance.test-d.ts +0 -125
  50. package/types/tests/view.test-d.ts +0 -146
@@ -1,174 +0,0 @@
1
- import { expectTypeOf, assertType, describe, it } from 'vitest'
2
- import type { Components, Form } from '../index.d.ts'
3
- import type { Address, Entry, Parent, ParentWithAddress } from './fixtures.ts'
4
-
5
- type ParentWithSection = Parent & {
6
- details: never
7
- }
8
-
9
- describe('Section components', () => {
10
- it('accepts section with nested components', () => {
11
- assertType<Components<ParentWithSection>>({
12
- details: {
13
- type: 'section',
14
- components: {
15
- title: { type: 'text' }
16
- }
17
- }
18
- })
19
- })
20
-
21
- it('provides typed item in section callbacks', () => {
22
- assertType<Components<ParentWithSection>>({
23
- details: {
24
- type: 'section',
25
- label({ item }) {
26
- expectTypeOf(item).not.toBeAny()
27
- expectTypeOf(item.title).toBeString()
28
- expectTypeOf(item.entries).toEqualTypeOf<Entry[]>()
29
- expectTypeOf(item).not.toHaveProperty('details')
30
- return item.title
31
- }
32
- }
33
- })
34
- })
35
-
36
- it('rejects invalid component keys in section', () => {
37
- assertType<Components<ParentWithSection>>({
38
- details: {
39
- type: 'section',
40
- components: {
41
- title: { type: 'text' },
42
- // @ts-expect-error 'nonExistent' is not a key of ParentWithSection
43
- nonExistent: { type: 'text' }
44
- }
45
- }
46
- })
47
- })
48
-
49
- it('accepts section with form prop', () => {
50
- const sectionForm: Form<ParentWithSection> = {
51
- type: 'form',
52
- components: {
53
- title: { type: 'text' }
54
- }
55
- }
56
-
57
- assertType<Components<ParentWithSection>>({
58
- details: {
59
- type: 'section',
60
- form: sectionForm
61
- }
62
- })
63
- })
64
- })
65
-
66
- describe('Section tabs', () => {
67
- it('accepts tabs on a non-nested section', () => {
68
- assertType<Components<ParentWithSection>>({
69
- details: {
70
- type: 'section',
71
- tabs: {
72
- general: {
73
- type: 'tab',
74
- components: {
75
- title: { type: 'text' }
76
- }
77
- }
78
- }
79
- }
80
- })
81
- })
82
-
83
- it('accepts tabs on a nested section typed against nested data', () => {
84
- assertType<Components<ParentWithAddress>>({
85
- address: {
86
- type: 'section',
87
- nested: true,
88
- tabs: {
89
- main: {
90
- type: 'tab',
91
- components: {
92
- street: { type: 'text' },
93
- city: { type: 'text' }
94
- }
95
- }
96
- }
97
- }
98
- })
99
- })
100
- })
101
-
102
- describe('Nested sections', () => {
103
- it('types nested section components against the value type', () => {
104
- assertType<Components<ParentWithAddress>>({
105
- address: {
106
- type: 'section',
107
- nested: true,
108
- components: {
109
- street: { type: 'text' },
110
- city: { type: 'text' }
111
- }
112
- }
113
- })
114
- })
115
-
116
- it('rejects invalid keys in nested section components', () => {
117
- assertType<Components<ParentWithAddress>>({
118
- address: {
119
- type: 'section',
120
- nested: true,
121
- components: {
122
- street: { type: 'text' },
123
- // @ts-expect-error 'zipCode' is not a key of Address
124
- zipCode: { type: 'text' }
125
- }
126
- }
127
- })
128
- })
129
-
130
- it('types item as Address in nested section callbacks', () => {
131
- assertType<Components<ParentWithAddress>>({
132
- address: {
133
- type: 'section',
134
- nested: true,
135
- components: {
136
- street: {
137
- type: 'text',
138
- onChange({ item }) {
139
- expectTypeOf(item).not.toBeAny()
140
- expectTypeOf(item).toEqualTypeOf<Address>()
141
- }
142
- }
143
- }
144
- }
145
- })
146
- })
147
-
148
- it('types nested section form against the value type', () => {
149
- assertType<Components<ParentWithAddress>>({
150
- address: {
151
- type: 'section',
152
- nested: true,
153
- form: {
154
- type: 'form',
155
- components: {
156
- street: { type: 'text' },
157
- city: { type: 'text' }
158
- }
159
- } satisfies Form<Address>
160
- }
161
- })
162
- })
163
-
164
- it('non-nested section on data key accepts parent item keys', () => {
165
- assertType<Components<ParentWithAddress>>({
166
- address: {
167
- type: 'section',
168
- components: {
169
- title: { type: 'text' }
170
- }
171
- }
172
- })
173
- })
174
- })
@@ -1,107 +0,0 @@
1
- import { expectTypeOf, assertType, describe, it } from 'vitest'
2
- import type { SelectSchema, MultiselectSchema } from '../index.d.ts'
3
- import type { Entry } from './fixtures.ts'
4
-
5
- describe('SelectSchema', () => {
6
- it('accepts simple string array options', () => {
7
- assertType<SelectSchema<Entry>>({
8
- type: 'select',
9
- options: ['draft', 'published', 'archived']
10
- })
11
- })
12
-
13
- it('accepts label/value object options', () => {
14
- assertType<SelectSchema<Entry>>({
15
- type: 'select',
16
- options: [
17
- { label: 'Draft', value: 'draft' },
18
- { label: 'Published', value: 'published' }
19
- ]
20
- })
21
- })
22
-
23
- it('accepts options with data callback', () => {
24
- assertType<SelectSchema<Entry>>({
25
- type: 'select',
26
- options: {
27
- data({ item }) {
28
- expectTypeOf(item).not.toBeAny()
29
- expectTypeOf(item).toMatchTypeOf<Entry>()
30
- return [
31
- { label: 'Option A', value: 1 },
32
- { label: 'Option B', value: 2 }
33
- ]
34
- },
35
- label: 'label',
36
- value: 'value'
37
- }
38
- })
39
- })
40
-
41
- it('accepts relate and relateBy', () => {
42
- assertType<SelectSchema<Entry>>({
43
- type: 'select',
44
- relate: true,
45
- relateBy: 'id'
46
- })
47
- })
48
- })
49
-
50
- describe('MultiselectSchema', () => {
51
- it('accepts multiple, searchable, taggable', () => {
52
- assertType<MultiselectSchema<Entry>>({
53
- type: 'multiselect',
54
- multiple: true,
55
- searchable: true,
56
- taggable: true,
57
- stayOpen: true
58
- })
59
- })
60
-
61
- it('accepts typed $Option with search callback', () => {
62
- type Tag = { id: number; name: string }
63
-
64
- assertType<MultiselectSchema<Entry, Tag>>({
65
- type: 'multiselect',
66
- searchable: true,
67
- search({ item, query }) {
68
- expectTypeOf(item).not.toBeAny()
69
- expectTypeOf(item).toMatchTypeOf<Entry>()
70
- expectTypeOf(query).not.toBeAny()
71
- expectTypeOf(query).toBeString()
72
- return [] as Tag[]
73
- },
74
- options: {
75
- data: [] as Tag[],
76
- label: 'name',
77
- value: 'id'
78
- }
79
- })
80
- })
81
-
82
- it('accepts label/value as accessor callbacks with typed option', () => {
83
- type Tag = { id: number; name: string }
84
-
85
- assertType<MultiselectSchema<Entry, Tag>>({
86
- type: 'multiselect',
87
- options: {
88
- data: [] as Tag[],
89
- label({ option }) {
90
- expectTypeOf(option).not.toBeAny()
91
- return option.name
92
- },
93
- value({ option }) {
94
- expectTypeOf(option).not.toBeAny()
95
- return option.id
96
- }
97
- }
98
- })
99
- })
100
-
101
- it('rejects invalid type', () => {
102
- assertType<MultiselectSchema<Entry>>({
103
- // @ts-expect-error 'radio' is not assignable to 'multiselect'
104
- type: 'radio'
105
- })
106
- })
107
- })
@@ -1,81 +0,0 @@
1
- import { expectTypeOf, assertType, describe, it } from 'vitest'
2
- import type { Component, Components, Form } from '../index.d.ts'
3
- import type { Entry, Parent, ParentWithMarkers } from './fixtures.ts'
4
-
5
- describe('Components', () => {
6
- it('accepts data-only components', () => {
7
- assertType<Components<Parent>>({
8
- title: { type: 'text' }
9
- })
10
- })
11
-
12
- it('infers array element type for list components', () => {
13
- assertType<Components<Parent>>({
14
- entries: {
15
- type: 'list',
16
- form: {
17
- type: 'form',
18
- components: {
19
- title: { type: 'text' }
20
- }
21
- } satisfies Form<Entry>
22
- }
23
- })
24
- })
25
-
26
- it('accepts UI-only keys for non-data components', () => {
27
- assertType<Components<ParentWithMarkers>>({
28
- viewButton: {
29
- type: 'button',
30
- text: 'View',
31
- events: {
32
- click({ item }) {
33
- expectTypeOf(item).not.toBeAny()
34
- expectTypeOf(item).toHaveProperty('title')
35
- expectTypeOf(item).toHaveProperty('entries')
36
- expectTypeOf(item).not.toHaveProperty('viewButton')
37
- expectTypeOf(item).not.toHaveProperty('spacer')
38
- }
39
- }
40
- }
41
- })
42
- })
43
-
44
- it('provides typed item in callbacks for data keys', () => {
45
- assertType<Components<Parent>>({
46
- title: {
47
- type: 'text',
48
- format({ item }) {
49
- expectTypeOf(item).not.toBeAny()
50
- expectTypeOf(item.title).toBeString()
51
- expectTypeOf(item.entries).toEqualTypeOf<Entry[]>()
52
- }
53
- }
54
- })
55
- })
56
- })
57
-
58
- describe('Components negative tests', () => {
59
- it('rejects unknown keys', () => {
60
- assertType<Components<Parent>>({
61
- title: { type: 'text' },
62
- // @ts-expect-error 'nonExistent' is not a key of Parent
63
- nonExistent: { type: 'text' }
64
- })
65
- })
66
- })
67
-
68
- describe('Components<any> compatibility', () => {
69
- it('Components<Specific> is assignable to Components<any>', () => {
70
- const specific: Components<Parent> = {
71
- title: { type: 'text' }
72
- }
73
- assertType<Components<any>>(specific)
74
- })
75
-
76
- it('Component<Specific> is assignable to Component<any>', () => {
77
- const specific: Component<Parent> = { type: 'text' }
78
- assertType<Component<any>>(specific)
79
- assertType<Component>(specific)
80
- })
81
- })
@@ -1,31 +0,0 @@
1
- import { expectTypeOf, assertType, describe, it } from 'vitest'
2
- import type { DitoContext, OrItemAccessor } from '../index.d.ts'
3
- import type { Entry, ParentWithMarkers } from './fixtures.ts'
4
-
5
- describe('DitoContext', () => {
6
- it('item strips never keys', () => {
7
- type Ctx = DitoContext<ParentWithMarkers>
8
- expectTypeOf<Ctx['item']>().not.toBeAny()
9
- expectTypeOf<Ctx['item']>().toHaveProperty('id')
10
- expectTypeOf<Ctx['item']>().toHaveProperty('title')
11
- expectTypeOf<Ctx['item']>().toHaveProperty('entries')
12
- expectTypeOf<Ctx['item']>().not.toHaveProperty('viewButton')
13
- expectTypeOf<Ctx['item']>().not.toHaveProperty('spacer')
14
- })
15
-
16
- it('item preserves data keys', () => {
17
- type Ctx = DitoContext<Entry>
18
- expectTypeOf<Ctx['item']>().not.toBeAny()
19
- expectTypeOf<Ctx['item']>().toEqualTypeOf<{
20
- id: number
21
- title: string
22
- }>()
23
- })
24
-
25
- it('OrItemAccessor accepts value or callback', () => {
26
- assertType<OrItemAccessor<Entry, {}, string>>('hello')
27
- assertType<OrItemAccessor<Entry, {}, string>>(
28
- (ctx: DitoContext<Entry>) => ctx.item.title
29
- )
30
- })
31
- })
@@ -1,24 +0,0 @@
1
- export type Entry = {
2
- id: number
3
- title: string
4
- }
5
-
6
- export type Parent = {
7
- id: number
8
- title: string
9
- entries: Entry[]
10
- }
11
-
12
- export type ParentWithMarkers = Parent & {
13
- viewButton: never
14
- spacer: never
15
- }
16
-
17
- export type Address = {
18
- street: string
19
- city: string
20
- }
21
-
22
- export type ParentWithAddress = Parent & {
23
- address: Address
24
- }
@@ -1,109 +0,0 @@
1
- import { expectTypeOf, assertType, describe, it } from 'vitest'
2
- import type { Form, ResolvableForm } from '../index.d.ts'
3
- import type { Entry, Parent } from './fixtures.ts'
4
-
5
- describe('Form assignability', () => {
6
- it('Form<Specific> is assignable to Form<any>', () => {
7
- const specific: Form<Entry> = {
8
- type: 'form',
9
- components: {
10
- title: { type: 'text' }
11
- }
12
- }
13
- assertType<Form<any>>(specific)
14
- })
15
-
16
- it('Form<Specific> is assignable to ResolvableForm', () => {
17
- const specific: Form<Entry> = {
18
- type: 'form',
19
- components: {
20
- title: { type: 'text' }
21
- }
22
- }
23
- assertType<ResolvableForm>(specific)
24
- })
25
-
26
- it('Record<string, Form<$Item>> is assignable to ResolvableForm<$Item>', () => {
27
- const module: Record<string, Form<Entry>> = {
28
- entryForm: { type: 'form', components: {} }
29
- }
30
- assertType<ResolvableForm<Entry>>(module)
31
- })
32
-
33
- it('Record<string, Form<WrongType>> is not assignable to ResolvableForm<$Item>', () => {
34
- const module: Record<string, Form<Parent>> = {
35
- parentForm: { type: 'form', components: {} }
36
- }
37
- // @ts-expect-error Form<Parent> should not be assignable to ResolvableForm<Entry>
38
- assertType<ResolvableForm<Entry>>(module)
39
- })
40
-
41
- it('() => import() pattern is assignable to ResolvableForm', () => {
42
- const importForm = () =>
43
- Promise.resolve({
44
- default: { type: 'form', components: {} } as Form<Entry>
45
- })
46
- assertType<ResolvableForm<Entry>>(importForm)
47
- })
48
-
49
- it('accepts event callbacks with typed item', () => {
50
- assertType<Form<Entry>>({
51
- type: 'form',
52
- events: {
53
- create({ item }) {
54
- expectTypeOf(item).not.toBeAny()
55
- expectTypeOf(item.title).toBeString()
56
- },
57
- submit({ item }) {
58
- expectTypeOf(item).not.toBeAny()
59
- expectTypeOf(item.id).toBeNumber()
60
- },
61
- error({ item, error }) {
62
- expectTypeOf(item).not.toBeAny()
63
- expectTypeOf(item.title).toBeString()
64
- expectTypeOf(error).not.toBeAny()
65
- expectTypeOf(error).toEqualTypeOf<Error>()
66
- }
67
- }
68
- })
69
- })
70
-
71
- it('accepts onCreate/onSubmit/onError top-level callbacks', () => {
72
- assertType<Form<Entry>>({
73
- type: 'form',
74
- onCreate({ item }) {
75
- expectTypeOf(item).not.toBeAny()
76
- expectTypeOf(item.title).toBeString()
77
- },
78
- onSubmit({ item }) {
79
- expectTypeOf(item).not.toBeAny()
80
- expectTypeOf(item.id).toBeNumber()
81
- },
82
- onError({ item, error }) {
83
- expectTypeOf(item).not.toBeAny()
84
- expectTypeOf(error).not.toBeAny()
85
- expectTypeOf(error).toEqualTypeOf<Error>()
86
- }
87
- })
88
- })
89
-
90
- it('typed Form variable used as form prop in list component', () => {
91
- const childForm: Form<Entry> = {
92
- type: 'form',
93
- components: {
94
- title: { type: 'text' }
95
- }
96
- }
97
-
98
- const parentForm: Form<Parent> = {
99
- type: 'form',
100
- components: {
101
- title: { type: 'text' },
102
- entries: {
103
- type: 'list',
104
- form: childForm
105
- }
106
- }
107
- }
108
- })
109
- })
@@ -1,20 +0,0 @@
1
- import { expectTypeOf, describe, it } from 'vitest'
2
- import type { DitoFormInstance, DitoContext } from '../index.d.ts'
3
- import type { Entry } from './fixtures.ts'
4
-
5
- describe('DitoComponentInstanceBase and DitoFormInstance', () => {
6
- it('DitoFormInstance extends base with item and form properties', () => {
7
- type Instance = DitoFormInstance<Entry>
8
- expectTypeOf<Instance['item']>().not.toBeAny()
9
- expectTypeOf<Instance['item']['title']>().toBeString()
10
- expectTypeOf<Instance['isCreating']>().toBeBoolean()
11
- expectTypeOf<Instance['submit']>().returns.toEqualTypeOf<Promise<boolean>>()
12
- })
13
-
14
- it('context is typed with item type', () => {
15
- type Instance = DitoFormInstance<Entry>
16
- expectTypeOf<Instance['context']>().not.toBeAny()
17
- expectTypeOf<Instance['context']>()
18
- .toEqualTypeOf<DitoContext<Entry>>()
19
- })
20
- })