@ditojs/admin 2.2.16 → 2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.2.16",
3
+ "version": "2.3.1",
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",
@@ -33,8 +33,8 @@
33
33
  "not ie_mob > 0"
34
34
  ],
35
35
  "dependencies": {
36
- "@ditojs/ui": "^2.2.14",
37
- "@ditojs/utils": "^2.2.0",
36
+ "@ditojs/ui": "^2.3.1",
37
+ "@ditojs/utils": "^2.3.0",
38
38
  "@kyvg/vue3-notification": "^2.9.0",
39
39
  "@lk77/vue3-color": "^3.0.6",
40
40
  "@tiptap/core": "^2.0.3",
@@ -74,7 +74,7 @@
74
74
  "vue-upload-component": "^3.1.8"
75
75
  },
76
76
  "devDependencies": {
77
- "@ditojs/build": "^2.2.3",
77
+ "@ditojs/build": "^2.3.0",
78
78
  "@vitejs/plugin-vue": "^4.1.0",
79
79
  "@vue/compiler-sfc": "^3.2.47",
80
80
  "pug": "^3.0.2",
@@ -83,7 +83,7 @@
83
83
  "vite": "^4.3.1"
84
84
  },
85
85
  "types": "types",
86
- "gitHead": "722b79d58335e249802d250fca9d250bad511b8c",
86
+ "gitHead": "1b5f5121a2e8e6d3741f0bb2b81d38c2343810d6",
87
87
  "scripts": {
88
88
  "build": "vite build",
89
89
  "watch": "yarn build --mode 'development' --watch",
@@ -131,6 +131,32 @@ export default DitoComponent.component('DitoContainer', {
131
131
  }
132
132
  }),
133
133
 
