@ditojs/admin 2.73.3 → 2.74.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.73.3",
3
+ "version": "2.74.0",
4
4
  "type": "module",
5
5
  "description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/admin",
@@ -42,8 +42,8 @@
42
42
  "not ie_mob > 0"
43
43
  ],
44
44
  "dependencies": {
45
- "@ditojs/ui": "^2.73.2",
46
- "@ditojs/utils": "^2.73.0",
45
+ "@ditojs/ui": "^2.74.0",
46
+ "@ditojs/utils": "^2.74.0",
47
47
  "@kyvg/vue3-notification": "^3.4.2",
48
48
  "@lk77/vue3-color": "^3.0.6",
49
49
  "@tiptap/core": "^3.15.3",
@@ -89,9 +89,9 @@
89
89
  "vue-upload-component": "^3.1.17"
90
90
  },
91
91
  "devDependencies": {
92
- "@ditojs/build": "^2.73.0",
92
+ "@ditojs/build": "^2.74.0",
93
93
  "typescript": "^5.9.3",
94
94
  "vite": "^7.3.1"
95
95
  },
96
- "gitHead": "55f8ac1cf101cf28feb1af341986c32e5b391b48"
96
+ "gitHead": "26c5b4bd33d1bc469c8b621a214ae3c8f0927c11"
97
97
  }
@@ -1,7 +1,9 @@
1
1
  <template lang="pug">
2
2
  .dito-affixes(
3
- v-if="visibleItems.length > 0"
3
+ v-if="hasContent"
4
+ :class="classes"
4
5
  )
6
+ slot(name="prepend")
5
7
  DitoAffix.dito-affix(
6
8
  v-for="(item, index) in visibleItems"
7
9
  :key="index"
@@ -10,19 +12,33 @@
10
12
  :item="item"
11
13
  :parentContext="parentContext"
12
14
  )
15
+ button.dito-affixes__clear(
16
+ v-if="clearable"
17
+ type="button"
18
+ :disabled="disabled"
19
+ @click.stop="$emit('clear')"
20
+ @mousedown.stop
21
+ )
22
+ slot(name="append")
13
23
  </template>
14
24
 
15
25
  <script>
16
26
  import DitoComponent from '../DitoComponent.js'
17
27
  import DitoAffix from './DitoAffix.vue'
18
28
  import { asArray, isString } from '@ditojs/utils'
29
+ import { hasSlotContent } from '@ditojs/ui/src'
19
30
  import { shouldRenderSchema } from '../utils/schema.js'
20
31
 
21
32
  export default DitoComponent.component('DitoAffixes', {
22
33
  components: { DitoAffix },
34
+ emits: ['clear'],
23
35
 
24
36
  props: {
25
37
  items: { type: [String, Object, Array], default: null },
38
+ position: { type: String, default: null },
39
+ absolute: { type: Boolean, default: false },
40
+ clearable: { type: Boolean, default: false },
41
+ disabled: { type: Boolean, default: false },
26
42
  parentContext: { type: Object, required: true }
27
43
  },
28
44
 
@@ -52,6 +68,23 @@ export default DitoComponent.component('DitoAffixes', {
52
68
  return this.processedItems.filter(item =>
53
69
  shouldRenderSchema(item, this.context)
54
70
  )
71
+ },
72
+
73
+ classes() {
74
+ const prefix = 'dito-affixes'
75
+ return {
76
+ [`${prefix}--${this.position}`]: this.position,
77
+ [`${prefix}--absolute`]: this.absolute
78
+ }
79
+ },
80
+
81
+ hasContent() {
82
+ return (
83
+ this.visibleItems.length > 0 ||
84
+ this.clearable ||
85
+ hasSlotContent(this.$slots.prepend) ||
86
+ hasSlotContent(this.$slots.append)
87
+ )
55
88
  }
56
89
  }
57
90
  })
