@ditojs/admin 3.2.7 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +6011 -5826
  3. package/dist/dito-admin.umd.js +3 -7
  4. package/package.json +18 -17
  5. package/src/DitoAdmin.js +44 -24
  6. package/src/DitoContext.js +28 -2
  7. package/src/DitoTypeComponent.js +1 -0
  8. package/src/appState.js +6 -3
  9. package/src/components/DitoAccount.vue +35 -27
  10. package/src/components/DitoAffixes.vue +9 -2
  11. package/src/components/DitoClipboard.vue +59 -93
  12. package/src/components/DitoConfirmMessage.vue +18 -0
  13. package/src/components/DitoContainer.vue +56 -18
  14. package/src/components/DitoCreateButton.vue +36 -84
  15. package/src/components/DitoDialog.vue +20 -15
  16. package/src/components/DitoDragHandle.vue +44 -0
  17. package/src/components/DitoDraggable.vue +43 -16
  18. package/src/components/DitoEditButtons.vue +48 -24
  19. package/src/components/DitoErrors.vue +9 -56
  20. package/src/components/DitoForm.vue +82 -42
  21. package/src/components/DitoInputAffixes.vue +50 -0
  22. package/src/components/DitoLabel.vue +1 -15
  23. package/src/components/DitoMenu.vue +45 -52
  24. package/src/components/DitoNotifications.vue +38 -14
  25. package/src/components/DitoOptionList.vue +81 -0
  26. package/src/components/DitoOptionsEditButtons.vue +57 -0
  27. package/src/components/DitoPagination.vue +1 -8
  28. package/src/components/DitoPane.vue +51 -39
  29. package/src/components/DitoPanel.vue +12 -26
  30. package/src/components/DitoPanels.vue +11 -5
  31. package/src/components/DitoResizeHandle.vue +47 -0
  32. package/src/components/DitoRoot.vue +113 -320
  33. package/src/components/DitoSchema.vue +72 -63
  34. package/src/components/DitoSchemaInlined.vue +8 -9
  35. package/src/components/DitoScopes.vue +14 -18
  36. package/src/components/DitoSidebar.vue +4 -29
  37. package/src/components/DitoSourceItem.vue +114 -0
  38. package/src/components/DitoTableCell.vue +4 -0
  39. package/src/components/DitoTableHead.vue +15 -17
  40. package/src/components/DitoTabs.vue +25 -0
  41. package/src/components/DitoTrail.vue +25 -25
  42. package/src/components/DitoTreeItem.vue +102 -141
  43. package/src/components/DitoUploadFile.vue +5 -2
  44. package/src/components/DitoView.vue +33 -24
  45. package/src/index.js +4 -1
  46. package/src/mixins/DitoMixin.js +77 -110
  47. package/src/mixins/DomMixin.js +12 -14
  48. package/src/mixins/EmitterMixin.js +3 -2
  49. package/src/mixins/ItemMixin.js +38 -94
  50. package/src/mixins/OptionsMixin.js +38 -25
  51. package/src/mixins/ResourceMixin.js +182 -111
  52. package/src/mixins/RouteMixin.js +61 -44
  53. package/src/mixins/SchemaParentMixin.js +4 -1
  54. package/src/mixins/SourceMixin.js +118 -100
  55. package/src/mixins/TypeMixin.js +42 -40
  56. package/src/mixins/ValidationMixin.js +16 -4
  57. package/src/mixins/ValidatorMixin.js +1 -1
  58. package/src/styles/_settings.scss +4 -0
  59. package/src/styles/_tippy.scss +0 -11
  60. package/src/styles/style.scss +0 -1
  61. package/src/types/DitoTypeButton.vue +11 -14
  62. package/src/types/DitoTypeCheckboxes.vue +35 -17
  63. package/src/types/DitoTypeCode.vue +19 -41
  64. package/src/types/DitoTypeColor.vue +67 -19
  65. package/src/types/DitoTypeDate.vue +11 -14
  66. package/src/types/DitoTypeList.vue +50 -62
  67. package/src/types/DitoTypeMarkup.vue +41 -54
  68. package/src/types/DitoTypeMultiselect.vue +82 -52
  69. package/src/types/DitoTypeNumber.vue +12 -15
  70. package/src/types/DitoTypeObject.vue +4 -38
  71. package/src/types/DitoTypeRadio.vue +19 -6
  72. package/src/types/DitoTypeSection.vue +23 -9
  73. package/src/types/DitoTypeSelect.vue +39 -23
  74. package/src/types/DitoTypeSlider.vue +9 -1
  75. package/src/types/DitoTypeSwitch.vue +1 -1
  76. package/src/types/DitoTypeText.vue +11 -14
  77. package/src/types/DitoTypeTreeList.vue +46 -8
  78. package/src/types/DitoTypeUpload.vue +190 -67
  79. package/src/utils/DataModel.js +368 -146
  80. package/src/utils/FileDropTargets.js +97 -0
  81. package/src/utils/ItemStores.js +31 -0
  82. package/src/utils/ListQuery.js +115 -0
  83. package/src/utils/LoadingTracker.js +76 -0
  84. package/src/utils/SchemaGraph.js +3 -2
  85. package/src/utils/Session.js +151 -0
  86. package/src/utils/ViewRegistry.js +85 -0
  87. package/src/utils/accessor.js +12 -4
  88. package/src/utils/affixes.js +33 -0
  89. package/src/utils/changeOnceEdited.js +33 -0
  90. package/src/utils/clipboard.js +55 -0
  91. package/src/utils/data.js +10 -5
  92. package/src/utils/dialogs.js +59 -0
  93. package/src/utils/dragResize.js +61 -0
  94. package/src/utils/filter.js +36 -2
  95. package/src/utils/html.js +9 -0
  96. package/src/utils/list.js +10 -0
  97. package/src/utils/options.js +4 -0
  98. package/src/utils/registration.js +18 -0
  99. package/src/utils/request.js +108 -13
  100. package/src/utils/route.js +6 -17
  101. package/src/utils/schema/data.js +24 -2
  102. package/src/utils/schema/events.js +19 -0
  103. package/src/utils/schema/item.js +114 -0
  104. package/src/utils/schema/lookup.js +39 -14
  105. package/src/utils/schema/setup.js +5 -5
  106. package/src/utils/schema/validation.js +16 -2
  107. package/src/utils/uid.js +105 -8
  108. package/src/validators/_decimals.js +16 -5
  109. package/types/index.d.ts +168 -41
  110. package/src/mixins/LoadingMixin.js +0 -23
  111. package/src/mixins/PulldownMixin.js +0 -72
  112. package/src/styles/_pulldown.scss +0 -38
