@ditojs/admin 2.100.0 → 3.1.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 (81) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +3555 -2719
  3. package/dist/dito-admin.umd.js +5 -6
  4. package/package.json +84 -46
  5. package/src/DitoAdmin.js +1 -65
  6. package/src/DitoComponent.js +1 -1
  7. package/src/DitoContext.js +51 -0
  8. package/src/DitoTypeComponent.js +23 -1
  9. package/src/components/DitoAffixes.vue +6 -3
  10. package/src/components/DitoButtons.vue +14 -1
  11. package/src/components/DitoContainer.vue +19 -10
  12. package/src/components/DitoCreateButton.vue +11 -2
  13. package/src/components/DitoDialog.vue +21 -2
  14. package/src/components/DitoForm.vue +152 -84
  15. package/src/components/DitoLabel.vue +3 -2
  16. package/src/components/DitoPane.vue +55 -11
  17. package/src/components/DitoPanel.vue +34 -5
  18. package/src/components/DitoPanels.vue +2 -1
  19. package/src/components/DitoRoot.vue +6 -6
  20. package/src/components/DitoRouterView.vue +27 -0
  21. package/src/components/DitoSchema.vue +278 -145
  22. package/src/components/DitoSchemaInlined.vue +1 -1
  23. package/src/components/DitoTabs.vue +16 -18
  24. package/src/components/DitoTrail.vue +5 -4
  25. package/src/components/DitoTreeItem.vue +17 -6
  26. package/src/components/DitoView.vue +21 -3
  27. package/src/components/index.js +1 -0
  28. package/src/mixins/DitoMixin.js +53 -7
  29. package/src/mixins/ItemMixin.js +3 -6
  30. package/src/mixins/NumberMixin.js +47 -31
  31. package/src/mixins/OptionsMixin.js +132 -71
  32. package/src/mixins/ResourceMixin.js +5 -2
  33. package/src/mixins/RouteMixin.js +39 -1
  34. package/src/mixins/SortableMixin.js +0 -13
  35. package/src/mixins/SourceMixin.js +155 -96
  36. package/src/mixins/TextMixin.js +1 -1
  37. package/src/mixins/TypeMixin.js +37 -22
  38. package/src/mixins/ValidationMixin.js +9 -27
  39. package/src/mixins/ValidatorMixin.js +5 -7
  40. package/src/mixins/ValueMixin.js +5 -2
  41. package/src/types/DitoTypeButton.vue +1 -1
  42. package/src/types/DitoTypeCheckbox.vue +1 -1
  43. package/src/types/DitoTypeCheckboxes.vue +1 -1
  44. package/src/types/DitoTypeCode.vue +8 -1
  45. package/src/types/DitoTypeColor.vue +20 -34
  46. package/src/types/DitoTypeComponent.vue +1 -1
  47. package/src/types/DitoTypeComputed.vue +4 -27
  48. package/src/types/DitoTypeDate.vue +16 -5
  49. package/src/types/DitoTypeLabel.vue +1 -4
  50. package/src/types/DitoTypeList.vue +9 -5
  51. package/src/types/DitoTypeMarkup.vue +25 -13
  52. package/src/types/DitoTypeMultiselect.vue +37 -6
  53. package/src/types/DitoTypeNumber.vue +1 -1
  54. package/src/types/DitoTypeObject.vue +10 -7
  55. package/src/types/DitoTypePanel.vue +20 -6
  56. package/src/types/DitoTypeProgress.vue +1 -1
  57. package/src/types/DitoTypeRadio.vue +1 -1
  58. package/src/types/DitoTypeSection.vue +32 -4
  59. package/src/types/DitoTypeSelect.vue +1 -1
  60. package/src/types/DitoTypeSlider.vue +1 -1
  61. package/src/types/DitoTypeSwitch.vue +1 -1
  62. package/src/types/DitoTypeText.vue +13 -15
  63. package/src/types/DitoTypeTextarea.vue +1 -1
  64. package/src/types/DitoTypeTreeList.vue +5 -7
  65. package/src/types/DitoTypeUpload.vue +1 -2
  66. package/src/utils/DataSchemaResolver.js +154 -0
  67. package/src/utils/FormModel.js +615 -0
  68. package/src/utils/data.js +20 -0
  69. package/src/utils/filter.js +3 -2
  70. package/src/utils/options.js +3 -0
  71. package/src/utils/request.js +70 -0
  72. package/src/utils/schema/data.js +659 -0
  73. package/src/utils/schema/lookup.js +250 -0
  74. package/src/utils/schema/setup.js +354 -0
  75. package/src/utils/schema/structure.js +172 -0
  76. package/src/utils/schema/types.js +34 -0
  77. package/src/utils/schema/validation.js +142 -0
  78. package/src/utils/units.js +70 -3
  79. package/types/index.d.ts +174 -59
  80. package/src/mixins/DataMixin.js +0 -131
  81. package/src/utils/schema.js +0 -1121