@@ -61,13 +94,34 @@ export default DitoComponent.component('DitoAffixes', {
61
94
  @import '../styles/_imports';
62
95
 
63
96
  .dito-affixes {
97
+ $self: &;
98
+
64
99
  @include user-select(none);
65
100
 
66
101
  display: flex;
67
102
  align-items: center;
68
103
  gap: $input-padding-hor;
69
104
 
70
- @at-root .dito-input & {
105
+ &--absolute {
106
+ position: absolute;
107
+ inset: $border-width;
108
+ margin: $input-padding-ver $input-padding-hor;
109
+ pointer-events: none;
110
+
111
+ > * {
112
+ pointer-events: auto;
113
+ }
114
+
115
+ &#{$self}--prefix {
116
+ right: unset;
117
+ }
118
+
119
+ &#{$self}--suffix {
120
+ left: unset;
121
+ }
122
+ }
123
+
124
+ @at-root .dito-component & {
71
125
  color: $color-grey;
72
126
 
73
127
  .dito-icon--disabled {
@@ -75,8 +129,44 @@ export default DitoComponent.component('DitoAffixes', {
75
129
  }
76
130
  }
77
131
 
78
- @at-root .dito-input:focus-within & {
132
+ @at-root .dito-component:focus-within:not(:has(.dito-component)) & {
79
133
  color: $color-active;
80
134
  }
135
+
136
+ &__clear {
137
+ --size: #{calc($input-height - 4 * $border-width)};
138
+
139
+ display: none;
140
+ position: relative;
141
+ cursor: pointer;
142
+ width: var(--size);
143
+ height: var(--size);
144
+ border-radius: $border-radius;
145
+ margin: (-$input-padding-ver + $border-width)
146
+ (-$input-padding-hor + $border-width);
147
+ padding: 0;
148
+ border: 0;
149
+
150
+ @at-root .dito-component:hover:not(:has(.dito-component)) & {
151
+ display: block;
152
+ }
153
+
154
+ &::before {
155
+ @extend %icon-clear;
156
+
157
+ color: $color-grey;
158
+ }
159
+
160
+ &:hover::before {
161
+ color: $color-black;
162
+ }
163
+ }
164
+
165
+ // Hide other affixes when clear button is shown
166
+ // prettier-ignore
167
+ @at-root .dito-component:hover:not(:has(.dito-component))
168
+ #{$self}:has(#{$self}__clear) > *:not(#{$self}__clear) {
169
+ display: none;
170
+ }
81
171
  }
82
172
  </style>
@@ -36,9 +36,9 @@
36
36
  :label="label"
37
37
  :single="single"
38
38
  :nested="nested"
39
- :disabled="componentDisabled"
40
39
  :accumulatedBasis="combinedBasis"
41
40
  @errors="onErrors"
41
+ @update:component="value => (component = value)"
42
42
  )
43
43
  DitoErrors(:errors="errors")
44
44
  </template>
@@ -48,6 +48,7 @@ import { isString, isNumber } from '@ditojs/utils'
48
48
  import DitoComponent from '../DitoComponent.js'
49
49
  import ValueMixin from '../mixins/ValueMixin.js'
50
50
  import ContextMixin from '../mixins/ContextMixin.js'
51
+ import DitoContext from '../DitoContext.js'
51
52
  import { getSchemaAccessor } from '../utils/accessor.js'
52
53
  import {
53
54
  getAllPanelEntries,
@@ -77,11 +78,29 @@ export default DitoComponent.component('DitoContainer', {
77
78
 
78
79
  data() {
79
80
  return {
80
- errors: null
81
+ errors: null,
82
+ // The nested type component instance, for context-based schema accessor
83
+ // evaluation.
84
+ component: null
81
85
  }
82
86
  },
83
87
 
84
88
  computed: {
89
+ context() {
90
+ return new DitoContext(
91
+ // When available, use the type component for context-based schema
92
+ // accessors, but fall back to container.
93
+ // TODO: Consider architectural inversion to eliminate timing issues:
94
+ // - Type components render DitoContainer at their root
95
+ // - Pass type component content through container's default slot
96
+ // - DitoPane/DitoButtons render type components directly
97
+ // - Eliminates need for component instance synchronization
98
+ // - Provides true synchronous access to component context
99
+ this.component ?? this,
100
+ { nested: this.nested }
101
+ )
102
+ },
103
+
85
104
  name() {
86
105
  return this.schema.name
87
106
  },
@@ -15,6 +15,7 @@ component.dito-label(
15
15
  )
16
16
  DitoAffixes.dito-label__prefix(
17
17
  :items="prefixes"
18
+ position="prefix"
18
19
  :parentContext="context"
19
20
  )
20
21
  label(
@@ -24,6 +25,7 @@ component.dito-label(
24
25
  )
25
26
  DitoAffixes.dito-label__suffix(
26
27
  :items="suffixes"
28
+ position="suffix"
27
29
  :parentContext="context"
28
30
  )
29
31
  .dito-info(
@@ -213,6 +215,7 @@ export default DitoComponent.component('DitoLabel', {
213
215
  @at-root button#{&} {
214
216
  border: 0;
215
217
  padding: 0;
218
+ background: none;
216
219
  text-align: start;
217
220
 
218
221
  &:hover {
@@ -7,6 +7,7 @@ import { asArray, camelize } from '@ditojs/utils'
7
7
  // @vue/component
8
8
  export default {
9
9
  mixins: [ValueMixin, ContextMixin, ValidationMixin],
10
+ emits: ['update:component'],
10
11
 
11
12
  props: {
12
13
  schema: { type: Object, required: true },
@@ -20,7 +21,6 @@ export default {
20
21
  label: { type: String, default: null },
21
22
  single: { type: Boolean, default: false },
22
23
  nested: { type: Boolean, default: true },
23
- disabled: { type: Boolean, default: false },
24
24
  accumulatedBasis: { type: Number, default: null }
25
25
  },
26
26
 
@@ -84,6 +84,11 @@ export default {
84
84
  }
85
85
  }),
86
86
 
87
+ disabled: getSchemaAccessor('disabled', {
88
+ type: Boolean,
89
+ default: false
90
+ }),
91
+
87
92
  maxLength: getSchemaAccessor('maxLength', {
88
93
  type: Number
89
94
  }),
@@ -167,6 +172,8 @@ export default {
167
172
 
168
173
  methods: {
169
174
  _register(add) {
175
+ // Provide component to container for schema accessor evaluation.
176
+ this.$emit('update:component', add ? this : null)
170
177
  // Prevent unnested type components from overriding parent data paths
171
178
  if (this.nested) {
172
179
  this.schemaComponent._registerComponent(this, add)
@@ -109,37 +109,6 @@
109
109
  cursor: grabbing;
110
110
  }
111
111
  }
112
-
113
- &--overlay {
114
- $overlay-inset: calc(2 * $border-width);
115
-
116
- position: absolute;
117
- inset: $overlay-inset;
118
- left: unset;
119
- padding: 0;
120
- border: 0;
121
- border-radius: $border-radius;
122
- cursor: pointer;
123
-
124
- &.dito-button--clear {
125
- width: calc(2em - 2 * ($border-width + $overlay-inset));
126
- display: none;
127
-
128
- @at-root .dito-component:hover:not(:has(.dito-component)) & {
129
- display: block;
130
- }
131
-
132
- &::before {
133
- @extend %icon-clear;
134
-
135
- color: $color-grey;
136
- }
137
-
138
- &:hover::before {
139
- color: $color-black;
140
- }
141
- }
142
- }
143
112
  }
144
113
 
145
114
  .dito-buttons {
@@ -13,12 +13,16 @@ DitoButton.dito-button(
13
13
  DitoAffixes(
14
14
  v-if="prefixes.length > 0"
15
15
  :items="prefixes"
16
+ position="prefix"
17
+ :disabled="disabled"
16
18
  :parentContext="context"
17
19
  )
18
20
  template(#suffix)
19
21
  DitoAffixes(
20
22
  v-if="suffixes.length > 0"
21
23
  :items="suffixes"
24
+ position="suffix"
25
+ :disabled="disabled"
22
26
  :parentContext="context"
23
27
  )
24
28
  .dito-info(
@@ -4,24 +4,36 @@ DitoTrigger.dito-color(
4
4
  trigger="focus"
5
5
  )
6
6
  template(#trigger)
7
- .dito-input(:class="{ 'dito-input--focus': showPopup }")
8
- input(
9
- :id="dataPath"
10
- ref="element"
11
- v-model="hexValue"
12
- type="input"
13
- size="8"
14
- v-bind="attributes"
15
- )
16
- .dito-color__preview(
17
- v-if="value"
18
- )
19
- div(:style="{ background: `#${hexValue || '00000000'}` }")
20
- button.dito-button--clear.dito-button--overlay(
21
- v-if="showClearButton"
22
- :disabled="disabled"
23
- @click.stop="clear"
24
- )
7
+ DitoInput(
8
+ :id="dataPath"
9
+ ref="element"
10
+ v-model="hexValue"
11
+ type="input"
12
+ size="8"
13
+ :focused="showPopup"
14
+ v-bind="attributes"
15
+ )
16
+ template(#prefix)
17
+ DitoAffixes(
18
+ :items="schema.prefix"
19
+ position="prefix"
20
+ :disabled="disabled"
21
+ :parentContext="context"
22
+ )
23
+ template(#suffix)
24
+ DitoAffixes(
25
+ :items="schema.suffix"
26
+ position="suffix"
27
+ :clearable="showClearButton"
28
+ :disabled="disabled"
29
+ :parentContext="context"
30
+ @clear="clear"
31
+ )
32
+ template(#append)
33
+ .dito-color__preview(
34
+ v-if="value"
35
+ )
36
+ div(:style="{ background: `#${hexValue || '00000000'}` }")
25
37
  template(#popup)
26
38
  SketchPicker.dito-color__picker(
27
39
  v-model="colorValue"
@@ -35,8 +47,9 @@ DitoTrigger.dito-color(
35
47
  import tinycolor from 'tinycolor2'
36
48
  import { Sketch as SketchPicker } from '@lk77/vue3-color'
37
49
  import { isObject, isString } from '@ditojs/utils'
38
- import { DitoTrigger } from '@ditojs/ui/src'
50
+ import { DitoTrigger, DitoInput } from '@ditojs/ui/src'
39
51
  import DitoTypeComponent from '../DitoTypeComponent.js'
52
+ import DitoAffixes from '../components/DitoAffixes.vue'
40
53
  import { getSchemaAccessor } from '../utils/accessor.js'
41
54
 
42
55
  // Monkey-patch the `SketchPicker's` `hex` computed property to return lowercase
@@ -48,7 +61,7 @@ SketchPicker.computed.hex = function () {
48
61
 
49
62
  // @vue/component
50
63
  export default DitoTypeComponent.register('color', {
51
- components: { DitoTrigger, SketchPicker },
64
+ components: { DitoTrigger, DitoInput, DitoAffixes, SketchPicker },
52
65
 
53
66
  data() {
54
67
  return {
@@ -214,7 +227,7 @@ $color-swatch-radius: $border-radius - $border-width;
214
227
 
215
228
  .dito-color {
216
229
  .dito-input {
217
- display: block;
230
+ display: flex;
218
231
  position: relative;
219
232
 
220
233
  input {
@@ -224,10 +237,6 @@ $color-swatch-radius: $border-radius - $border-width;
224
237
  }
225
238
  }
226
239
 
227
- .dito-button--clear {
228
- margin-right: $color-swatch-width;
229
- }
230
-
231
240
  &__picker {
232
241
  margin: $popup-margin;
233
242
  border: $border-style;
@@ -238,15 +247,14 @@ $color-swatch-radius: $border-radius - $border-width;
238
247
 
239
248
  &__preview {
240
249
  background: $pattern-transparency;
250
+ margin: (-$input-padding-ver) (-$input-padding-hor);
251
+ margin-left: 0;
241
252
  border-left: $border-style;
242
253
 
243
254
  &,
244
255
  div {
245
- position: absolute;
246
256
  width: $color-swatch-width;
247
- top: 0;
248
- right: 0;
249
- bottom: 0;
257
+ height: calc($input-height - 2 * $border-width);
250
258
  border-top-right-radius: $color-swatch-radius;
251
259
  border-bottom-right-radius: $color-swatch-radius;
252
260
  }
@@ -12,18 +12,20 @@
12
12
  template(#prefix)
13
13
  DitoAffixes(
14
14
  :items="schema.prefix"
15
+ position="prefix"
16
+ absolute
17
+ :disabled="disabled"
15
18
  :parentContext="context"
16
19
  )
17
20
  template(#suffix)
18
21
  DitoAffixes(
19
22
  :items="schema.suffix"
20
- :parentContext="context"
21
- )
22
- button.dito-button--clear.dito-button--overlay(
23
- v-if="showClearButton"
23
+ position="suffix"
24
+ absolute
25
+ :clearable="showClearButton"
24
26
  :disabled="disabled"
25
- @click.stop="clear"
26
- @mousedown.stop
27
+ :parentContext="context"
28
+ @clear="clear"
27
29
  )
28
30
  </template>
29
31
 
@@ -1,14 +1,17 @@
1
1
  <template lang="pug">
2
2
  .dito-multiselect
3
3
  .dito-multiselect__inner
4
+ DitoAffixes(
5
+ :items="schema.prefix"
6
+ position="prefix"
7
+ absolute
8
+ :disabled="disabled"
9
+ :parentContext="context"
10
+ )
4
11
  VueMultiselect(
5
12
  ref="element"
6
13
  v-model="selectedOptions"
7
- :class=`{
8
- 'multiselect--multiple': multiple,
9
- 'multiselect--loading': isLoading,
10
- 'multiselect--highlight': showHighlight
11
- }`
14
+ :class="multiselectClasses"
12
15
  :showLabels="false"
13
16
  :placeholder="placeholder"
14
17
  tagPlaceholder="Press enter to add new tag"
@@ -31,11 +34,14 @@
31
34
  @tag="onAddTag"
32
35
  @search-change="onSearchChange"
33
36
  )
34
- button.dito-button--clear.dito-button--overlay(
35
- v-if="showClearButton"
36
- type="button"
37
+ DitoAffixes(
38
+ :items="schema.suffix"
39
+ position="suffix"
40
+ absolute
41
+ :clearable="showClearButton"
37
42
  :disabled="disabled"
38
- @click="clear"
43
+ :parentContext="context"
44
+ @clear="clear"
39
45
  )
40
46
  //- Edit button is never disabled, even if the field is disabled.
41
47
  DitoEditButtons(
@@ -56,6 +62,7 @@ import DitoTypeComponent from '../DitoTypeComponent.js'
56
62
  import DitoContext from '../DitoContext.js'
57
63
  import TypeMixin from '../mixins/TypeMixin.js'
58
64
  import OptionsMixin from '../mixins/OptionsMixin.js'
65
+ import DitoAffixes from '../components/DitoAffixes.vue'
59
66
  import VueMultiselect from 'vue-multiselect'
60
67
  import { getSchemaAccessor } from '../utils/accessor.js'
61
68
  import { isBoolean } from '@ditojs/utils'
@@ -63,7 +70,7 @@ import { isBoolean } from '@ditojs/utils'
63
70
  // @vue/component
64
71
  export default DitoTypeComponent.register('multiselect', {
65
72
  mixins: [OptionsMixin],
66
- components: { VueMultiselect },
73
+ components: { DitoAffixes, VueMultiselect },
67
74
 
68
75
  data() {
69
76
  return {
@@ -125,6 +132,15 @@ export default DitoTypeComponent.register('multiselect', {
125
132
  default: false
126
133
  }),
127
134
 
135
+ multiselectClasses() {
136
+ const prefix = 'multiselect'
137
+ return {
138
+ [`${prefix}--multiple`]: this.multiple,
139
+ [`${prefix}--loading`]: this.isLoading,
140
+ [`${prefix}--highlight`]: this.showHighlight
141
+ }
142
+ },
143
+
128
144
  placeholder() {
129
145
  let { placeholder, searchable, taggable } = this.schema
130
146
  if (isBoolean(placeholder)) {
@@ -239,6 +255,8 @@ $tag-line-height: 1em;
239
255
  &__inner {
240
256
  flex: 1;
241
257
  position: relative;
258
+ display: flex;
259
+ align-items: center;
242
260
  }
243
261
 
244
262
  .dito-edit-buttons {
@@ -12,17 +12,18 @@ DitoInput.dito-number(
12
12
  template(#prefix)
13
13
  DitoAffixes(
14
14
  :items="schema.prefix"
15
+ position="prefix"
16
+ :disabled="disabled"
15
17
  :parentContext="context"
16
18
  )
17
19
  template(#suffix)
18
20
  DitoAffixes(
19
21
  :items="schema.suffix"
20
- :parentContext="context"
21
- )
22
- button.dito-button--clear.dito-button--overlay(
23
- v-if="showClearButton"
22
+ position="suffix"
23
+ :clearable="showClearButton"
24
24
  :disabled="disabled"
25
- @click.stop="clear"
25
+ :parentContext="context"
26
+ @clear="clear"
26
27
  )
27
28
  </template>
28
29
 
@@ -2,6 +2,13 @@
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"
7
+ position="prefix"
8
+ absolute
9
+ :disabled="disabled"
10
+ :parentContext="context"
11
+ )
5
12
  select(
6
13
  :id="dataPath"
7
14
  ref="element"
@@ -32,10 +39,14 @@
32
39
  v-else-if="selectedOption"
33
40
  )
34
41
  option(:value="selectedValue") {{ getLabelForOption(selectedOption) }}
35
- button.dito-button--clear.dito-button--overlay(
36
- v-if="showClearButton"
42
+ DitoAffixes(
43
+ :items="schema.suffix"
44
+ position="suffix"
45
+ absolute
46
+ :clearable="showClearButton"
37
47
  :disabled="disabled"
38
- @click="clear"
48
+ :parentContext="context"
49
+ @clear="clear"
39
50
  )
40
51
  //- Edit button is never disabled, even if the field is disabled.
41
52
  DitoEditButtons(
@@ -54,10 +65,12 @@
54
65
  <script>
55
66
  import DitoTypeComponent from '../DitoTypeComponent.js'
56
67
  import OptionsMixin from '../mixins/OptionsMixin.js'
68
+ import DitoAffixes from '../components/DitoAffixes.vue'
57
69
 
58
70
  // @vue/component
59
71
  export default DitoTypeComponent.register('select', {
60
72
  mixins: [OptionsMixin],
73
+ components: { DitoAffixes },
61
74
 
62
75
  nativeField: true,
63
76
 
@@ -9,17 +9,18 @@ DitoInput.dito-text(
9
9
  template(#prefix)
10
10
  DitoAffixes(
11
11
  :items="schema.prefix"
12
+ position="prefix"
13
+ :disabled="disabled"
12
14
  :parentContext="context"
13
15
  )
14
16
  template(#suffix)
15
17
  DitoAffixes(
16
18
  :items="schema.suffix"
17
- :parentContext="context"
18
- )
19
- button.dito-button--clear.dito-button--overlay(
20
- v-if="showClearButton"
19
+ position="suffix"
20
+ :clearable="showClearButton"
21
21
  :disabled="disabled"
22
- @click.stop="clear"
22
+ :parentContext="context"
23
+ @clear="clear"
23
24
  )
24
25
  </template>
25
26