@ditojs/admin 2.8.0 → 2.8.2

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.8.0",
3
+ "version": "2.8.2",
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",
@@ -83,7 +83,7 @@
83
83
  "vite": "^4.3.5"
84
84
  },
85
85
  "types": "types",
86
- "gitHead": "6f589b645e8841b1875330d9918a7d952af49b31",
86
+ "gitHead": "d9a0905b7c93d509a3710b36c749cc6e651401d3",
87
87
  "scripts": {
88
88
  "build": "vite build",
89
89
  "watch": "yarn build --mode 'development' --watch",
@@ -73,6 +73,10 @@ export default DitoComponent.component('DitoClipboard', {
73
73
  }
74
74
  },
75
75
 
76
+ watch: {
77
+ 'appState.clipboardData': 'checkClipboard'
78
+ },
79
+
76
80
  mounted() {
77
81
  // Check clipboard content whenever something gets copied or the window gets
78
82
  // (re)activated, as those are the moments when the clipboard can change:
@@ -40,6 +40,15 @@ export default DitoComponent.component('DitoCreateButton', {
40
40
 
41
41
  props: {
42
42
  schema: { type: Object, required: true },
43
+ // The next four props are there for `DitoContext` and the `context()`
44
+ // getter in `DitoMixin`.
45
+ // TODO: Should they be moved to shared mixin that defines them as required
46
+ // and also provides the `context()` getter, perhaps `ContextMixin`?
47
+ // `schema` could be included as well, and `ContextMixin` could be used in
48
+ // `DitoForm`, `DitoView`, `DitoPanel`, `DitoSchema`, `DitoEditButtons`,
49
+ // etc? But the problem with the root components is that they don't have
50
+ // these props. We could add a `contextAttributes()` getter for easy passing
51
+ // on as `v-bind="contextAttributes"`.
43
52
  dataPath: { type: String, required: true },
44
53
  data: { type: [Object, Array], default: null },
45
54
  meta: { type: Object, required: true },
@@ -72,7 +81,14 @@ export default DitoComponent.component('DitoCreateButton', {
72
81
  isFormCreatable(form) {
73
82
  // Forms can be excluded from the list by providing `if: false` or
74
83
  // `creatable: false`.
75
- return form.creatable !== false && this.shouldRenderSchema(form)
84
+ return (
85
+ this.shouldRenderSchema(form) &&
86
+ this.getSchemaValue('creatable', {
87
+ type: Boolean,
88
+ default: true,
89
+ schema: form
90
+ })
91
+ )
76
92
  },
77
93
 
78
94
  createItem(form, type = null) {
@@ -1,9 +1,10 @@
1
1
  <template lang="pug">
2
- UseSortable(
2
+ UseSortable.dito-draggable(
3
3
  v-if="draggable"
4
+ :class="{ 'dito-draggable--dragging': isDragging }"
4
5
  :tag="tag"
5
6
  :modelValue="modelValue"
6
- :options="options"
7
+ :options="{ ...options, onStart, onEnd }"
7
8
  @update:modelValue="$emit('update:modelValue', $event)"
8
9
  )
9
10
  slot
@@ -15,6 +16,7 @@ component(
15
16
  </template>
16
17
 
17
18
  <script>
19
+ import { addEvents } from '@ditojs/ui'
18
20
  import DitoComponent from '../DitoComponent'
19
21
  import { UseSortable } from '@vueuse/integrations/useSortable/component'
20
22
 
@@ -40,6 +42,73 @@ export default DitoComponent.component('DitoDraggable', {
40
42
  type: Boolean,
41
43
  default: true
42
44
  }
45
+ },
46
+
47
+ data() {
48
+ return {
49
+ mouseEvents: null,
50
+ isDragging: false
51
+ }
52
+ },
53
+
54
+ methods: {
55
+ onStart(event) {
56
+ this.isDragging = true
57
+ this.options.onStart?.(event)
58
+ this.mouseEvents?.remove()
59
+ },
60
+
61
+ onEnd(event) {
62
+ this.options.onEnd?.(event)
63
+ // Keep `isDragging` true until the next mouse interaction so that
64
+ // confused hover states are cleared before removing the hover catcher.
65
+ this.mouseEvents = addEvents(this.$el, {
66
+ mousedown: this.onMouse,
67
+ mousemove: this.onMouse,
68
+ mouseleave: this.onMouse
69
+ })
70
+ },
71
+
72
+ onMouse() {
73
+ this.isDragging = false
74
+ this.mouseEvents.remove()
75
+ this.mouseEvents = null
76
+ }
43
77
  }
44
78
  })
45
79
  </script>
80
+
81
+ <style lang="scss">
82
+ @import '../styles/_imports';
83
+
84
+ .dito-draggable {
85
+ // Overlay a hover catcher while we're dragging to prevent hover states from
86
+ // getting stuck / confused.
87
+ &:has(&__chosen),
88
+ &--dragging {
89
+ > * {
90
+ position: relative;
91
+
92
+ > :first-child::after {
93
+ content: '';
94
+ position: absolute;
95
+ inset: 0;
96
+ }
97
+ }
98
+ }
99
+
100
+ &__fallback {
101
+ filter: drop-shadow(0 2px 4px $color-shadow);
102
+
103
+ // Nested <td> need to also switch to `display: flex` style during dragging.
104
+ &,
105
+ td {
106
+ display: flex;
107
+
108
+ > * {
109
+ flex: 1;
110
+ }
111
+ }
112
+ }
113
+ }
114
+ </style>
@@ -10,18 +10,18 @@ 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="hasDraggable"
14
- :class="{ 'dito-disabled': disabled }"
13
+ v-if="draggable"
14
+ :class="{ 'dito-disabled': isDraggableDisabled }"
15
15
  v-bind="getButtonAttributes(verbs.drag)"
16
16
  )
17
17
  RouterLink.dito-button(
18
- v-if="hasEditable"
19
- :class="{ 'dito-disabled': disabled || !editPath }"
18
+ v-if="editable"
19
+ :class="{ 'dito-disabled': isEditableDisabled }"
20
20
  :to="editPath ? { path: editPath } : {}"
21
21
  v-bind="getButtonAttributes(verbs.edit)"
22
22
  )
23
23
  DitoCreateButton(
24
- v-if="hasCreatable"
24
+ v-if="creatable"
25
25
  :schema="schema"
26
26
  :dataPath="dataPath"
27
27
  :data="data"
@@ -30,12 +30,12 @@ DitoButtons.dito-edit-buttons.dito-buttons-round(
30
30
  :path="createPath"
31
31
  :verb="verbs.create"
32
32
  :text="createButtonText"
33
- :disabled="disabled || !createPath"
33
+ :disabled="isCreatableDisabled"
34
34
  )
35
35
  button.dito-button(
36
- v-if="hasDeletable"
36
+ v-if="deletable"
37
37
  type="button"
38
- :disabled="disabled || !isFormDeletable"
38
+ :disabled="isDeletableDisabled"
39
39
  v-bind="getButtonAttributes(verbs.delete)"
40
40
  @click="$emit('delete')"
41
41
  )
@@ -70,24 +70,28 @@ export default DitoComponent.component('DitoEditButtons', {
70
70
  return this.getLabel(this.schema.form)
71
71
  },
72
72
 
73
- hasDraggable() {
74
- return this.hasOption('draggable')
73
+ isDraggableDisabled() {
74
+ return this.disabled || !this.hasSchemaOption('draggable')
75
75
  },
76
76
 
77
- hasEditable() {
78
- return this.hasOption('editable')
77
+ isDeletableDisabled() {
78
+ return this.disabled || !this.hasSchemaOption('deletable')
79
79
  },
80
80
 
81
- hasCreatable() {
82
- return this.hasOption('creatable')
83
- },
84
-
85
- hasDeletable() {
86
- return this.hasOption('deletable')
81
+ isEditableDisabled() {
82
+ return (
83
+ this.disabled ||
84
+ !this.editPath ||
85
+ !this.hasSchemaOption('editable')
86
+ )
87
87
  },
88
88
 
89
- isFormDeletable() {
90
- return this.schema.deletable !== false
89
+ isCreatableDisabled() {
90
+ return (
91
+ this.disabled ||
92
+ !this.createPath ||
93
+ !this.hasSchemaOption('creatable')
94
+ )
91
95
  },
92
96
 
93
97
  createButtonText() {
@@ -105,11 +109,13 @@ export default DitoComponent.component('DitoEditButtons', {
105
109
  },
106
110
 
107
111
  methods: {
108
- hasOption(name) {
109
- // The options of the outer component are passed to the buttons component
110
- // through properties `this[name]`, but can be disabled on a per-form
111
- // basis by setting `schema[name]` to `false`.
112
- return !!(this[name] && this.schema[name] !== false)
112
+ hasSchemaOption(name) {
113
+ // All options can be disabled on a per-form basis by setting
114
+ // `schema[name]` to `false` or a callback returning `false`.
115
+ return this.getSchemaValue(name, {
116
+ type: Boolean,
117
+ default: true
118
+ })
113
119
  }
114
120
  }
115
121
  })
@@ -26,14 +26,14 @@ export default DitoComponent.component('DitoElement', {
26
26
 
27
27
  classes() {
28
28
  return {
29
- ...this.$attrs.class,
29
+ ...asObject(this.$attrs.class),
30
30
  ...asObject(this.options.class)
31
31
  }
32
32
  },
33
33
 
34
34
  styles() {
35
35
  return {
36
- ...this.$attrs.style,
36
+ ...asObject(this.$attrs.style),
37
37
  ...asObject(this.options.style)
38
38
  }
39
39
  }
@@ -123,8 +123,6 @@ export default DitoComponent.component('DitoLabel', {
123
123
  label {
124
124
  cursor: inherit;
125
125
  font-weight: bold;
126
- white-space: nowrap;
127
- line-height: $input-height;
128
126
  }
129
127
 
130
128
  label,
@@ -132,6 +130,9 @@ export default DitoComponent.component('DitoLabel', {
132
130
  &__suffix {
133
131
  @include user-select(none);
134
132
  @include ellipsis;
133
+
134
+ white-space: nowrap;
135
+ line-height: $input-height;
135
136
  }
136
137
 
137
138
  &__prefix + label,
@@ -219,6 +219,7 @@ export default DitoComponent.component('DitoPane', {
219
219
 
220
220
  .dito-pane {
221
221
  $self: &;
222
+ $root-padding: $content-padding - $form-spacing-half;
222
223
 
223
224
  display: flex;
224
225
  position: relative;
@@ -240,7 +241,7 @@ export default DitoComponent.component('DitoPane', {
240
241
  margin: 0;
241
242
  // Move the negative margin used to remove the padding added by
242
243
  // `.dito-container` inside `.dito-pane` to the padding:
243
- padding: $content-padding - $form-spacing-half;
244
+ padding: $root-padding;
244
245
 
245
246
  &#{$self}--single {
246
247
  padding: $content-padding;
@@ -260,9 +261,9 @@ export default DitoComponent.component('DitoPane', {
260
261
  content: '';
261
262
  width: 100%;
262
263
  border-bottom: $border-style;
263
- // Add removed $form-spacing-half again to the ruler
264
- margin: (-$content-padding + $form-spacing-half) $form-spacing-half
265
- $form-spacing-half;
264
+ // Shift ruler up by $root-padding to exclude removed $form-spacing-half.
265
+ margin: (-$root-padding) $form-spacing-half $root-padding;
266
+ padding: $form-spacing-half;
266
267
  }
267
268
  }
268
269
 
@@ -273,6 +274,9 @@ export default DitoComponent.component('DitoPane', {
273
274
  }
274
275
 
275
276
  .dito-break {
277
+ // `.dito-break` is rendered as <span> so we can use the
278
+ // `.dito-container:first-of-type` selector to match the first container
279
+ // even if it has a break before it.
276
280
  display: block;
277
281
  flex: 100%;
278
282
  height: 0;
@@ -53,22 +53,24 @@ slot(name="before")
53
53
  template(
54
54
  v-if="hasTabs"
55
55
  )
56
- DitoPane.dito-pane__tab(
56
+ template(
57
57
  v-for="(tabSchema, tab) in tabs"
58
- v-show="selectedTab === tab"
59
- ref="tabs"
60
- :key="tab"
61
- :tab="tab"
62
- :schema="tabSchema"
63
- :dataPath="dataPath"
64
- :data="data"
65
- :meta="meta"
66
- :store="store"
67
- :single="!inlined && !hasMainPane"
68
- :disabled="disabled"
69
- :generateLabels="generateLabels"
70
- :accumulatedBasis="accumulatedBasis"
71
58
  )
59
+ DitoPane.dito-pane__tab(
60
+ v-if="selectedTab === tab"
61
+ ref="tabs"
62
+ :key="tab"
63
+ :tab="tab"
64
+ :schema="tabSchema"
65
+ :dataPath="dataPath"
66
+ :data="data"
67
+ :meta="meta"
68
+ :store="store"
69
+ :single="!inlined && !hasMainPane"
70
+ :disabled="disabled"
71
+ :generateLabels="generateLabels"
72
+ :accumulatedBasis="accumulatedBasis"
73
+ )
72
74
  DitoPane.dito-pane__main(
73
75
  v-if="hasMainPane"
74
76
  ref="components"
@@ -212,7 +214,7 @@ export default DitoComponent.component('DitoSchema', {
212
214
  },
213
215
 
214
216
  clipboard() {
215
- return this.schema?.clipboard
217
+ return this.schema?.clipboard ?? null
216
218
  },
217
219
 
218
220
  hasHeader() {
@@ -674,6 +676,7 @@ export default DitoComponent.component('DitoSchema', {
674
676
  {
675
677
  // Needed for DitoContext handling inside `processData` and
676
678
  // `processSchemaData()`:
679
+ rootData: this.rootData,
677
680
  component: this,
678
681
  schemaOnly,
679
682
  target
@@ -770,7 +773,7 @@ export default DitoComponent.component('DitoSchema', {
770
773
  grid-template-rows: min-content;
771
774
  grid-template-columns: 100%;
772
775
 
773
- > *:only-child {
776
+ > :only-child {
774
777
  grid-row-end: none;
775
778
  }
776
779
  }
@@ -82,17 +82,10 @@ export default DitoComponent.component('DitoSchemaInlined', {
82
82
  grid-template-rows: min-content;
83
83
  grid-template-columns: 100%;
84
84
 
85
- > .dito-schema-content {
85
+ &:not(:hover, .dito-schema--open) {
86
86
  > .dito-schema-header {
87
- justify-content: space-between;
88
- position: relative;
89
-
90
- .dito-label {
91
- width: 100%;
92
- margin: 0;
93
- // Prevent collapsing to min-height when alone in
94
- // .dito-schema-content, due to grid-template-rows: min-content
95
- min-height: $input-height;
87
+ > .dito-clipboard {
88
+ display: none;
96
89
  }
97
90
  }
98
91
  }
@@ -2,7 +2,7 @@
2
2
  .dito-tree-item(
3
3
  :id="dataPath"
4
4
  :class=`{
5
- 'dito-dragging': dragging,
5
+ 'dito-dragging': isDragging,
6
6
  'dito-active': active
7
7
  }`
8
8
  :style="level > 0 && { '--level': level }"
@@ -76,7 +76,7 @@
76
76
  v-if="childrenSchema"
77
77
  v-show="opened"
78
78
  :modelValue="updateOrder(childrenSchema, childrenList)"
79
- :options="getSortableOptions(true)"
79
+ :options="getDraggableOptions(true)"
80
80
  :draggable="childrenDraggable"
81
81
  @update:modelValue="value => (childrenList = value)"
82
82
  )
@@ -2,19 +2,20 @@
2
2
  export default {
3
3
  data() {
4
4
  return {
5
- dragging: false
5
+ isDragging: false
6
6
  }
7
7
  },
8
8
 
9
9
  methods: {
10
- getSortableOptions(forceFallback = false) {
10
+ getDraggableOptions(forceFallback = false) {
11
+ const prefix = 'dito-draggable'
11
12
  return {
12
13
  animation: 150,
13
14
  handle: '.dito-button-drag',
14
- dragClass: 'dito-sortable-active',
15
- chosenClass: 'dito-sortable-chosen',
16
- ghostClass: 'dito-sortable-ghost',
17
- fallbackClass: 'dito-sortable-fallback',
15
+ dragClass: `${prefix}__drag`,
16
+ chosenClass: `${prefix}__chosen`,
17
+ ghostClass: `${prefix}__ghost`,
18
+ fallbackClass: `${prefix}__fallback`,
18
19
  forceFallback,
19
20
  onStart: this.onStartDrag,
20
21
  onEnd: this.onEndDrag
@@ -22,11 +23,11 @@ export default {
22
23
  },
23
24
 
24
25
  onStartDrag() {
25
- this.dragging = true
26
+ this.isDragging = true
26
27
  },
27
28
 
28
29
  onEndDrag({ oldIndex, newIndex }) {
29
- this.dragging = false
30
+ this.isDragging = false
30
31
  if (oldIndex !== newIndex) {
31
32
  this.onChange()
32
33
  }
@@ -28,7 +28,6 @@ export default {
28
28
  data() {
29
29
  return {
30
30
  parsedValue: undefined,
31
- changedValue: undefined,
32
31
  focused: false
33
32
  }
34
33
  },
@@ -48,37 +47,27 @@ export default {
48
47
 
49
48
  value: {
50
49
  get() {
51
- let value = computeValue(
50
+ const value = computeValue(
52
51
  this.schema,
53
52
  this.data,
54
53
  this.name,
55
54
  this.dataPath,
56
55
  { component: this }
57
56
  )
58
- const { formatValue } = this.$options
59
- if (formatValue) {
60
- value = formatValue(this.schema, value, this.dataPath)
61
- }
62
57
  const { format } = this.schema
63
- if (format) {
64
- value = format(new DitoContext(this, { value }))
65
- }
66
- return value
58
+ return format
59
+ ? format(new DitoContext(this, { value }))
60
+ : value
67
61
  },
68
62
 
69
63
  set(value) {
70
- const { parseValue } = this.$options
71
- if (parseValue) {
72
- value = parseValue(this.schema, value, this.dataPath)
73
- }
74
64
  const { parse } = this.schema
75
65
  if (parse) {
76
66
  value = parse(new DitoContext(this, { value }))
77
67
  }
68
+ this.parsedValue = value
78
69
  // eslint-disable-next-line vue/no-mutating-props
79
70
  this.data[this.name] = value
80
- this.parsedValue = value
81
- this.changedValue = undefined
82
71
  }
83
72
  },
84
73
 
@@ -295,7 +284,6 @@ export default {
295
284
 
296
285
  clear() {
297
286
  this.value = null
298
- this.changedValue = undefined
299
287
  this.blur()
300
288
  this.onChange()
301
289
  },
@@ -318,21 +306,12 @@ export default {
318
306
  },
319
307
 
320
308
  onChange() {
321
- const value =
322
- this.parsedValue !== undefined ? this.parsedValue : this.value
323
-
324
- if (this.$options.nativeField) {
325
- // For some odd reason, the native change event now sometimes fires
326
- // twice on Vue3. Filter out second call.
327
- // TODO: Investigate why this happens, and if it's a bug in Vue3.
328
- if (value === this.changedValue) return
329
- this.changedValue = value
330
- }
331
-
332
309
  this.markDirty()
333
310
  this.emitEvent('change', {
334
- // Prevent endless parse recursion:
335
- context: { value },
311
+ context: {
312
+ // Prevent endless parse recursion:
313
+ value: this.parsedValue !== undefined ? this.parsedValue : this.value
314
+ },
336
315
  // Pass `schemaComponent` as parent, so change events can propagate up.
337
316
  parent: this.schemaComponent
338
317
  })
@@ -13,7 +13,7 @@
13
13
  }
14
14
 
15
15
  .dito-layout-horizontal {
16
- > *:not(:first-child) {
16
+ > :not(:first-child) {
17
17
  padding-left: $form-spacing;
18
18
  }
19
19
  }
@@ -4,7 +4,6 @@
4
4
  @import '_pulldown';
5
5
  @import '_layout';
6
6
  @import '_scroll';
7
- @import '_sortable';
8
7
  @import '_table';
9
8
  @import '_info';
10
9
  @import '_tippy';
@@ -39,7 +39,7 @@
39
39
  DitoDraggable(
40
40
  tag="tbody"
41
41
  :modelValue="updateOrder(sourceSchema, listData, paginationRange)"
42
- :options="getSortableOptions()"
42
+ :options="getDraggableOptions()"
43
43
  :draggable="draggable"
44
44
  @update:modelValue="value => (listData = value)"
45
45
  )