@ditojs/admin 3.1.1 → 3.1.3
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.
- package/dist/dito-admin.es.js +1583 -1557
- package/dist/dito-admin.umd.js +5 -5
- package/package.json +5 -5
- package/src/components/DitoCreateButton.vue +5 -2
- package/src/mixins/DitoMixin.js +11 -6
- package/src/mixins/OptionsMixin.js +0 -5
- package/src/mixins/SourceMixin.js +17 -3
- package/src/mixins/TypeMixin.js +5 -0
- package/src/types/DitoTypeMarkup.vue +12 -3
- package/src/utils/FormModel.js +5 -1
- package/src/utils/schema/data.js +15 -1
- package/src/utils/schema/lookup.js +52 -15
- package/types/index.d.ts +13 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/admin",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
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",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"not ie_mob > 0"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@ditojs/ui": "^3.1.
|
|
45
|
-
"@ditojs/utils": "^3.1.
|
|
44
|
+
"@ditojs/ui": "^3.1.3",
|
|
45
|
+
"@ditojs/utils": "^3.1.3"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"vue-upload-component": "^3.2.2"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@ditojs/build": "^3.1.
|
|
90
|
+
"@ditojs/build": "^3.1.3",
|
|
91
91
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
92
92
|
"@tiptap/core": "^3.31.4",
|
|
93
93
|
"@tiptap/extension-blockquote": "^3.31.4",
|
|
@@ -131,5 +131,5 @@
|
|
|
131
131
|
"vue-router": "^5.3.1",
|
|
132
132
|
"vue-upload-component": "^3.2.2"
|
|
133
133
|
},
|
|
134
|
-
"gitHead": "
|
|
134
|
+
"gitHead": "3dba854ad248a56da1614122b960ad8a7eda70dd"
|
|
135
135
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
v-if="creatableForm"
|
|
5
5
|
:type="isInlined ? 'button' : 'submit'"
|
|
6
6
|
:disabled="disabled"
|
|
7
|
-
v-bind="getButtonAttributes(verb, formLabel)"
|
|
7
|
+
v-bind="getButtonAttributes(verb, formLabel, text)"
|
|
8
8
|
@click="createItem(creatableForm)"
|
|
9
9
|
) {{ text }}
|
|
10
10
|
template(
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
button.dito-button(
|
|
14
14
|
type="button"
|
|
15
15
|
:disabled="disabled"
|
|
16
|
-
v-bind
|
|
16
|
+
v-bind=`{
|
|
17
|
+
...pulldownTriggerAttributes,
|
|
18
|
+
...getButtonAttributes(verb, null, text)
|
|
19
|
+
}`
|
|
17
20
|
@mousedown.stop="onPulldownMouseDown()"
|
|
18
21
|
) {{ text }}
|
|
19
22
|
ul.dito-pulldown(
|
package/src/mixins/DitoMixin.js
CHANGED
|
@@ -295,13 +295,18 @@ export default {
|
|
|
295
295
|
: labelize(name) || ''
|
|
296
296
|
},
|
|
297
297
|
|
|
298
|
-
getButtonAttributes(verb, subject = null) {
|
|
299
|
-
//
|
|
300
|
-
|
|
298
|
+
getButtonAttributes(verb, subject = null, text = null) {
|
|
299
|
+
// Buttons that display text are named by it, others by what they act on
|
|
300
|
+
// if known, e.g. 'Add Section'.
|
|
301
|
+
const label = text
|
|
302
|
+
? null
|
|
303
|
+
: `${labelize(verb)}${subject ? ` ${subject}` : ''}`
|
|
301
304
|
return {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
class: `dito-button--${verb}`,
|
|
306
|
+
...(label && {
|
|
307
|
+
'title': label,
|
|
308
|
+
'aria-label': label
|
|
309
|
+
})
|
|
305
310
|
}
|
|
306
311
|
},
|
|
307
312
|
|
|
@@ -82,11 +82,6 @@ export default {
|
|
|
82
82
|
return this.getOptionForValue(this.selectedValue)
|
|
83
83
|
},
|
|
84
84
|
|
|
85
|
-
// The form model of the data, owned by the dialog or route component.
|
|
86
|
-
formModel() {
|
|
87
|
-
return (this.dialogComponent ?? this.routeComponent).formModel
|
|
88
|
-
},
|
|
89
|
-
|
|
90
85
|
// The resolver of the options in the form model of the data, which loads
|
|
91
86
|
// them and shares them with the computes of the form. It is looked up with
|
|
92
87
|
// the entry of this component, in the shape of the entries of
|
|
@@ -424,7 +424,7 @@ export default {
|
|
|
424
424
|
// @override ResourceMixin.clearData()
|
|
425
425
|
clearData() {
|
|
426
426
|
this.total = 0
|
|
427
|
-
this.
|
|
427
|
+
this.setLoadedValue(null)
|
|
428
428
|
},
|
|
429
429
|
|
|
430
430
|
// @override ResourceMixin.setData()
|
|
@@ -437,7 +437,7 @@ export default {
|
|
|
437
437
|
if (this.isListSource && isArray(data)) {
|
|
438
438
|
this.setLoadedListItems(data)
|
|
439
439
|
} else if (!data || this.isObjectSource && isObject(data)) {
|
|
440
|
-
this.
|
|
440
|
+
this.setLoadedValue(data)
|
|
441
441
|
} else if (this.unwrapListData(data)) {
|
|
442
442
|
// The format didn't match, see if we received a `{ results, total }`
|
|
443
443
|
// object, in which case `this.value` was already set by
|
|
@@ -483,7 +483,21 @@ export default {
|
|
|
483
483
|
// current order. Lists in the data of forms, views and dialogs are
|
|
484
484
|
// numbered by `FormModel`, see `initializeData()`.
|
|
485
485
|
setLoadedListItems(items) {
|
|
486
|
-
this.
|
|
486
|
+
this.setLoadedValue(
|
|
487
|
+
updateOrder(this.sourceSchema, items, this.paginationRange)
|
|
488
|
+
)
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
// Writes the value loaded through the source's resource. Loading isn't an
|
|
492
|
+
// edit of the data, so the value is written as a clean change, which
|
|
493
|
+
// doesn't make the form dirty, e.g. for sources whose value is stored,
|
|
494
|
+
// like the order of their items, see `FormModel.applyCleanChanges()`.
|
|
495
|
+
setLoadedValue(value) {
|
|
496
|
+
this.formModel
|
|
497
|
+
.applyCleanChanges(() => {
|
|
498
|
+
this.value = value
|
|
499
|
+
})
|
|
500
|
+
.catch(console.error)
|
|
487
501
|
},
|
|
488
502
|
|
|
489
503
|
createItem(schema, type, index = null) {
|
package/src/mixins/TypeMixin.js
CHANGED
|
@@ -37,6 +37,11 @@ export default {
|
|
|
37
37
|
return this.schema.name
|
|
38
38
|
},
|
|
39
39
|
|
|
40
|
+
// The form model of the data, owned by the dialog or route component.
|
|
41
|
+
formModel() {
|
|
42
|
+
return (this.dialogComponent ?? this.routeComponent).formModel
|
|
43
|
+
},
|
|
44
|
+
|
|
40
45
|
type() {
|
|
41
46
|
return this.schema.type
|
|
42
47
|
},
|
|
@@ -251,9 +251,11 @@ export default DitoTypeComponent.register('markup', {
|
|
|
251
251
|
const onFocus = () => this.onFocus()
|
|
252
252
|
|
|
253
253
|
const onBlur = () => {
|
|
254
|
-
// Write the value first, so that validating on blur validates it
|
|
254
|
+
// Write the value first, so that validating on blur validates it, and
|
|
255
|
+
// emit the change once the editor isn't focused anymore.
|
|
255
256
|
updateValue()
|
|
256
257
|
this.onBlur()
|
|
258
|
+
emitChange()
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
const onUpdate = () => {
|
|
@@ -275,9 +277,16 @@ export default DitoTypeComponent.register('markup', {
|
|
|
275
277
|
ignoreWatch = true
|
|
276
278
|
this.value = value
|
|
277
279
|
}
|
|
278
|
-
if (!this.focused
|
|
279
|
-
|
|
280
|
+
if (!this.focused) {
|
|
281
|
+
emitChange()
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Emits the change of the value, once the editing is done.
|
|
286
|
+
const emitChange = () => {
|
|
287
|
+
if (changed) {
|
|
280
288
|
changed = false
|
|
289
|
+
this.onChange()
|
|
281
290
|
}
|
|
282
291
|
}
|
|
283
292
|
|
package/src/utils/FormModel.js
CHANGED
|
@@ -584,7 +584,11 @@ function takeOverChangedValues(target, before, after) {
|
|
|
584
584
|
return target
|
|
585
585
|
}
|
|
586
586
|
const isSameShape = (
|
|
587
|
-
|
|
587
|
+
(
|
|
588
|
+
isPlainObject(before) &&
|
|
589
|
+
isPlainObject(after) &&
|
|
590
|
+
isPlainObject(target)
|
|
591
|
+
) || (
|
|
588
592
|
isArray(before) &&
|
|
589
593
|
isArray(after) &&
|
|
590
594
|
isArray(target) &&
|
package/src/utils/schema/data.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
hasFormSchema,
|
|
24
24
|
hasMultipleFormSchemas,
|
|
25
25
|
getFormSchemas,
|
|
26
|
+
getItemFormSchema,
|
|
26
27
|
getItemFormSchemaFromForms
|
|
27
28
|
} from './lookup.js'
|
|
28
29
|
|
|
@@ -605,7 +606,20 @@ export function processSchemaData(schema, data, {
|
|
|
605
606
|
}
|
|
606
607
|
}
|
|
607
608
|
|
|
608
|
-
|
|
609
|
+
const components = isFunction(schema.components)
|
|
610
|
+
? // Unnested components with `components()` callbacks, e.g. sections:
|
|
611
|
+
getItemFormSchema(
|
|
612
|
+
schema,
|
|
613
|
+
data,
|
|
614
|
+
new DitoContext(options.component, {
|
|
615
|
+
schema,
|
|
616
|
+
data,
|
|
617
|
+
dataPath,
|
|
618
|
+
rootData: options.rootData
|
|
619
|
+
})
|
|
620
|
+
).components
|
|
621
|
+
: schema.components
|
|
622
|
+
processComponents(components, componentPath)
|
|
609
623
|
processTabOrPanelSchemas(schema.tabs, componentPath)
|
|
610
624
|
processTabOrPanelSchemas(schema.panels, componentPath)
|
|
611
625
|
|
|
@@ -6,26 +6,32 @@ import {
|
|
|
6
6
|
isFunction,
|
|
7
7
|
camelize
|
|
8
8
|
} from '@ditojs/utils'
|
|
9
|
-
import { reactive } from 'vue'
|
|
9
|
+
import { reactive, computed } from 'vue'
|
|
10
10
|
import { getTypeOptions } from './types.js'
|
|
11
11
|
import {
|
|
12
12
|
findNestedSchemaComponent,
|
|
13
13
|
isSchema,
|
|
14
14
|
isSingleComponentView
|
|
15
15
|
} from './structure.js'
|
|
16
|
-
// TODO:
|
|
17
|
-
//
|
|
18
|
-
// modules. Move this out of the lookup, see the form model.
|
|
16
|
+
// TODO: Setting up the forms of `components()` callbacks causes the only
|
|
17
|
+
// circular import between these modules.
|
|
19
18
|
import { setupForm } from './setup.js'
|
|
20
19
|
|
|
21
20
|
const emptySchema = {}
|
|
22
21
|
|
|
22
|
+
// The forms with the components that `components()` callbacks create, by
|
|
23
|
+
// callback and by the data that they create them for, see
|
|
24
|
+
// `getFormWithCreatedComponents()`.
|
|
25
|
+
const formsByComponentsCallback = new WeakMap()
|
|
26
|
+
|
|
23
27
|
export function hasFormSchema(schema) {
|
|
24
28
|
// Support both single form and multiple forms notation, as well as inlined
|
|
25
|
-
// components.
|
|
29
|
+
// components, also created by `components()` callbacks.
|
|
26
30
|
return (
|
|
27
|
-
isSchema(schema) &&
|
|
28
|
-
|
|
31
|
+
isSchema(schema) && (
|
|
32
|
+
isObject(schema.form || schema.forms || schema.components) ||
|
|
33
|
+
isFunction(schema.components)
|
|
34
|
+
)
|
|
29
35
|
)
|
|
30
36
|
}
|
|
31
37
|
|
|
@@ -96,20 +102,51 @@ export function getFormSchemas(schema, context, modifyForm) {
|
|
|
96
102
|
Object.entries(forms).map(([type, form]) => {
|
|
97
103
|
// Support `schema.components` callbacks to create components on the fly.
|
|
98
104
|
if (context && isFunction(form.components)) {
|
|
99
|
-
|
|
100
|
-
// the setting of defaults will be picked up by downstream code.
|
|
101
|
-
form = reactive({
|
|
102
|
-
...form,
|
|
103
|
-
components: form.components(context)
|
|
104
|
-
})
|
|
105
|
-
// Process the form again, now that we have the components.
|
|
106
|
-
setupForm(context.api, form).catch(console.error)
|
|
105
|
+
form = getFormWithCreatedComponents(form, context)
|
|
107
106
|
}
|
|
108
107
|
return [type, modifyForm?.(form) ?? form]
|
|
109
108
|
})
|
|
110
109
|
)
|
|
111
110
|
}
|
|
112
111
|
|
|
112
|
+
// Returns the form with the components that its `components()` callback
|
|
113
|
+
// creates for the data of `context`. The forms are cached per data, and the
|
|
114
|
+
// callback only creates new components when the data that it reads changes,
|
|
115
|
+
// so that rendering and the walks of the form model, see `FormModel`, share
|
|
116
|
+
// the same component schemas. `components()` callbacks therefore derive the
|
|
117
|
+
// components from the data of their context, not from its components.
|
|
118
|
+
function getFormWithCreatedComponents(form, context) {
|
|
119
|
+
const { item } = context
|
|
120
|
+
if (!isObject(item)) {
|
|
121
|
+
return createFormWithComponents(form, context)
|
|
122
|
+
}
|
|
123
|
+
let formsByItem = formsByComponentsCallback.get(form.components)
|
|
124
|
+
if (!formsByItem) {
|
|
125
|
+
formsByItem = new WeakMap()
|
|
126
|
+
formsByComponentsCallback.set(form.components, formsByItem)
|
|
127
|
+
}
|
|
128
|
+
let formWithCreatedComponents = formsByItem.get(item)
|
|
129
|
+
if (!formWithCreatedComponents) {
|
|
130
|
+
formWithCreatedComponents = computed(() =>
|
|
131
|
+
createFormWithComponents(form, context)
|
|
132
|
+
)
|
|
133
|
+
formsByItem.set(item, formWithCreatedComponents)
|
|
134
|
+
}
|
|
135
|
+
return formWithCreatedComponents.value
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function createFormWithComponents(form, context) {
|
|
139
|
+
// Make the form schema reactive since `setupForm()` is async, so that the
|
|
140
|
+
// setting of defaults will be picked up by downstream code.
|
|
141
|
+
const formWithComponents = reactive({
|
|
142
|
+
...form,
|
|
143
|
+
components: form.components(context)
|
|
144
|
+
})
|
|
145
|
+
// Process the form again, now that it has its components.
|
|
146
|
+
setupForm(context.api, formWithComponents).catch(console.error)
|
|
147
|
+
return formWithComponents
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
export function getItemFormSchemaFromForms(forms, item) {
|
|
114
151
|
return forms[item?.type] || forms.default || null
|
|
115
152
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1548,9 +1548,18 @@ export interface RadioSchema<$Item = any>
|
|
|
1548
1548
|
layout?: 'horizontal' | 'vertical'
|
|
1549
1549
|
}
|
|
1550
1550
|
|
|
1551
|
-
type SectionContent<$Data> = {
|
|
1552
|
-
/**
|
|
1553
|
-
|
|
1551
|
+
type SectionContent<$Data, $Item = $Data> = {
|
|
1552
|
+
/**
|
|
1553
|
+
* The section's field components, or a callback that
|
|
1554
|
+
* creates them from the data, e.g. fields that depend on
|
|
1555
|
+
* a selected template. The callback runs again when the
|
|
1556
|
+
* data that it reads changes, and receives the context of
|
|
1557
|
+
* the section, so that `item` is the item that contains
|
|
1558
|
+
* it, also for nested sections.
|
|
1559
|
+
*/
|
|
1560
|
+
components?:
|
|
1561
|
+
| Components<$Data>
|
|
1562
|
+
| ItemAccessor<$Item, {}, Components<$Data>>
|
|
1554
1563
|
/**
|
|
1555
1564
|
* A form schema for the section's content. Use this
|
|
1556
1565
|
* instead of `components` to get form-level options
|
|
@@ -1690,7 +1699,7 @@ export type SectionSchema<$Item = any, $Nested = $Item> = BaseSchema<$Item> &
|
|
|
1690
1699
|
* @defaultValue `false`
|
|
1691
1700
|
*/
|
|
1692
1701
|
nested: true
|
|
1693
|
-
} & SectionContent<$Nested>)
|
|
1702
|
+
} & SectionContent<$Nested, $Item>)
|
|
1694
1703
|
)
|
|
1695
1704
|
|
|
1696
1705
|
export interface CheckboxSchema<$Item = any> extends BaseSchema<$Item> {
|