@@ -1,5 +1,6 @@
1
+ import { isFunction } from '@ditojs/utils'
1
2
  import DitoContext from '../DitoContext.js'
2
- import { computeValue } from '../utils/schema.js'
3
+ import { computeValue } from '../utils/schema/data.js'
3
4
 
4
5
  export default {
5
6
  computed: {
@@ -12,8 +13,10 @@ export default {
12
13
  this.dataPath,
13
14
  { component: this }
14
15
  )
16
+ // Only call `format()` if it's a function, as some types use `format`
17
+ // for other purposes, e.g. `DitoTypeColor` for the color format.
15
18
  const { format } = this.schema
16
- return format
19
+ return isFunction(format)
17
20
  ? format(new DitoContext(this, { value }))
18
21
  : value
19
22
  },
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  DitoButton.dito-button(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  ref="element"
5
5
  :type="type"
6
6
  :text="text"
@@ -1,7 +1,7 @@
1
1
  <template lang="pug">
2
2
  .dito-checkbox
3
3
  input(
4
- :id="dataPath"
4
+ :id="componentPath"
5
5
  ref="element"
6
6
  v-model="value"
7
7
  type="checkbox"
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  ul.dito-checkboxes(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  role="group"
5
5
  :aria-label="label"
6
6
  :class="`dito-layout--${schema.layout || 'vertical'}`"
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  .dito-code(
3
- :id="dataPath"
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="dataPath"
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
- import tinycolor from 'tinycolor2'
51
- import { Sketch as SketchPicker } from '@lk77/vue3-color'
52
- import { isObject, isString } from '@ditojs/utils'
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(value) {
91
- this.value = convertColor(value, this.colorFormat)
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 isObject(color) // a vue3-color color object
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="dataPath"
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
- computed: {
30
- value: {
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="dataPath"
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 { isDate, assignDeeply } from '@ditojs/utils'
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
- return isDate(value) ? value.toISOString() : value
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,8 +1,5 @@
1
1
  <template lang="pug">
2
- DitoLabel.dito-label-component(
3
- :label="value"
4
- :dataPath="dataPath"
5
- )
2
+ DitoLabel.dito-label-component(:label="value")
6
3
  </template>
7
4
 
8
5
  <script>
@@ -1,7 +1,7 @@
1
1
  <template lang="pug">
2
2
  .dito-list(
3
3
  v-if="isReady"
4
- :id="dataPath"
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="updateOrder(sourceSchema, listData, paginationRange)"
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="getDataPath(index)"
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="dataPath")
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="{ name, icon, isActive, onClick } in buttons"
11
- :key="name"
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.readyonly,
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
- readyonly: 'updateEditorOptions',
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
- this.onBlur()
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 = ({ name, icon, command, attributes, onClick }) => {
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
- name,
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="dataPath"
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="isLoading"
33
+ :loading="isLoadingOptions || isLoadingSearchedOptions"
34
34
  v-bind="attributes"
35
35
  @open="onOpen"
36
36
  @close="onClose"
@@ -80,6 +80,9 @@ export default DitoTypeComponent.register('multiselect', {
80
80
  data() {
81
81
  return {
82
82
  searchedOptions: null,
83
+ isLoadingSearchedOptions: false,
84
+ // The term of the current search, see `onSearchChange()`:
85
+ currentSearchTerm: null,
83
86
  populate: false
84
87
  }
85
88
  },
@@ -141,7 +144,9 @@ export default DitoTypeComponent.register('multiselect', {
141
144
  const prefix = 'multiselect'
142
145
  return {
143
146
  [`${prefix}--multiple`]: this.multiple,
144
- [`${prefix}--loading`]: this.isLoading,
147
+ [`${prefix}--loading`]: (
148
+ this.isLoadingOptions || this.isLoadingSearchedOptions
149
+ ),
145
150
  [`${prefix}--highlight`]: this.showHighlight
146
151
  }
147
152
  },
@@ -225,18 +230,44 @@ export default DitoTypeComponent.register('multiselect', {
225
230
  }
226
231
  },
227
232
 
233
+ // Returns the options that `searchFilter()` returns for the search term,
234
+ // or `null` if it fails. Waits for the options, as the filter receives
235
+ // them, e.g. to search them when the search term is entered while they are
236
+ // still loading.
237
+ async loadSearchedOptions(searchTerm) {
238
+ try {
239
+ await this.optionsResolver.waitForValue()
240
+ return await this.searchFilter(new DitoContext(this, { searchTerm }))
241
+ } catch (error) {
242
+ this.addError(error.message || error)
243
+ return null
244
+ }
245
+ },
246
+
228
247
  async onSearchChange(searchTerm) {
229
248
  if (this.searchFilter) {
249
+ this.currentSearchTerm = searchTerm
230
250
  if (searchTerm) {
231
251
  // Set `searchedOptions` to an empty array, before it will be
232
252
  // populated asynchronously with the actual results.
233
253
  this.searchedOptions = []
234
- this.searchedOptions = await this.resolveData(
235
- () => this.searchFilter(new DitoContext(this, { searchTerm }))
236
- )
254
+ // Use a timeout to allow already resolved promises to return options
255
+ // without showing a loading indicator.
256
+ const timer = setTimeout(() => {
257
+ this.isLoadingSearchedOptions = true
258
+ }, 0)
259
+ const options = await this.loadSearchedOptions(searchTerm)
260
+ clearTimeout(timer)
261
+ // Searches that were replaced by newer ones in the meantime neither
262
+ // show their options nor end the loading state.
263
+ if (searchTerm === this.currentSearchTerm) {
264
+ this.searchedOptions = options
265
+ this.isLoadingSearchedOptions = false
266
+ }
237
267
  } else {
238
268
  // Clear `searchedOptions` when the query is cleared.
239
269
  this.searchedOptions = null
270
+ this.isLoadingSearchedOptions = false
240
271
  }
241
272
  }
242
273
  }
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  DitoInput.dito-number(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  ref="element"
5
5
  v-model="inputValue"
6
6
  type="number"
@@ -1,7 +1,7 @@
1
1
  <template lang="pug">
2
2
  .dito-object(
3
3
  v-if="isReady"
4
- :id="dataPath"
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="objectData && 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 button outside of the
43
- //- schema, the edit buttons inside are handled by `DitoSchemaInlined`.
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,32 @@
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
- defaultValue: () => undefined, // Callback to override `defaultValue: null`
7
- excludeValue: true,
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, but
13
- // remove the added name, so it doesn't get appended twice to data-path.
14
- const { name, ...panel } = schema
15
- return panel
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
+ // `getAllPanelEntries()`, as unnested components don't add it to data
17
+ // paths.
18
+ return schema
19
+ },
20
+
21
+ async processSchema(api, schema, name, routes, level) {
22
+ // Process the panel's components so their forms get resolved too.
23
+ await setupSchemaComponents(api, schema, routes, level)
24
+ },
25
+
26
+ render() {
27
+ // Panel components are displayed in the sidebar, see
28
+ // `DitoContainer.panelEntries`, not in place.
29
+ return null
16
30
  }
17
31
  })
18
32
  </script>
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  progress.dito-progress(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  ref="element"
5
5
  :value="progressValue"
6
6
  :max="progressMax"
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  ul.dito-radio-buttons(
3
- :id="dataPath"
3
+ :id="componentPath"
4
4
  role="radiogroup"
5
5
  :aria-label="label"
6
6
  :class="`dito-layout--${schema.layout || 'vertical'}`"