@@ -12,7 +12,6 @@
12
12
  :disabled="disabled"
13
13
  :collapsed="collapsed"
14
14
  :collapsible="collapsible"
15
- :labelNode="labelNode"
16
15
  )
17
16
  </template>
18
17
 
@@ -23,7 +22,7 @@ import SchemaParentMixin from '../mixins/SchemaParentMixin.js'
23
22
  import { getSchemaAccessor } from '../utils/accessor.js'
24
23
  import { setupSchemaComponents } from '../utils/schema/setup.js'
25
24
  import { getItemFormSchema } from '../utils/schema/lookup.js'
26
- import { hasComponentNamed } from '../utils/schema/structure.js'
25
+ import { hasComponentNamed, isCompact } from '../utils/schema/structure.js'
27
26
 
28
27
  // @vue/component
29
28
  export default DitoTypeComponent.register('section', {
@@ -39,6 +38,17 @@ export default DitoTypeComponent.register('section', {
39
38
  treatNullAsMissing: ({ schema }) => !!schema.nested,
40
39
  defaultNested: false,
41
40
  generateLabel: false,
41
+ // The header of the section's form displays the label, except in compact
42
+ // forms, which don't have labels in their header, see `DitoSchemaInlined`.
43
+ // The form can depend on the item, see `item`.
44
+ rendersOwnLabel: context =>
45
+ !isCompact(
46
+ getItemFormSchema(
47
+ context.schema,
48
+ context.nested ? context.value : context.item,
49
+ context
50
+ )
51
+ ),
42
52
 
43
53
  computed: {
44
54
  item() {
@@ -94,14 +104,18 @@ export default DitoTypeComponent.register('section', {
94
104
  @import '../styles/_imports';
95
105
 
96
106
  .dito-section {
97
- &--labelled {
98
- border: $border-width solid transparent;
99
- border-radius: $border-radius;
100
- transition: border-color 0.2s $ease-out-quart;
101
- margin-top: $form-spacing-half;
107
+ &--labelled > &__schema {
108
+ // Display the header with the label above the bordered content.
109
+ > .dito-schema-content {
110
+ border: $border-style;
111
+ border-radius: $border-radius;
112
+ margin-top: $form-spacing-half;
113
+ }
102
114
 
103
- &:has(.dito-schema--open) {
104
- border-color: $border-color;
115
+ > .dito-schema-header + .dito-schema-content > .dito-pane {
116
+ // Inside the border, the content's margin replaces the spacing below the
117
+ // header, see `DitoSchema`.
118
+ margin-top: -$form-spacing-half;
105
119
  }
106
120
  }
107
121
  }
@@ -2,13 +2,10 @@
2
2
  //- Nesting is needed to make an arrow appear over the select item:
3
3
  .dito-select
4
4
  .dito-select__inner
5
- DitoAffixes(
6
- :items="schema.prefix"
5
+ DitoInputAffixes(
7
6
  position="prefix"
8
- mode="input"
7
+ v-bind="inputAffixesProps.prefix"
9
8
  absolute
10
- :disabled="disabled"
11
- :parentContext="context"
12
9
  )
13
10
  select(
14
11
  :id="componentPath"
@@ -21,6 +18,13 @@
21
18
  template(
22
19
  v-if="populate"
23
20
  )
21
+ //- Plain values whose option disappeared are kept and shown as
22
+ //- unavailable, rather than cleared without the user noticing:
23
+ option(
24
+ v-if="shouldShowUnavailableValue"
25
+ :value="selectedValue"
26
+ disabled
27
+ ) {{ getLabelForOption(selectedValue) }} (unavailable)
24
28
  template(
25
29
  v-for="option in options"
26
30
  )
@@ -40,40 +44,35 @@
40
44
  v-else-if="selectedOption"
41
45
  )
42
46
  option(:value="selectedValue") {{ getLabelForOption(selectedOption) }}
43
- DitoAffixes(
44
- :items="schema.suffix"
47
+ DitoInputAffixes(
45
48
  position="suffix"
46
- mode="input"
49
+ v-bind="inputAffixesProps.suffix"
47
50
  absolute
48
- :clearable="showClearButton"
49
- :disabled="disabled"
50
- :inlineInfo="inlineInfo"
51
- :parentContext="context"
52
51
  @clear="clear"
53
52
  )
54
- //- Edit button is never disabled, even if the field is disabled.
55
- DitoEditButtons(
53
+ DitoOptionsEditButtons(
56
54
  v-if="editable"
57
55
  :schema="schema"
58
56
  :dataPath="dataPath"
59
57
  :data="data"
60
58
  :meta="meta"
61
59
  :store="store"
62
- :disabled="false"
63
- :editable="editable"
64
- :editPath="editPath"
60
+ :parentContext="context"
61
+ :optionValue="selectedValue"
65
62
  )
66
63
  </template>
67
64
 
68
65
  <script>
69
66
  import DitoTypeComponent from '../DitoTypeComponent.js'
70
67
  import OptionsMixin from '../mixins/OptionsMixin.js'
71
- import DitoAffixes from '../components/DitoAffixes.vue'
68
+ import DitoInputAffixes from '../components/DitoInputAffixes.vue'
69
+ import { getInputAffixesProps } from '../utils/affixes.js'
70
+ import DitoOptionsEditButtons from '../components/DitoOptionsEditButtons.vue'
72
71
 
73
72
  // @vue/component
74
73
  export default DitoTypeComponent.register('select', {
75
74
  mixins: [OptionsMixin],
76
- components: { DitoAffixes },
75
+ components: { DitoInputAffixes, DitoOptionsEditButtons },
77
76
 
78
77
  nativeField: true,
79
78
 
@@ -83,6 +82,27 @@ export default DitoTypeComponent.register('select', {
83
82
  // TODO: Set to `false` Once lineto e2e tests address their issues.
84
83
  populate: true
85
84
  }
85
+ },
86
+
87
+ computed: {
88
+ inputAffixesProps() {
89
+ return getInputAffixesProps(this)
90
+ },
91
+
92
+ // Whether the value is set but none of the loaded options has it. Values
93
+ // of option objects are replaced with `null` when their option disappears,
94
+ // see `OptionsMixin`, but plain values match any value, so they stay.
95
+ // Object values without `optionValue` are left to the select, which
96
+ // matches them to their options by deep equality.
97
+ shouldShowUnavailableValue() {
98
+ const { selectedValue } = this
99
+ return (
100
+ selectedValue != null &&
101
+ typeof selectedValue !== 'object' &&
102
+ this.hasOptions &&
103
+ !this.findOptionForValue(selectedValue)
104
+ )
105
+ }
86
106
  }
87
107
  })
88
108
  </script>
@@ -119,10 +139,6 @@ export default DitoTypeComponent.register('select', {
119
139
  }
120
140
  }
121
141
 
122
- .dito-edit-buttons {
123
- margin-left: $form-spacing-half;
124
- }
125
-
126
142
  // Handle width fill separately due to required nesting of select:
127
143
  &.dito-component--fill {
128
144
  select {
@@ -10,11 +10,15 @@
10
10
  :max="max"
11
11
  :step="stepValue"
12
12
  )
13
+ //- The range input holds the name and the label of the field, the number
14
+ //- input gets a label of its own and no name, to not submit it twice:
13
15
  DitoInput.dito-number(
14
16
  v-if="input"
15
17
  v-model="inputValue"
16
18
  type="number"
17
19
  v-bind="attributes"
20
+ :name="null"
21
+ :aria-label="numberInputLabel"
18
22
  :min="min"
19
23
  :max="max"
20
24
  :step="stepValue"
@@ -38,7 +42,11 @@ export default DitoTypeComponent.register('slider', {
38
42
  input: getSchemaAccessor('input', {
39
43
  type: Boolean,
40
44
  default: true
41
- })
45
+ }),
46
+
47
+ numberInputLabel() {
48
+ return `${this.label || this.labelize(this.name)} Value`
49
+ }
42
50
  }
43
51
  })
44
52
  </script>
@@ -33,7 +33,7 @@ export default DitoTypeComponent.register('switch', {
33
33
  @import '../styles/_imports';
34
34
 
35
35
  .dito-switch {
36
- .dito-switch-label {
36
+ &__label {
37
37
  font-size: $font-size-small;
38
38
  }
39
39
  }
@@ -7,22 +7,14 @@ DitoInput.dito-text(
7
7
  v-bind="attributes"
8
8
  )
9
9
  template(#prefix)
10
- DitoAffixes(
11
- :items="schema.prefix"
10
+ DitoInputAffixes(
12
11
  position="prefix"
13
- mode="input"
14
- :disabled="disabled"
15
- :parentContext="context"
12
+ v-bind="inputAffixesProps.prefix"
16
13
  )
17
14
  template(#suffix)
18
- DitoAffixes(
19
- :items="schema.suffix"
15
+ DitoInputAffixes(
20
16
  position="suffix"
21
- mode="input"
22
- :clearable="showClearButton"
23
- :disabled="disabled"
24
- :inlineInfo="inlineInfo"
25
- :parentContext="context"
17
+ v-bind="inputAffixesProps.suffix"
26
18
  @clear="clear"
27
19
  )
28
20
  </template>
@@ -30,7 +22,8 @@ DitoInput.dito-text(
30
22
  <script>
31
23
  import DitoTypeComponent from '../DitoTypeComponent.js'
32
24
  import TextMixin from '../mixins/TextMixin'
33
- import DitoAffixes from '../components/DitoAffixes.vue'
25
+ import DitoInputAffixes from '../components/DitoInputAffixes.vue'
26
+ import { getInputAffixesProps } from '../utils/affixes.js'
34
27
  import { DitoInput } from '@ditojs/ui/src'
35
28
 
36
29
  const maskedPassword = '****************'
@@ -49,7 +42,7 @@ export default DitoTypeComponent.register(
49
42
  // @vue/component
50
43
  {
51
44
  mixins: [TextMixin],
52
- components: { DitoInput, DitoAffixes },
45
+ components: { DitoInput, DitoInputAffixes },
53
46
  nativeField: true,
54
47
  textField: true,
55
48
  ignoreMissingValue: ({ schema }) => schema.type === 'password',
@@ -67,6 +60,10 @@ export default DitoTypeComponent.register(
67
60
  },
68
61
 
69
62
  computed: {
63
+ inputAffixesProps() {
64
+ return getInputAffixesProps(this)
65
+ },
66
+
70
67
  inputType() {
71
68
  return (
72
69
  {
@@ -14,7 +14,7 @@
14
14
  :data="treeData"
15
15
  :draggable="draggable"
16
16
  :open="true"
17
- @update:data="data => (value = data)"
17
+ @change-children="changeChildren"
18
18
  )
19
19
  .dito-tree-form-container(
20
20
  v-if="editPath && hasEditableForms"
@@ -28,7 +28,9 @@ import DitoTypeComponent from '../DitoTypeComponent.js'
28
28
  import SourceMixin from '../mixins/SourceMixin.js'
29
29
  import { resolveSchemaComponents } from '../utils/schema/setup.js'
30
30
  import { hasFormSchema, getFormSchemas } from '../utils/schema/lookup.js'
31
+ import { updateOrder } from '../utils/schema/data.js'
31
32
  import { getParentDataPath } from '../utils/data.js'
33
+ import { isPathWithin } from '../utils/route.js'
32
34
 
33
35
  export default DitoTypeComponent.register(
34
36
  ['tree-list', 'tree-object'],
@@ -36,24 +38,20 @@ export default DitoTypeComponent.register(
36
38
  {
37
39
  mixins: [SourceMixin],
38
40
 
39
- provide() {
40
- return { container: this }
41
- },
42
-
43
41
  getSourceType(type) {
44
42
  return type === 'tree-object' ? 'object' : 'list'
45
43
  },
46
44
 
47
45
  computed: {
48
46
  path() {
49
- // Accessed from DitoTreeItem through `container.path`:
47
+ // Accessed from DitoTreeItem through `sourceComponent.path`:
50
48
  return this.formComponent?.path
51
49
  },
52
50
 
53
51
  editPath() {
54
- // Accessed from DitoTreeItem through `container.editPath`:
52
+ // Accessed from DitoTreeItem through `sourceComponent.editPath`:
55
53
  const path = this.$route.path.slice(this.path?.length)
56
- return path.startsWith(`/${this.schema.path}`) ? path : ''
54
+ return isPathWithin(path, `/${this.schema.path}`) ? path : ''
57
55
  },
58
56
 
59
57
  treeData() {
@@ -100,6 +98,46 @@ export default DitoTypeComponent.register(
100
98
  }
101
99
  },
102
100
 
101
+ methods: {
102
+ // Replaces the children of the tree item that holds them in `data`, as
103
+ // the only writer of the tree, see `DitoTreeItem.changeChildren()`.
104
+ changeChildren({ data, childrenSchema, path, children }) {
105
+ const previousChildren = data[childrenSchema.name]
106
+ updateOrder(childrenSchema, children)
107
+ if (data === this.treeData && this.isListSource) {
108
+ // The root item of tree lists holds the list in a wrapper object,
109
+ // see `treeData`.
110
+ this.value = children
111
+ } else {
112
+ data[childrenSchema.name] = children
113
+ }
114
+ this.followEditedChild({
115
+ childrenPath: childrenSchema.path && `${path}/${childrenSchema.path}`,
116
+ previousChildren,
117
+ children
118
+ })
119
+ this.onChange()
120
+ },
121
+
122
+ // Keeps the form of an edited child or of one of its descendants open
123
+ // at the child's new index, or closes it if the child was removed.
124
+ followEditedChild({ childrenPath, previousChildren, children }) {
125
+ const prefix = `${childrenPath}/`
126
+ const { editPath } = this
127
+ if (childrenPath && editPath.startsWith(prefix)) {
128
+ const [index, ...rest] = editPath.slice(prefix.length).split('/')
129
+ const newIndex = children.indexOf(previousChildren?.[index])
130
+ if (newIndex !== +index) {
131
+ const path =
132
+ newIndex >= 0
133
+ ? [`${this.path}${prefix}${newIndex}`, ...rest].join('/')
134
+ : this.path
135
+ this.$router.push({ path, query: this.$route.query })
136
+ }
137
+ }
138
+ }
139
+ },
140
+
103
141
  async processSchema(
104
142
  api,
105
143
  schema,