134
+ flexGrow() {
135
+ // Interpret '>50%' as '50%, flex-grow: 1`
136
+ return (
137
+ this.widthOperator === '>' ||
138
+ this.width === 'fill'
139
+ )
140
+ },
141
+
142
+ flexShrink() {
143
+ // Interpret '<50%' as '50%, flex-shrink: 1`
144
+ return this.widthOperator === '<'
145
+ },
146
+
147
+ flexBasis() {
148
+ const width = this.width
149
+ // 'auto' = no fitting:
150
+ const basis = [null, 'auto', 'fill'].includes(width)
151
+ ? 'auto'
152
+ : /%$/.test(width)
153
+ ? parseFloat(width) // percentage
154
+ : /[a-z]/.test(width)
155
+ ? width // native units
156
+ : parseFraction(width) * 100 // fraction
157
+ return isNumber(basis) ? `${basis}%` : basis
158
+ },
159
+
134
160
  containerClass() {
135
161
  const { class: containerClass } = this.schema
136
162
  const prefix = 'dito-container'
@@ -150,39 +176,27 @@ export default DitoComponent.component('DitoContainer', {
150
176
  }
151
177
  },
152
178
 
153
- componentBasis() {
154
- const width = this.width
155
- // 'auto' = no fitting:
156
- const basis = [null, 'auto', 'fill'].includes(width)
157
- ? 'auto'
158
- : /%$/.test(width)
159
- ? parseFloat(width) // percentage
160
- : /[a-z]/.test(width)
161
- ? width // native units
162
- : parseFraction(width) * 100 // fraction
163
- return isNumber(basis) ? `${basis}%` : basis
164
- },
165
-
166
179
  containerStyle() {
167
- // Interpret '>50%' as '50%, flex-grow: 1`
168
- const grow = (
169
- this.widthOperator === '>' ||
170
- this.width === 'fill'
171
- )
172
- // Interpret '<50%' as '50%, flex-shrink: 1`
173
- const shrink = this.widthOperator === '<'
174
180
  return {
175
- flex: `${grow ? 1 : 0} ${shrink ? 1 : 0} ${this.componentBasis}`
181
+ flex: `${
182
+ this.flexGrow ? 1 : 0
183
+ } ${
184
+ this.flexShrink ? 1 : 0
185
+ } ${
186
+ this.flexBasis
187
+ }`
176
188
  }
177
189
  },
178
190
 
179
191
  componentClass() {
180
- const basisIsAuto = this.componentBasis === 'auto'
192
+ const basisIsAuto = this.flexBasis === 'auto'
181
193
  return {
182
194
  // TODO: BEM?
183
195
  'dito-single': this.single,
184
196
  'dito-disabled': this.componentDisabled,
185
197
  'dito-width-fill': !basisIsAuto || this.width === 'fill',
198
+ 'dito-width-grow': this.flexGrow,
199
+ 'dito-width-shrink': this.flexShrink,
186
200
  'dito-has-errors': !!this.errors
187
201
  }
188
202
  }
@@ -10,23 +10,25 @@ DitoButtons.dito-edit-buttons.dito-buttons-round(
10
10
  )
11
11
  //- Firefox doesn't like <button> here, so use <a> instead:
12
12
  a.dito-button(
13
- v-if="isDraggable"
13
+ v-if="hasDraggable"
14
14
  v-bind="getButtonAttributes(verbs.drag)"
15
15
  )
16
16
  RouterLink.dito-button(
17
- v-if="isEditable"
18
- :to="{ path: editPath }"
17
+ v-if="hasEditable"
18
+ :class="{ 'dito-disabled': !editPath }"
19
+ :to="editPath ? { path: editPath } : {}"
19
20
  v-bind="getButtonAttributes(verbs.edit)"
20
21
  )
21
22
  DitoCreateButton(
22
- v-if="isCreatable"
23
+ v-if="hasCreatable"
24
+ :class="{ 'dito-disabled': !createPath }"
23
25
  :schema="schema"
24
26
  :path="createPath"
25
27
  :verb="verbs.create"
26
28
  :text="createButtonText"
27
29
  )
28
30
  button.dito-button(
29
- v-if="isDeletable"
31
+ v-if="hasDeletable"
30
32
  type="button"
31
33
  v-bind="getButtonAttributes(verbs.delete)"
32
34
  @click="$emit('delete')"
@@ -61,19 +63,19 @@ export default DitoComponent.component('DitoEditButtons', {
61
63
  return this.getLabel(this.schema.form)
62
64
  },
63
65
 
64
- isDraggable() {
66
+ hasDraggable() {
65
67
  return this.hasOption('draggable')
66
68
  },
67
69
 
68
- isEditable() {
69
- return this.hasOption('editable') && !!this.editPath
70
+ hasEditable() {
71
+ return this.hasOption('editable')
70
72
  },
71
73
 
72
- isCreatable() {
73
- return this.hasOption('creatable') && !!this.createPath
74
+ hasCreatable() {
75
+ return this.hasOption('creatable')
74
76
  },
75
77
 
76
- isDeletable() {
78
+ hasDeletable() {
77
79
  return this.hasOption('deletable')
78
80
  },
79
81
 
@@ -289,8 +289,9 @@ export default DitoComponent.component('DitoForm', {
289
289
  if (
290
290
  param &&
291
291
  this.providesData &&
292
- from.params[param] !== 'create' &&
293
- to.params[param] !== from.params[param]
292
+ from.matched[0].path === to.matched[0].path && // Staying on same form?
293
+ from.params[param] !== 'create' && // But haven't been creating?
294
+ to.params[param] !== from.params[param] // Going to a different entity?
294
295
  ) {
295
296
  this.loadData(true)
296
297
  }
@@ -28,8 +28,7 @@ component.dito-label(
28
28
  )
29
29
  .dito-info(
30
30
  v-if="info"
31
- :data-tippy-content="info"
32
- data-tippy-theme="info"
31
+ :data-info="info"
33
32
  )
34
33
  slot(name="edit-buttons")
35
34
  </template>
@@ -118,8 +117,11 @@ export default DitoComponent.component('DitoLabel', {
118
117
  .dito-label-suffix {
119
118
  @include user-select(none);
120
119
  @include ellipsis;
120
+ }
121
121
 
122
- &::after {
122
+ .dito-label-prefix + label,
123
+ label + .dito-label-suffix {
124
+ &::before {
123
125
  content: '\a0'; // &nbsp;
124
126
  }
125
127
  }
@@ -224,10 +224,10 @@ export default DitoComponent.component('DitoPanel', {
224
224
  > .dito-schema-content {
225
225
  > .dito-pane {
226
226
  padding: $form-spacing-half $form-spacing;
227
- }
228
227
 
229
- .dito-container {
230
- padding: $form-spacing-half;
228
+ > .dito-container {
229
+ padding: $form-spacing-half;
230
+ }
231
231
  }
232
232
 
233
233
  .dito-object {
@@ -92,7 +92,16 @@ export default DitoComponent.component('DitoRoot', {
92
92
 
93
93
  async mounted() {
94
94
  tippyDelegate(this.$el, {
95
- target: '[data-tippy-content]'
95
+ target: '.dito-info',
96
+ onCreate(instance) {
97
+ instance.setContent(instance.reference.dataset.info)
98
+ instance.setProps({
99
+ theme: 'info',
100
+ interactive: true,
101
+ animation: 'shift-away-subtle',
102
+ delay: 250
103
+ })
104
+ }
96
105
  })
97
106
  // Clear the label marked as active on all mouse and keyboard events, except
98
107
  // the ones that DitoLabel itself intercepts.
@@ -1,5 +1,6 @@
1
1
  import DitoContext from '../DitoContext.js'
2
2
  import DataMixin from './DataMixin.js'
3
+ import { hasViewSchema, getViewEditPath } from '../utils/schema.js'
3
4
  import { getSchemaAccessor } from '../utils/accessor.js'
4
5
  import { setTemporaryId, isReference } from '../utils/data.js'
5
6
  import {
@@ -145,6 +146,23 @@ export default {
145
146
  }
146
147
  }),
147
148
 
149
+ editable: getSchemaAccessor('editable', {
150
+ type: Boolean,
151
+ default: false,
152
+ get(editable) {
153
+ return (
154
+ editable &&
155
+ hasViewSchema(this.schema, this.context)
156
+ )
157
+ }
158
+ }),
159
+
160
+ editPath() {
161
+ return this.editable && this.selectedValue
162
+ ? getViewEditPath(this.schema, this.selectedValue, this.context)
163
+ : null
164
+ },
165
+
148
166
  groupByLabel() {
149
167
  return this.groupBy ? 'label' : null
150
168
  },
@@ -227,7 +245,7 @@ export default {
227
245
  return isString(optionValue)
228
246
  ? option?.[optionValue]
229
247
  : isFunction(optionValue)
230
- ? optionValue.call(this, new DitoContext(this, { option }))
248
+ ? optionValue(new DitoContext(this, { option }))
231
249
  : option
232
250
  },
233
251
 
@@ -236,7 +254,7 @@ export default {
236
254
  return isString(optionLabel)
237
255
  ? option?.[optionLabel]
238
256
  : isFunction(optionLabel)
239
- ? optionLabel.call(this, new DitoContext(this, { option }))
257
+ ? optionLabel(new DitoContext(this, { option }))
240
258
  : labelize(`${option}`)
241
259
  }
242
260
  },
@@ -55,19 +55,15 @@ export default {
55
55
  { component: this }
56
56
  )
57
57
  const { format } = this.schema
58
- // `schema.format` is only ever called in the life-cycle
59
- // of the component and thus it's ok to bind it to `this`
60
58
  return format
61
- ? format.call(this, new DitoContext(this, { value }))
59
+ ? format(new DitoContext(this, { value }))
62
60
  : value
63
61
  },
64
62
 
65
63
  set(value) {
66
64
  const { parse } = this.schema
67
- // `schema.parse` is only ever called in the life-cycle
68
- // of the component and thus it's ok to bind it to `this`
69
65
  this.parsedValue = parse
70
- ? parse.call(this, new DitoContext(this, { value }))
66
+ ? parse(new DitoContext(this, { value }))
71
67
  : value
72
68
  // eslint-disable-next-line vue/no-mutating-props
73
69
  this.data[this.name] = this.parsedValue
@@ -191,18 +191,18 @@
191
191
  cursor: pointer;
192
192
 
193
193
  &.dito-button-clear {
194
+ $self: &;
195
+
194
196
  width: $input-height;
195
197
  z-index: 1;
196
198
  background: $color-white;
197
199
  display: none;
198
200
 
199
- $button-clear: &;
200
-
201
201
  @at-root .dito-component:hover {
202
202
  // Support two levels of nesting inside .dito-component, used by TypeColor
203
- & > #{$button-clear},
204
- & > * > #{$button-clear},
205
- & > * > * > #{$button-clear} {
203
+ & > #{$self},
204
+ & > * > #{$self},
205
+ & > * > * > #{$self} {
206
206
  display: block;
207
207
  }
208
208
  }
@@ -1,3 +1,5 @@
1
+ @import 'tippy.js/animations/shift-away-subtle.css';
2
+
1
3
  .dito-info {
2
4
  --size: calc(1em * var(--line-height));
3
5
 
@@ -21,6 +23,7 @@
21
23
  .tippy-box[data-theme~='info'] {
22
24
  background-color: $color-active;
23
25
  color: $color-white;
26
+ filter: drop-shadow(0 2px 4px $color-shadow);
24
27
 
25
28
  > .tippy-arrow::before {
26
29
  color: $color-active;
@@ -14,8 +14,7 @@ button.dito-button(
14
14
  span {{ text }}
15
15
  .dito-info(
16
16
  v-if="!label && info"
17
- :data-tippy-content="info"
18
- data-tippy-theme="info"
17
+ :data-info="info"
19
18
  )
20
19
  template(
21
20
  v-else
@@ -97,19 +96,22 @@ export default DitoTypeComponent.register(
97
96
  @import '../styles/_imports';
98
97
 
99
98
  .dito-button {
100
- display: flex;
99
+ $self: &;
101
100
 
102
101
  &__text {
103
102
  position: relative;
104
- width: 100%;
105
103
  min-width: min-content;
106
104
  height: calc(1em * var(--line-height));
107
105
 
108
106
  span {
109
107
  @include ellipsis;
110
108
 
111
- position: absolute;
112
- inset: 0;
109
+ #{$self}:not(.dito-width-grow) & {
110
+ // Use `position: absolute` to prevent the text from growing over the
111
+ // button's width, which would cause the button to grow as well.
112
+ position: absolute;
113
+ inset: 0;
114
+ }
113
115
  }
114
116
  }
115
117
  }
@@ -251,9 +251,11 @@ export default DitoTypeComponent.register('list', {
251
251
 
252
252
  getEditPath(item, index) {
253
253
  if (this.editable) {
254
- const path = getViewEditPath(this.schema, this.context) || this.path
255
254
  const id = this.getItemId(this.schema, item, index)
256
- return `${path}/${id}`
255
+ return (
256
+ getViewEditPath(this.schema, id, this.context) ||
257
+ `${this.path}/${id}`
258
+ )
257
259
  }
258
260
  return null
259
261
  },
@@ -5,37 +5,48 @@
5
5
  'dito-multiselect-multiple': multiple
6
6
  }`
7
7
  )
8
- VueMultiselect(
9
- ref="element"
10
- v-model="selectedOptions"
11
- :class="{ 'multiselect--show-highlight': showHighlight }"
12
- :showLabels="false"
13
- :placeholder="placeholder"
14
- tagPlaceholder="Press enter to add new tag"
15
- :options="populate && activeOptions || []"
16
- :customLabel="getLabelForOption"
17
- :trackBy="optionValue"
18
- :groupLabel="groupByLabel"
19
- :groupValues="groupByOptions"
20
- :multiple="multiple"
21
- :taggable="taggable"
22
- :searchable="searchable"
23
- :internalSearch="!searchFilter"
24
- :preserveSearch="!!searchFilter"
25
- :clearOnSelect="!searchFilter"
26
- :closeOnSelect="!stayOpen"
27
- :loading="isLoading"
28
- v-bind="attributes"
29
- @open="onOpen"
30
- @close="onClose"
31
- @tag="onAddTag"
32
- @search-change="onSearchChange"
33
- )
34
- button.dito-button-clear.dito-button-overlay(
35
- v-if="showClearButton"
36
- type="button"
37
- :disabled="disabled"
38
- @click="clear"
8
+ .dito-multiselect__inner
9
+ VueMultiselect(
10
+ ref="element"
11
+ v-model="selectedOptions"
12
+ :class="{ 'multiselect--show-highlight': showHighlight }"
13
+ :showLabels="false"
14
+ :placeholder="placeholder"
15
+ tagPlaceholder="Press enter to add new tag"
16
+ :options="populate && activeOptions || []"
17
+ :customLabel="getLabelForOption"
18
+ :trackBy="optionValue"
19
+ :groupLabel="groupByLabel"
20
+ :groupValues="groupByOptions"
21
+ :multiple="multiple"
22
+ :taggable="taggable"
23
+ :searchable="searchable"
24
+ :internalSearch="!searchFilter"
25
+ :preserveSearch="!!searchFilter"
26
+ :clearOnSelect="!searchFilter"
27
+ :closeOnSelect="!stayOpen"
28
+ :loading="isLoading"
29
+ v-bind="attributes"
30
+ @open="onOpen"
31
+ @close="onClose"
32
+ @tag="onAddTag"
33
+ @search-change="onSearchChange"
34
+ )
35
+ button.dito-button-clear.dito-button-overlay(
36
+ v-if="showClearButton"
37
+ type="button"
38
+ :disabled="disabled"
39
+ @click="clear"
40
+ )
41
+ DitoEditButtons(
42
+ v-if="editable"
43
+ :editable="editable"
44
+ :editPath="editPath"
45
+ :schema="schema"
46
+ :dataPath="dataPath"
47
+ :data="data"
48
+ :meta="meta"
49
+ :store="store"
39
50
  )
40
51
  </template>
41
52
 
@@ -218,12 +229,20 @@ $tag-padding: 3px;
218
229
  $tag-line-height: 1em;
219
230
 
220
231
  .dito-multiselect {
232
+ display: inline-flex;
221
233
  position: relative;
222
234
 
235
+ &__inner {
236
+ flex: 1;
237
+ position: relative;
238
+ }
239
+
240
+ // TODO: BEM
223
241
  &.dito-multiselect-single {
224
242
  --input-width: 100%;
225
243
  }
226
244
 
245
+ // TODO: BEM
227
246
  &.dito-multiselect-multiple {
228
247
  --input-width: auto;
229
248
  }
@@ -234,6 +253,10 @@ $tag-line-height: 1em;
234
253
  }
235
254
  }
236
255
 
256
+ .dito-edit-buttons {
257
+ margin-left: $form-spacing-half;
258
+ }
259
+
237
260
  .dito-button-clear {
238
261
  width: $spinner-width;
239
262
  }
@@ -1,40 +1,51 @@
1
1
  <template lang="pug">
2
2
  //- Nesting is needed to make an arrow appear over the select item:
3
3
  .dito-select
4
- select(
5
- :id="dataPath"
6
- ref="element"
7
- v-model="selectedValue"
8
- v-bind="attributes"
9
- @mousedown="populate = true"
10
- @focus="populate = true"
11
- )
12
- template(
13
- v-if="populate"
4
+ .dito-select__inner
5
+ select(
6
+ :id="dataPath"
7
+ ref="element"
8
+ v-model="selectedValue"
9
+ v-bind="attributes"
10
+ @mousedown="populate = true"
11
+ @focus="populate = true"
14
12
  )
15
13
  template(
16
- v-for="option in options"
14
+ v-if="populate"
17
15
  )
18
- optgroup(
19
- v-if="groupBy"
20
- :label="option[groupByLabel]"
16
+ template(
17
+ v-for="option in options"
21
18
  )
19
+ optgroup(
20
+ v-if="groupBy"
21
+ :label="option[groupByLabel]"
22
+ )
23
+ option(
24
+ v-for="opt in option[groupByOptions]"
25
+ :value="getValueForOption(opt)"
26
+ ) {{ getLabelForOption(opt) }}
22
27
  option(
23
- v-for="opt in option[groupByOptions]"
24
- :value="getValueForOption(opt)"
25
- ) {{ getLabelForOption(opt) }}
26
- option(
27
- v-else
28
- :value="getValueForOption(option)"
29
- ) {{ getLabelForOption(option) }}
30
- template(
31
- v-else-if="selectedOption"
28
+ v-else
29
+ :value="getValueForOption(option)"
30
+ ) {{ getLabelForOption(option) }}
31
+ template(
32
+ v-else-if="selectedOption"
33
+ )
34
+ option(:value="selectedValue") {{ getLabelForOption(selectedOption) }}
35
+ button.dito-button-clear.dito-button-overlay(
36
+ v-if="showClearButton"
37
+ :disabled="disabled"
38
+ @click="clear"
32
39
  )
33
- option(:value="selectedValue") {{ getLabelForOption(selectedOption) }}
34
- button.dito-button-clear.dito-button-overlay(
35
- v-if="showClearButton"
36
- :disabled="disabled"
37
- @click="clear"
40
+ DitoEditButtons(
41
+ v-if="editable"
42
+ :editable="editable"
43
+ :editPath="editPath"
44
+ :schema="schema"
45
+ :dataPath="dataPath"
46
+ :data="data"
47
+ :meta="meta"
48
+ :store="store"
38
49
  )
39
50
  </template>
40
51
 
@@ -65,12 +76,30 @@ export default DitoTypeComponent.register('select', {
65
76
  $select-arrow-right: calc(($select-arrow-width - $select-arrow-size) / 2);
66
77
 
67
78
  .dito-select {
68
- display: inline-block;
79
+ display: inline-flex;
69
80
  position: relative;
70
81
 
71
82
  select {
72
83
  padding-right: $select-arrow-width;
73
84
  }
85
+
86
+ &__inner {
87
+ flex: 1;
88
+ position: relative;
89
+
90
+ &::after {
91
+ position: absolute;
92
+ @include arrow($select-arrow-size);
93
+
94
+ bottom: $select-arrow-bottom;
95
+ right: calc(#{$select-arrow-right} + #{$border-width});
96
+ }
97
+ }
98
+
99
+ .dito-edit-buttons {
100
+ margin-left: $form-spacing-half;
101
+ }
102
+
74
103
  // Handle .dito-width-fill separately due to required nesting of select:
75
104
  &.dito-width-fill {
76
105
  select {
@@ -78,14 +107,6 @@ $select-arrow-right: calc(($select-arrow-width - $select-arrow-size) / 2);
78
107
  }
79
108
  }
80
109
 
81
- &::after {
82
- position: absolute;
83
- @include arrow($select-arrow-size);
84
-
85
- bottom: $select-arrow-bottom;
86
- right: calc(#{$select-arrow-right} + #{$border-width});
87
- }
88
-
89
110
  &.dito-disabled::after {
90
111
  border-color: $border-color;
91
112
  }