@ditojs/admin 2.99.1 → 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.
Files changed (84) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +3532 -2763
  3. package/dist/dito-admin.umd.js +6 -6
  4. package/package.json +82 -46
  5. package/src/DitoComponent.js +1 -1
  6. package/src/DitoContext.js +51 -0
  7. package/src/DitoTypeComponent.js +6 -1
  8. package/src/components/DitoAffixes.vue +6 -3
  9. package/src/components/DitoContainer.vue +19 -10
  10. package/src/components/DitoCreateButton.vue +11 -2
  11. package/src/components/DitoDialog.vue +21 -2
  12. package/src/components/DitoForm.vue +156 -83
  13. package/src/components/DitoLabel.vue +3 -2
  14. package/src/components/DitoPane.vue +55 -11
  15. package/src/components/DitoPanel.vue +33 -5
  16. package/src/components/DitoPanels.vue +2 -1
  17. package/src/components/DitoRoot.vue +13 -6
  18. package/src/components/DitoSchema.vue +278 -145
  19. package/src/components/DitoSchemaInlined.vue +1 -1
  20. package/src/components/DitoTabs.vue +16 -18
  21. package/src/components/DitoTrail.vue +5 -4
  22. package/src/components/DitoTreeItem.vue +17 -6
  23. package/src/components/DitoView.vue +25 -2
  24. package/src/mixins/DitoMixin.js +42 -7
  25. package/src/mixins/ItemMixin.js +3 -6
  26. package/src/mixins/NumberMixin.js +47 -31
  27. package/src/mixins/OptionsMixin.js +122 -71
  28. package/src/mixins/ResourceMixin.js +5 -2
  29. package/src/mixins/RouteMixin.js +39 -1
  30. package/src/mixins/SortableMixin.js +0 -13
  31. package/src/mixins/SourceMixin.js +142 -95
  32. package/src/mixins/TextMixin.js +1 -1
  33. package/src/mixins/TypeMixin.js +37 -22
  34. package/src/mixins/ValidationMixin.js +9 -27
  35. package/src/mixins/ValidatorMixin.js +5 -7
  36. package/src/mixins/ValueMixin.js +5 -2
  37. package/src/types/DitoTypeButton.vue +1 -1
  38. package/src/types/DitoTypeCheckbox.vue +1 -1
  39. package/src/types/DitoTypeCheckboxes.vue +1 -1
  40. package/src/types/DitoTypeCode.vue +8 -1
  41. package/src/types/DitoTypeColor.vue +20 -34
  42. package/src/types/DitoTypeComponent.vue +1 -1
  43. package/src/types/DitoTypeComputed.vue +4 -27
  44. package/src/types/DitoTypeDate.vue +16 -5
  45. package/src/types/DitoTypeLabel.vue +1 -4
  46. package/src/types/DitoTypeList.vue +9 -5
  47. package/src/types/DitoTypeMarkup.vue +25 -13
  48. package/src/types/DitoTypeMultiselect.vue +26 -6
  49. package/src/types/DitoTypeNumber.vue +1 -1
  50. package/src/types/DitoTypeObject.vue +10 -7
  51. package/src/types/DitoTypePanel.vue +19 -6
  52. package/src/types/DitoTypeProgress.vue +1 -1
  53. package/src/types/DitoTypeRadio.vue +1 -1
  54. package/src/types/DitoTypeSection.vue +32 -4
  55. package/src/types/DitoTypeSelect.vue +1 -1
  56. package/src/types/DitoTypeSlider.vue +1 -1
  57. package/src/types/DitoTypeSwitch.vue +1 -1
  58. package/src/types/DitoTypeText.vue +13 -15
  59. package/src/types/DitoTypeTextarea.vue +1 -1
  60. package/src/types/DitoTypeTreeList.vue +12 -7
  61. package/src/types/DitoTypeUpload.vue +1 -2
  62. package/src/utils/DataSchemaResolver.js +141 -0
  63. package/src/utils/DataSchemaResolver.test.js +132 -0
  64. package/src/utils/FormModel.js +556 -0
  65. package/src/utils/FormModel.test.js +808 -0
  66. package/src/utils/data.js +20 -0
  67. package/src/utils/data.test.js +28 -0
  68. package/src/utils/filter.js +3 -2
  69. package/src/utils/options.js +3 -0
  70. package/src/utils/schema/data.js +617 -0
  71. package/src/utils/schema/data.test.js +471 -0
  72. package/src/utils/schema/lookup.js +266 -0
  73. package/src/utils/schema/lookup.test.js +61 -0
  74. package/src/utils/schema/setup.js +354 -0
  75. package/src/utils/schema/structure.js +172 -0
  76. package/src/utils/schema/structure.test.js +57 -0
  77. package/src/utils/schema/types.js +34 -0
  78. package/src/utils/schema/validation.js +142 -0
  79. package/src/utils/schema/validation.test.js +395 -0
  80. package/src/utils/units.js +70 -3
  81. package/src/utils/units.test.js +71 -0
  82. package/types/index.d.ts +149 -59
  83. package/src/mixins/DataMixin.js +0 -131
  84. package/src/utils/schema.js +0 -1121
@@ -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,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`]: this.isLoading,
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.resolveData(
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,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,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
- 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
+ // `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>
@@ -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'}`"
@@ -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 { getItemFormSchema, processSchemaComponents } from '../utils/schema.js'
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
- defaultValue: () => undefined, // Callback to override `defaultValue: null`
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 processSchemaComponents(api, schema, routes, level)
88
+ await setupSchemaComponents(api, schema, routes, level)
61
89
  }
62
90
  })
63
91
  </script>
@@ -11,7 +11,7 @@
11
11
  :parentContext="context"
12
12
  )
13
13
  select(
14
- :id="dataPath"
14
+ :id="componentPath"
15
15
  ref="element"
16
16
  v-model="selectedValue"
17
17
  v-bind="attributes"