@ditojs/admin 2.100.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dito-admin.css +1 -1
- package/dist/dito-admin.es.js +3421 -2662
- package/dist/dito-admin.umd.js +6 -6
- package/package.json +81 -45
- package/src/DitoComponent.js +1 -1
- package/src/DitoContext.js +51 -0
- package/src/DitoTypeComponent.js +6 -1
- package/src/components/DitoAffixes.vue +6 -3
- package/src/components/DitoContainer.vue +19 -10
- package/src/components/DitoCreateButton.vue +11 -2
- package/src/components/DitoDialog.vue +21 -2
- package/src/components/DitoForm.vue +156 -83
- package/src/components/DitoLabel.vue +3 -2
- package/src/components/DitoPane.vue +55 -11
- package/src/components/DitoPanel.vue +33 -5
- package/src/components/DitoPanels.vue +2 -1
- package/src/components/DitoRoot.vue +13 -6
- package/src/components/DitoSchema.vue +278 -145
- package/src/components/DitoSchemaInlined.vue +1 -1
- package/src/components/DitoTabs.vue +16 -18
- package/src/components/DitoTrail.vue +5 -4
- package/src/components/DitoTreeItem.vue +17 -6
- package/src/components/DitoView.vue +25 -2
- package/src/mixins/DitoMixin.js +42 -7
- package/src/mixins/ItemMixin.js +3 -6
- package/src/mixins/NumberMixin.js +47 -31
- package/src/mixins/OptionsMixin.js +122 -71
- package/src/mixins/ResourceMixin.js +5 -2
- package/src/mixins/RouteMixin.js +39 -1
- package/src/mixins/SortableMixin.js +0 -13
- package/src/mixins/SourceMixin.js +142 -95
- package/src/mixins/TextMixin.js +1 -1
- package/src/mixins/TypeMixin.js +37 -22
- package/src/mixins/ValidationMixin.js +9 -27
- package/src/mixins/ValidatorMixin.js +5 -7
- package/src/mixins/ValueMixin.js +5 -2
- package/src/types/DitoTypeButton.vue +1 -1
- package/src/types/DitoTypeCheckbox.vue +1 -1
- package/src/types/DitoTypeCheckboxes.vue +1 -1
- package/src/types/DitoTypeCode.vue +8 -1
- package/src/types/DitoTypeColor.vue +20 -34
- package/src/types/DitoTypeComponent.vue +1 -1
- package/src/types/DitoTypeComputed.vue +4 -27
- package/src/types/DitoTypeDate.vue +16 -5
- package/src/types/DitoTypeLabel.vue +1 -4
- package/src/types/DitoTypeList.vue +9 -5
- package/src/types/DitoTypeMarkup.vue +25 -13
- package/src/types/DitoTypeMultiselect.vue +26 -6
- package/src/types/DitoTypeNumber.vue +1 -1
- package/src/types/DitoTypeObject.vue +10 -7
- package/src/types/DitoTypePanel.vue +19 -6
- package/src/types/DitoTypeProgress.vue +1 -1
- package/src/types/DitoTypeRadio.vue +1 -1
- package/src/types/DitoTypeSection.vue +32 -4
- package/src/types/DitoTypeSelect.vue +1 -1
- package/src/types/DitoTypeSlider.vue +1 -1
- package/src/types/DitoTypeSwitch.vue +1 -1
- package/src/types/DitoTypeText.vue +13 -15
- package/src/types/DitoTypeTextarea.vue +1 -1
- package/src/types/DitoTypeTreeList.vue +12 -7
- package/src/types/DitoTypeUpload.vue +1 -2
- package/src/utils/DataSchemaResolver.js +141 -0
- package/src/utils/DataSchemaResolver.test.js +132 -0
- package/src/utils/FormModel.js +556 -0
- package/src/utils/FormModel.test.js +808 -0
- package/src/utils/data.js +20 -0
- package/src/utils/data.test.js +28 -0
- package/src/utils/filter.js +3 -2
- package/src/utils/options.js +3 -0
- package/src/utils/schema/data.js +617 -0
- package/src/utils/schema/data.test.js +471 -0
- package/src/utils/schema/lookup.js +266 -0
- package/src/utils/schema/lookup.test.js +61 -0
- package/src/utils/schema/setup.js +354 -0
- package/src/utils/schema/structure.js +172 -0
- package/src/utils/schema/structure.test.js +57 -0
- package/src/utils/schema/types.js +34 -0
- package/src/utils/schema/validation.js +142 -0
- package/src/utils/schema/validation.test.js +395 -0
- package/src/utils/units.js +70 -3
- package/src/utils/units.test.js +71 -0
- package/types/index.d.ts +149 -59
- package/src/mixins/DataMixin.js +0 -131
- package/src/utils/schema.js +0 -1121
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-code(
|
|
3
|
-
:id="
|
|
3
|
+
:id="componentPath"
|
|
4
4
|
:style="style"
|
|
5
5
|
)
|
|
6
6
|
.dito-code__editor(ref="editor")
|
|
@@ -120,6 +120,13 @@ export default DitoTypeComponent.register('code', {
|
|
|
120
120
|
flask.updateLanguage(language)
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
+
this.$watch(
|
|
124
|
+
() => this.readonly || this.disabled,
|
|
125
|
+
readonly =>
|
|
126
|
+
readonly ? flask.enableReadonlyMode() : flask.disableReadonlyMode(),
|
|
127
|
+
{ immediate: true }
|
|
128
|
+
)
|
|
129
|
+
|
|
123
130
|
setCode(this.value || '')
|
|
124
131
|
},
|
|
125
132
|
|
|
@@ -5,7 +5,7 @@ DitoTrigger.dito-color(
|
|
|
5
5
|
)
|
|
6
6
|
template(#trigger)
|
|
7
7
|
DitoInput(
|
|
8
|
-
:id="
|
|
8
|
+
:id="componentPath"
|
|
9
9
|
ref="element"
|
|
10
10
|
v-model="hexValue"
|
|
11
11
|
type="input"
|
|
@@ -39,7 +39,7 @@ DitoTrigger.dito-color(
|
|
|
39
39
|
div(:style="{ background: `#${hexValue || '00000000'}` }")
|
|
40
40
|
template(#popup)
|
|
41
41
|
SketchPicker.dito-color__picker(
|
|
42
|
-
v-model="colorValue"
|
|
42
|
+
v-model:tinyColor="colorValue"
|
|
43
43
|
:disableAlpha="!alpha"
|
|
44
44
|
:disableFields="!inputs"
|
|
45
45
|
:presetColors="presets"
|
|
@@ -47,21 +47,14 @@ DitoTrigger.dito-color(
|
|
|
47
47
|
</template>
|
|
48
48
|
|
|
49
49
|
<script>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
import {
|
|
50
|
+
// Use the `tinycolor` exported by `vue-color`, as required for its
|
|
51
|
+
// `v-model:tinyColor` binding.
|
|
52
|
+
import { SketchPicker, tinycolor } from 'vue-color'
|
|
53
53
|
import { DitoTrigger, DitoInput } from '@ditojs/ui/src'
|
|
54
54
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
55
55
|
import DitoAffixes from '../components/DitoAffixes.vue'
|
|
56
56
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
57
57
|
|
|
58
|
-
// Monkey-patch the `SketchPicker's` `hex` computed property to return lowercase
|
|
59
|
-
// hex values instead of uppercase ones.
|
|
60
|
-
const { hex } = SketchPicker.computed
|
|
61
|
-
SketchPicker.computed.hex = function () {
|
|
62
|
-
return hex.call(this).toLowerCase()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
58
|
// @vue/component
|
|
66
59
|
export default DitoTypeComponent.register('color', {
|
|
67
60
|
components: { DitoTrigger, DitoInput, DitoAffixes, SketchPicker },
|
|
@@ -80,15 +73,21 @@ export default DitoTypeComponent.register('color', {
|
|
|
80
73
|
|
|
81
74
|
colorValue: {
|
|
82
75
|
get() {
|
|
83
|
-
return (
|
|
84
|
-
this.convertedValue ||
|
|
85
|
-
this.value ||
|
|
86
|
-
(this.colorFormat === 'hex' ? '' : {})
|
|
87
|
-
)
|
|
76
|
+
return tinycolor(this.convertedValue || this.value || '#000000')
|
|
88
77
|
},
|
|
89
78
|
|
|
90
|
-
set(
|
|
91
|
-
|
|
79
|
+
set(color) {
|
|
80
|
+
// Picker changes take precedence over values typed into the input.
|
|
81
|
+
this.convertedValue = null
|
|
82
|
+
// Skip unchanged colors, as converting formats like `hsl` back and
|
|
83
|
+
// forth isn't lossless and would cause endless updates.
|
|
84
|
+
const { value } = this
|
|
85
|
+
if (
|
|
86
|
+
!value ||
|
|
87
|
+
color.toHex8String() !== tinycolor(value).toHex8String()
|
|
88
|
+
) {
|
|
89
|
+
this.value = toTinyColorFormat(color, this.colorFormat)
|
|
90
|
+
}
|
|
92
91
|
}
|
|
93
92
|
},
|
|
94
93
|
|
|
@@ -174,21 +173,7 @@ export default DitoTypeComponent.register('color', {
|
|
|
174
173
|
})
|
|
175
174
|
|
|
176
175
|
function convertColor(color, format) {
|
|
177
|
-
return
|
|
178
|
-
? toVue3ColorFormat(color, format)
|
|
179
|
-
: toTinyColorFormat(tinycolor(color), format)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function toVue3ColorFormat(color, format) {
|
|
183
|
-
const value =
|
|
184
|
-
color[
|
|
185
|
-
{
|
|
186
|
-
hex: color?.a < 1 ? 'hex8' : 'hex',
|
|
187
|
-
rgb: 'rgba'
|
|
188
|
-
}[format] ||
|
|
189
|
-
format
|
|
190
|
-
]
|
|
191
|
-
return isString(value) && value[0] === '#' ? value.toLowerCase() : value
|
|
176
|
+
return toTinyColorFormat(tinycolor(color), format)
|
|
192
177
|
}
|
|
193
178
|
|
|
194
179
|
// This should really be in tinycolor, but it only has the string equivalent
|
|
@@ -224,6 +209,7 @@ function toTinyColorFormat(color, format) {
|
|
|
224
209
|
|
|
225
210
|
<style lang="scss">
|
|
226
211
|
@import '../styles/_imports';
|
|
212
|
+
@import 'vue-color/style.css';
|
|
227
213
|
|
|
228
214
|
$color-swatch-width: $pattern-transparency-size;
|
|
229
215
|
$color-swatch-radius: $border-radius - $border-width;
|
|
@@ -15,7 +15,7 @@ component(
|
|
|
15
15
|
// }
|
|
16
16
|
|
|
17
17
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
18
|
-
import { resolveSchemaComponent } from '../utils/schema.js'
|
|
18
|
+
import { resolveSchemaComponent } from '../utils/schema/setup.js'
|
|
19
19
|
|
|
20
20
|
// @vue/component
|
|
21
21
|
export default DitoTypeComponent.register('component', {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
//- TODO: Find a better way to trigger evaluation of `value` that dose not
|
|
3
|
-
//- involve actually rendering it when the component is not visible.
|
|
4
2
|
input.dito-text.dito-input(
|
|
5
|
-
:id="
|
|
3
|
+
:id="componentPath"
|
|
6
4
|
ref="element"
|
|
7
5
|
:name="name"
|
|
8
6
|
type="text"
|
|
@@ -14,37 +12,16 @@ input.dito-text.dito-input(
|
|
|
14
12
|
|
|
15
13
|
<script>
|
|
16
14
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
17
|
-
import ValueMixin from '../mixins/ValueMixin.js'
|
|
18
|
-
import DataMixin from '../mixins/DataMixin.js'
|
|
19
15
|
|
|
20
16
|
export default DitoTypeComponent.register(
|
|
21
17
|
['computed', 'data', 'hidden'],
|
|
22
18
|
// @vue/component
|
|
23
19
|
{
|
|
24
|
-
mixins: [DataMixin],
|
|
25
|
-
|
|
26
20
|
defaultValue: () => undefined, // Callback to override `defaultValue: null`
|
|
27
21
|
defaultVisible: false,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
get() {
|
|
32
|
-
const { schema } = this
|
|
33
|
-
if (schema.data || schema.dataPath) {
|
|
34
|
-
const value = this.handleDataSchema(schema, 'value')
|
|
35
|
-
// TODO: Fix side-effects
|
|
36
|
-
// eslint-disable-next-line max-len
|
|
37
|
-
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
|
38
|
-
this.data[this.name] = value
|
|
39
|
-
}
|
|
40
|
-
return ValueMixin.computed.value.get.call(this)
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
set(value) {
|
|
44
|
-
ValueMixin.computed.value.set.call(this, value)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
22
|
+
// The value is resolved from `schema.data` or `schema.dataPath` by
|
|
23
|
+
// `FormModel`, see `hasValueFromDataSchema()`:
|
|
24
|
+
valueFromDataSchema: true
|
|
48
25
|
}
|
|
49
26
|
)
|
|
50
27
|
</script>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
.dito-date
|
|
3
3
|
component(
|
|
4
4
|
:is="getComponent(type)"
|
|
5
|
-
:id="
|
|
5
|
+
:id="componentPath"
|
|
6
6
|
ref="element"
|
|
7
7
|
v-model="dateValue"
|
|
8
8
|
:locale="locale"
|
|
@@ -41,7 +41,12 @@ import {
|
|
|
41
41
|
DitoTimePicker,
|
|
42
42
|
DitoDateTimePicker
|
|
43
43
|
} from '@ditojs/ui/src'
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
isDate,
|
|
46
|
+
assignDeeply,
|
|
47
|
+
formatPlainDate,
|
|
48
|
+
parsePlainDate
|
|
49
|
+
} from '@ditojs/utils'
|
|
45
50
|
|
|
46
51
|
export default DitoTypeComponent.register(
|
|
47
52
|
['date', 'datetime', 'time'],
|
|
@@ -58,7 +63,7 @@ export default DitoTypeComponent.register(
|
|
|
58
63
|
dateValue: {
|
|
59
64
|
get() {
|
|
60
65
|
const { value } = this
|
|
61
|
-
return value ? new Date(value) : value
|
|
66
|
+
return value ? parsePlainDate(value) ?? new Date(value) : value
|
|
62
67
|
},
|
|
63
68
|
|
|
64
69
|
set(value) {
|
|
@@ -91,8 +96,14 @@ export default DitoTypeComponent.register(
|
|
|
91
96
|
}
|
|
92
97
|
},
|
|
93
98
|
|
|
94
|
-
processValue({ value }) {
|
|
95
|
-
|
|
99
|
+
processValue({ schema, value }) {
|
|
100
|
+
// Send dates as their plain date, so they can't shift to another day
|
|
101
|
+
// through time zones, and all other date types as ISO strings.
|
|
102
|
+
return isDate(value)
|
|
103
|
+
? schema.type === 'date'
|
|
104
|
+
? formatPlainDate(value)
|
|
105
|
+
: value.toISOString()
|
|
106
|
+
: value
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-list(
|
|
3
3
|
v-if="isReady"
|
|
4
|
-
:id="
|
|
4
|
+
:id="componentPath"
|
|
5
5
|
role="region"
|
|
6
6
|
:aria-label="label || labelize(schema.name)"
|
|
7
7
|
)
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
as="tbody"
|
|
43
43
|
:options="getDraggableOptions()"
|
|
44
44
|
:draggable="draggable"
|
|
45
|
-
:modelValue="
|
|
45
|
+
:modelValue="listData"
|
|
46
46
|
@update:modelValue="value => (listData = value)"
|
|
47
47
|
)
|
|
48
48
|
tr(
|
|
49
49
|
v-for="(item, index) in listData"
|
|
50
|
-
:id="
|
|
50
|
+
:id="getComponentPath(index)"
|
|
51
51
|
:key="getItemUid(schema, item)"
|
|
52
52
|
)
|
|
53
53
|
template(
|
|
@@ -163,10 +163,10 @@ import DitoContext from '../DitoContext.js'
|
|
|
163
163
|
import SourceMixin from '../mixins/SourceMixin.js'
|
|
164
164
|
import SortableMixin from '../mixins/SortableMixin.js'
|
|
165
165
|
import {
|
|
166
|
-
getViewEditPath,
|
|
167
166
|
resolveSchemaComponent,
|
|
168
167
|
resolveSchemaComponents
|
|
169
|
-
} from '../utils/schema.js'
|
|
168
|
+
} from '../utils/schema/setup.js'
|
|
169
|
+
import { getViewEditPath } from '../utils/schema/lookup.js'
|
|
170
170
|
import { createFiltersPanel } from '../utils/filter.js'
|
|
171
171
|
import { appendDataPath } from '../utils/data.js'
|
|
172
172
|
import { pickBy, equals, hyphenate } from '@ditojs/utils'
|
|
@@ -250,6 +250,10 @@ export default DitoTypeComponent.register('list', {
|
|
|
250
250
|
return appendDataPath(this.dataPath, index)
|
|
251
251
|
},
|
|
252
252
|
|
|
253
|
+
getComponentPath(index) {
|
|
254
|
+
return appendDataPath(this.componentPath, index)
|
|
255
|
+
},
|
|
256
|
+
|
|
253
257
|
getEditPath(item, index) {
|
|
254
258
|
if (this.editable) {
|
|
255
259
|
const id = this.getItemId(this.schema, item, index)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
.dito-markup(:id="
|
|
2
|
+
.dito-markup(:id="componentPath")
|
|
3
3
|
.dito-buttons.dito-buttons--toolbar(
|
|
4
4
|
v-if="groupedButtons.length > 0"
|
|
5
5
|
)
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
v-for="buttons in groupedButtons"
|
|
8
8
|
)
|
|
9
9
|
button.dito-button(
|
|
10
|
-
v-for="{
|
|
11
|
-
:key="
|
|
10
|
+
v-for="{ id, label, icon, isActive, onClick } in buttons"
|
|
11
|
+
:key="id"
|
|
12
12
|
:class="{ 'dito-button--active': isActive }"
|
|
13
|
+
:aria-label="label"
|
|
14
|
+
:aria-pressed="isActive"
|
|
13
15
|
@click="onClick"
|
|
14
16
|
)
|
|
15
17
|
DitoIcon(:name="icon")
|
|
@@ -169,7 +171,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
169
171
|
|
|
170
172
|
editorOptions() {
|
|
171
173
|
return {
|
|
172
|
-
editable: !this.
|
|
174
|
+
editable: !this.readonly,
|
|
173
175
|
autoFocus: this.autofocus,
|
|
174
176
|
disableInputRules: !this.enableRules.input,
|
|
175
177
|
disablePasteRules: !this.enableRules.paste,
|
|
@@ -237,7 +239,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
237
239
|
},
|
|
238
240
|
|
|
239
241
|
watch: {
|
|
240
|
-
|
|
242
|
+
readonly: 'updateEditorOptions',
|
|
241
243
|
autofocus: 'updateEditorOptions',
|
|
242
244
|
enableRules: 'updateEditorOptions'
|
|
243
245
|
},
|
|
@@ -249,8 +251,9 @@ export default DitoTypeComponent.register('markup', {
|
|
|
249
251
|
const onFocus = () => this.onFocus()
|
|
250
252
|
|
|
251
253
|
const onBlur = () => {
|
|
252
|
-
|
|
254
|
+
// Write the value first, so that validating on blur validates it.
|
|
253
255
|
updateValue()
|
|
256
|
+
this.onBlur()
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
const onUpdate = () => {
|
|
@@ -258,10 +261,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
258
261
|
this.onInput()
|
|
259
262
|
}
|
|
260
263
|
|
|
261
|
-
const setValueDebounced = debounce(() =>
|
|
262
|
-
ignoreWatch = true
|
|
263
|
-
updateValue()
|
|
264
|
-
}, 100)
|
|
264
|
+
const setValueDebounced = debounce(() => updateValue(), 100)
|
|
265
265
|
|
|
266
266
|
const updateValue = () => {
|
|
267
267
|
const content = this.editor.getHTML()
|
|
@@ -270,6 +270,9 @@ export default DitoTypeComponent.register('markup', {
|
|
|
270
270
|
: content
|
|
271
271
|
if (value !== this.value) {
|
|
272
272
|
changed = true
|
|
273
|
+
// The value comes from the editor, so don't set it back as content,
|
|
274
|
+
// which would re-parse it and e.g. collapse trailing whitespace.
|
|
275
|
+
ignoreWatch = true
|
|
273
276
|
this.value = value
|
|
274
277
|
}
|
|
275
278
|
if (!this.focused && changed) {
|
|
@@ -337,7 +340,6 @@ export default DitoTypeComponent.register('markup', {
|
|
|
337
340
|
async onClickLink(editor) {
|
|
338
341
|
const attributes = await this.rootComponent.showDialog({
|
|
339
342
|
components: {
|
|
340
|
-
DitoIcon,
|
|
341
343
|
href: {
|
|
342
344
|
type: 'url',
|
|
343
345
|
label: 'Link',
|
|
@@ -440,9 +442,18 @@ export default DitoTypeComponent.register('markup', {
|
|
|
440
442
|
const list = []
|
|
441
443
|
const { commands } = this.editor
|
|
442
444
|
|
|
443
|
-
const addButton = ({
|
|
445
|
+
const addButton = ({
|
|
446
|
+
name,
|
|
447
|
+
id = name,
|
|
448
|
+
label = this.labelize(id),
|
|
449
|
+
icon,
|
|
450
|
+
command,
|
|
451
|
+
attributes,
|
|
452
|
+
onClick
|
|
453
|
+
}) => {
|
|
444
454
|
list.push({
|
|
445
|
-
|
|
455
|
+
id,
|
|
456
|
+
label,
|
|
446
457
|
icon,
|
|
447
458
|
isActive: this.editor.isActive(name, attributes),
|
|
448
459
|
onClick: () => {
|
|
@@ -479,6 +490,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
479
490
|
if (setting.includes(value)) {
|
|
480
491
|
addButton({
|
|
481
492
|
name,
|
|
493
|
+
id: `${name}-${value}`,
|
|
482
494
|
icon: `${icon}-${value}`,
|
|
483
495
|
command,
|
|
484
496
|
attributes: { [attribute]: value },
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
:parentContext="context"
|
|
11
11
|
)
|
|
12
12
|
VueMultiselect(
|
|
13
|
-
:id="
|
|
13
|
+
:id="componentPath"
|
|
14
14
|
ref="element"
|
|
15
15
|
v-model="selectedOptions"
|
|
16
16
|
:aria-label="label"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
:preserveSearch="!!searchFilter"
|
|
31
31
|
:clearOnSelect="!searchFilter"
|
|
32
32
|
:closeOnSelect="!stayOpen"
|
|
33
|
-
:loading="
|
|
33
|
+
:loading="isLoadingOptions || isLoadingSearchedOptions"
|
|
34
34
|
v-bind="attributes"
|
|
35
35
|
@open="onOpen"
|
|
36
36
|
@close="onClose"
|
|
@@ -80,6 +80,7 @@ export default DitoTypeComponent.register('multiselect', {
|
|
|
80
80
|
data() {
|
|
81
81
|
return {
|
|
82
82
|
searchedOptions: null,
|
|
83
|
+
isLoadingSearchedOptions: false,
|
|
83
84
|
populate: false
|
|
84
85
|
}
|
|
85
86
|
},
|
|
@@ -141,7 +142,9 @@ export default DitoTypeComponent.register('multiselect', {
|
|
|
141
142
|
const prefix = 'multiselect'
|
|
142
143
|
return {
|
|
143
144
|
[`${prefix}--multiple`]: this.multiple,
|
|
144
|
-
[`${prefix}--loading`]:
|
|
145
|
+
[`${prefix}--loading`]: (
|
|
146
|
+
this.isLoadingOptions || this.isLoadingSearchedOptions
|
|
147
|
+
),
|
|
145
148
|
[`${prefix}--highlight`]: this.showHighlight
|
|
146
149
|
}
|
|
147
150
|
},
|
|
@@ -225,15 +228,32 @@ export default DitoTypeComponent.register('multiselect', {
|
|
|
225
228
|
}
|
|
226
229
|
},
|
|
227
230
|
|
|
231
|
+
// Returns the options that `searchFilter()` returns for the search term,
|
|
232
|
+
// showing the loading state while it's pending, and `null` if it fails.
|
|
233
|
+
async loadSearchedOptions(searchTerm) {
|
|
234
|
+
// Use a timeout to allow already resolved promises to return options
|
|
235
|
+
// without showing a loading indicator.
|
|
236
|
+
const timer = setTimeout(() => {
|
|
237
|
+
this.isLoadingSearchedOptions = true
|
|
238
|
+
}, 0)
|
|
239
|
+
let options = null
|
|
240
|
+
try {
|
|
241
|
+
options = await this.searchFilter(new DitoContext(this, { searchTerm }))
|
|
242
|
+
} catch (error) {
|
|
243
|
+
this.addError(error.message || error)
|
|
244
|
+
}
|
|
245
|
+
clearTimeout(timer)
|
|
246
|
+
this.isLoadingSearchedOptions = false
|
|
247
|
+
return options
|
|
248
|
+
},
|
|
249
|
+
|
|
228
250
|
async onSearchChange(searchTerm) {
|
|
229
251
|
if (this.searchFilter) {
|
|
230
252
|
if (searchTerm) {
|
|
231
253
|
// Set `searchedOptions` to an empty array, before it will be
|
|
232
254
|
// populated asynchronously with the actual results.
|
|
233
255
|
this.searchedOptions = []
|
|
234
|
-
this.searchedOptions = await this.
|
|
235
|
-
() => this.searchFilter(new DitoContext(this, { searchTerm }))
|
|
236
|
-
)
|
|
256
|
+
this.searchedOptions = await this.loadSearchedOptions(searchTerm)
|
|
237
257
|
} else {
|
|
238
258
|
// Clear `searchedOptions` when the query is cleared.
|
|
239
259
|
this.searchedOptions = null
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-object(
|
|
3
3
|
v-if="isReady"
|
|
4
|
-
:id="
|
|
4
|
+
:id="componentPath"
|
|
5
5
|
)
|
|
6
6
|
.dito-object-content(
|
|
7
7
|
v-if="objectData"
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
:disabled="disabled || isLoading"
|
|
19
19
|
:collapsed="collapsed"
|
|
20
20
|
:collapsible="collapsible"
|
|
21
|
-
:deletable="
|
|
22
|
-
:editable="objectData && editable"
|
|
23
|
-
:editPath="path"
|
|
21
|
+
:deletable="deletable"
|
|
24
22
|
:accumulatedBasis="accumulatedBasis"
|
|
25
23
|
@delete="deleteItem(objectData)"
|
|
26
24
|
)
|
|
@@ -39,8 +37,9 @@
|
|
|
39
37
|
v-else
|
|
40
38
|
v-html="getItemLabel(schema, objectData)"
|
|
41
39
|
)
|
|
42
|
-
//- NOTE: `DitoEditButtons` here only handle the create
|
|
43
|
-
//- schema, the edit buttons inside are handled by
|
|
40
|
+
//- NOTE: For inlined objects, `DitoEditButtons` here only handle the create
|
|
41
|
+
//- button outside of the schema, the other edit buttons inside are handled by
|
|
42
|
+
//- `DitoSchemaInlined`.
|
|
44
43
|
DitoEditButtons(
|
|
45
44
|
:buttons="buttonSchemas"
|
|
46
45
|
:schema="schema"
|
|
@@ -52,6 +51,10 @@
|
|
|
52
51
|
:disabled="disabled || isLoading"
|
|
53
52
|
:creatable="creatable"
|
|
54
53
|
:createPath="createPath"
|
|
54
|
+
:editable="editable"
|
|
55
|
+
:editPath="path"
|
|
56
|
+
:deletable="!isInlined && deletable"
|
|
57
|
+
@delete="deleteItem(objectData)"
|
|
55
58
|
)
|
|
56
59
|
</template>
|
|
57
60
|
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
60
63
|
import DitoContext from '../DitoContext.js'
|
|
61
64
|
import SourceMixin from '../mixins/SourceMixin.js'
|
|
62
|
-
import { resolveSchemaComponent } from '../utils/schema.js'
|
|
65
|
+
import { resolveSchemaComponent } from '../utils/schema/setup.js'
|
|
63
66
|
|
|
64
67
|
// @vue/component
|
|
65
68
|
export default DitoTypeComponent.register('object', {
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
3
|
+
import { setupSchemaComponents } from '../utils/schema/setup.js'
|
|
3
4
|
|
|
4
5
|
// @vue/component
|
|
5
6
|
export default DitoTypeComponent.register('panel', {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// Panels share the data of their schema, or provide their own data through
|
|
8
|
+
// `schema.data`, see `DitoPanel`, so they never hold a value of their own.
|
|
9
|
+
defaultNested: false,
|
|
8
10
|
generateLabel: false,
|
|
9
11
|
omitSpacing: true,
|
|
10
12
|
|
|
11
13
|
getPanelSchema(api, schema) {
|
|
12
|
-
// For a TypePanel, the component schema is also the panel schema
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
return
|
|
14
|
+
// For a TypePanel, the component schema is also the panel schema. Its name
|
|
15
|
+
// is added to the panel's data path and component path, see
|
|
16
|
+
// `getPanelEntry()`, as unnested components don't add it to data paths.
|
|
17
|
+
return schema
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async processSchema(api, schema, name, routes, level) {
|
|
21
|
+
// Process the panel's components so their forms get resolved too.
|
|
22
|
+
await setupSchemaComponents(api, schema, routes, level)
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
render() {
|
|
26
|
+
// Panel components are displayed in the sidebar, see
|
|
27
|
+
// `DitoContainer.panelEntries`, not in place.
|
|
28
|
+
return null
|
|
16
29
|
}
|
|
17
30
|
})
|
|
18
31
|
</script>
|
|
@@ -17,14 +17,26 @@
|
|
|
17
17
|
</template>
|
|
18
18
|
|
|
19
19
|
<script>
|
|
20
|
+
import { normalizeDataPath, parseDataPath } from '@ditojs/utils'
|
|
20
21
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
22
|
+
import SchemaParentMixin from '../mixins/SchemaParentMixin.js'
|
|
21
23
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
22
|
-
import {
|
|
24
|
+
import { setupSchemaComponents } from '../utils/schema/setup.js'
|
|
25
|
+
import { getItemFormSchema } from '../utils/schema/lookup.js'
|
|
26
|
+
import { hasComponentNamed } from '../utils/schema/structure.js'
|
|
23
27
|
|
|
24
28
|
// @vue/component
|
|
25
29
|
export default DitoTypeComponent.register('section', {
|
|
26
|
-
|
|
30
|
+
// Register the section's schema with the section rather than the form, so
|
|
31
|
+
// that the section can reveal its components, see `navigateToComponent()`.
|
|
32
|
+
mixins: [SchemaParentMixin],
|
|
33
|
+
// Nested sections hold their data in an object, other sections don't have
|
|
34
|
+
// their own value. Use a callback to override `defaultValue: null`.
|
|
35
|
+
defaultValue: ({ schema }) => (schema.nested ? {} : undefined),
|
|
27
36
|
ignoreMissingValue: ({ schema }) => !schema.nested && !('default' in schema),
|
|
37
|
+
// The object of nested sections can't be `null`, unlike the value of the
|
|
38
|
+
// `object` type, where `null` means that there's no object.
|
|
39
|
+
treatNullAsMissing: ({ schema }) => !!schema.nested,
|
|
28
40
|
defaultNested: false,
|
|
29
41
|
generateLabel: false,
|
|
30
42
|
|
|
@@ -52,12 +64,28 @@ export default DitoTypeComponent.register('section', {
|
|
|
52
64
|
},
|
|
53
65
|
|
|
54
66
|
methods: {
|
|
55
|
-
getItemFormSchema
|
|
67
|
+
getItemFormSchema,
|
|
68
|
+
|
|
69
|
+
async navigateToComponent(dataPath, onComplete, options) {
|
|
70
|
+
// Unnested sections share the data path of their parent schema, so
|
|
71
|
+
// decide by schema whether the section displays the value at `dataPath`:
|
|
72
|
+
const { mainSchemaComponent } = this
|
|
73
|
+
const dataPathPrefix = this.dataPath ? `${this.dataPath}/` : ''
|
|
74
|
+
const normalizedDataPath = normalizeDataPath(dataPath)
|
|
75
|
+
const componentName = normalizedDataPath.startsWith(dataPathPrefix)
|
|
76
|
+
? parseDataPath(normalizedDataPath.slice(dataPathPrefix.length))[0]
|
|
77
|
+
: null
|
|
78
|
+
return (
|
|
79
|
+
!!mainSchemaComponent &&
|
|
80
|
+
hasComponentNamed(mainSchemaComponent.schema, componentName) &&
|
|
81
|
+
mainSchemaComponent.navigateToComponent(dataPath, onComplete, options)
|
|
82
|
+
)
|
|
83
|
+
}
|
|
56
84
|
},
|
|
57
85
|
|
|
58
86
|
async processSchema(api, schema, name, routes, level) {
|
|
59
87
|
// Process section components so their forms get resolved too.
|
|
60
|
-
await
|
|
88
|
+
await setupSchemaComponents(api, schema, routes, level)
|
|
61
89
|
}
|
|
62
90
|
})
|
|
63
91
|
</script>
|