@ditojs/admin 2.7.5 → 2.8.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 (37) hide show
  1. package/dist/dito-admin.es.js +1417 -1376
  2. package/dist/dito-admin.umd.js +5 -5
  3. package/dist/style.css +1 -1
  4. package/package.json +5 -5
  5. package/src/DitoContext.js +4 -0
  6. package/src/DitoTypeComponent.js +1 -1
  7. package/src/components/DitoClipboard.vue +22 -0
  8. package/src/components/DitoContainer.vue +7 -12
  9. package/src/components/DitoCreateButton.vue +21 -1
  10. package/src/components/DitoDialog.vue +5 -0
  11. package/src/components/DitoEditButtons.vue +35 -25
  12. package/src/components/DitoElement.vue +2 -2
  13. package/src/components/DitoForm.vue +9 -4
  14. package/src/components/DitoHeader.vue +22 -6
  15. package/src/components/DitoLabel.vue +41 -23
  16. package/src/components/DitoPane.vue +47 -35
  17. package/src/components/DitoRoot.vue +1 -2
  18. package/src/components/DitoSchema.vue +139 -165
  19. package/src/components/DitoSchemaInlined.vue +12 -9
  20. package/src/components/DitoTabs.vue +65 -23
  21. package/src/components/DitoTreeItem.vue +1 -1
  22. package/src/components/DitoView.vue +0 -1
  23. package/src/mixins/DitoMixin.js +6 -5
  24. package/src/mixins/ItemMixin.js +5 -1
  25. package/src/mixins/TypeMixin.js +14 -30
  26. package/src/styles/_button.scss +13 -12
  27. package/src/styles/_settings.scss +2 -2
  28. package/src/types/DitoTypeList.vue +1 -1
  29. package/src/types/DitoTypeMultiselect.vue +2 -3
  30. package/src/types/DitoTypeObject.vue +6 -4
  31. package/src/types/DitoTypePanel.vue +1 -1
  32. package/src/types/DitoTypeSection.vue +38 -10
  33. package/src/types/DitoTypeSelect.vue +2 -1
  34. package/src/types/DitoTypeUpload.vue +2 -2
  35. package/src/utils/options.js +1 -1
  36. package/src/utils/schema.js +8 -6
  37. package/types/index.d.ts +1 -1
@@ -3,11 +3,11 @@
3
3
  template(
4
4
  v-for="(tabSchema, key) in tabs"
5
5
  )
6
- RouterLink.dito-link(
6
+ a.dito-link(
7
7
  v-if="shouldRenderSchema(tabSchema)"
8
8
  :key="key"
9
- :to="{ hash: `#${key}` }"
10
- :class="{ 'dito-active': selectedTab === key }"
9
+ :class="{ 'dito-active': modelValue === key }"
10
+ @click="$emit('update:modelValue', key)"
11
11
  ) {{ getLabel(tabSchema, key) }}
12
12
  </template>
13
13
 
@@ -16,45 +16,87 @@ import DitoComponent from '../DitoComponent.js'
16
16
 
17
17
  // @vue/component
18
18
  export default DitoComponent.component('DitoTabs', {
19
+ emits: ['update:modelValue'],
19
20
  props: {
20
21
  tabs: { type: Object, default: null },
21
- selectedTab: { type: String, default: null }
22
+ modelValue: { type: String, default: null }
22
23
  }
23
24
  })
24
25
  </script>
25
26
 
26
27
  <style lang="scss">
28
+ @use 'sass:color';
27
29
  @import '../styles/_imports';
28
30
 
29
- $tab-color-background: $color-lightest;
30
- $tab-color-inactive: $color-light;
31
- $tab-color-active: $color-lightest;
32
- $tab-color-hover: $color-white;
33
-
34
31
  .dito-tabs {
35
- // See: https://codepen.io/tholex/pen/hveBx/
36
- margin-left: auto;
32
+ display: flex;
37
33
 
38
- a {
39
- display: block;
34
+ .dito-link {
40
35
  @include user-select(none);
41
36
 
42
- background: $tab-color-inactive;
43
- padding: $tab-padding-ver $tab-padding-hor;
44
- margin-left: $tab-margin;
45
- border-top-left-radius: $tab-radius;
46
- border-top-right-radius: $tab-radius;
37
+ display: block;
38
+ white-space: nowrap;
47
39
 
48
40
  &:hover {
49
- background: $tab-color-hover;
41
+ background: $color-white;
50
42
  }
51
43
 
52
- &:active {
53
- background: $tab-color-active;
44
+ // When in main header:
45
+ .dito-header & {
46
+ background: $color-light;
47
+ padding: $tab-padding-ver $tab-padding-hor;
48
+ margin-left: $tab-margin;
49
+ border-top-left-radius: $tab-radius;
50
+ border-top-right-radius: $tab-radius;
51
+
52
+ &:active {
53
+ background: $color-lightest;
54
+ }
55
+
56
+ &.dito-active {
57
+ background: $color-lightest;
58
+ }
54
59
  }
55
60
 
56
- &.dito-active {
57
- background: $tab-color-background;
61
+ // When inside a inline schema:
62
+ .dito-schema-inlined &,
63
+ .dito-label & {
64
+ background: $color-lighter;
65
+ border: $border-style;
66
+ padding: $input-padding;
67
+ margin-left: -$border-width;
68
+ white-space: nowrap;
69
+
70
+ &:first-child {
71
+ border-top-left-radius: $tab-radius;
72
+ border-bottom-left-radius: $tab-radius;
73
+ }
74
+
75
+ &:last-child {
76
+ border-top-right-radius: $tab-radius;
77
+ border-bottom-right-radius: $tab-radius;
78
+ }
79
+
80
+ &:active {
81
+ background: $color-lighter;
82
+ }
83
+
84
+ &.dito-active {
85
+ background: $color-active;
86
+ border-color: color.adjust($color-active, $lightness: -10%);
87
+ color: $color-white;
88
+ z-index: 1;
89
+ }
90
+ }
91
+ }
92
+
93
+ .dito-schema & {
94
+ // Push clipboard to the right in the flex layout, see:
95
+ // https://codepen.io/tholex/pen/hveBx/
96
+ margin-left: auto;
97
+
98
+ &:last-child {
99
+ margin-right: auto;
58
100
  }
59
101
  }
60
102
  }
@@ -16,7 +16,7 @@
16
16
  )
17
17
  .dito-chevron(
18
18
  v-if="numEntries"
19
- :class="{ 'dito-opened': opened }"
19
+ :class="{ 'dito-open': opened }"
20
20
  )
21
21
  .dito-tree-label(
22
22
  v-html="label"
@@ -22,7 +22,6 @@ template(
22
22
  :store="getChildStore(name)"
23
23
  :disabled="isLoading"
24
24
  scrollable
25
- headerInMenu
26
25
  )
27
26
  </template>
28
27
 
@@ -52,6 +52,7 @@ export default {
52
52
  data() {
53
53
  return {
54
54
  appState,
55
+ isMounted: false,
55
56
  overrides: null // See accessor.js
56
57
  }
57
58
  },
@@ -171,13 +172,13 @@ export default {
171
172
  }
172
173
  },
173
174
 
175
+ mounted() {
176
+ this.isMounted = true
177
+ },
178
+
174
179
  beforeCreate() {
175
180
  const uid = nextUid++
176
- Object.defineProperty(this, '$uid', {
177
- get() {
178
- return uid
179
- }
180
- })
181
+ Object.defineProperty(this, '$uid', { get: () => uid })
181
182
  },
182
183
 
183
184
  methods: {
@@ -134,7 +134,11 @@ export default {
134
134
  text = `${formLabel} ${hadLabel ? `'${text}'` : text}`
135
135
  }
136
136
  }
137
- return asObject ? { text, prefix, suffix } : text
137
+ return asObject
138
+ ? text || prefix || suffix
139
+ ? { text, prefix, suffix }
140
+ : null
141
+ : text
138
142
  }
139
143
  }
140
144
  }
@@ -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
 
@@ -118,6 +107,11 @@ export default {
118
107
  return this.processedData
119
108
  },
120
109
 
110
+ labelNode() {
111
+ const node = this.isMounted ? this.$el.previousElementSibling : null
112
+ return node?.matches('.dito-label') ? node : null
113
+ },
114
+
121
115
  visible: getSchemaAccessor('visible', {
122
116
  type: Boolean,
123
117
  default() {
@@ -290,7 +284,6 @@ export default {
290
284
 
291
285
  clear() {
292
286
  this.value = null
293
- this.changedValue = undefined
294
287
  this.blur()
295
288
  this.onChange()
296
289
  },
@@ -313,21 +306,12 @@ export default {
313
306
  },
314
307
 
315
308
  onChange() {
316
- const value =
317
- this.parsedValue !== undefined ? this.parsedValue : this.value
318
-
319
- if (this.$options.nativeField) {
320
- // For some odd reason, the native change event now sometimes fires
321
- // twice on Vue3. Filter out second call.
322
- // TODO: Investigate why this happens, and if it's a bug in Vue3.
323
- if (value === this.changedValue) return
324
- this.changedValue = value
325
- }
326
-
327
309
  this.markDirty()
328
310
  this.emitEvent('change', {
329
- // Prevent endless parse recursion:
330
- context: { value },
311
+ context: {
312
+ // Prevent endless parse recursion:
313
+ value: this.parsedValue !== undefined ? this.parsedValue : this.value
314
+ },
331
315
  // Pass `schemaComponent` as parent, so change events can propagate up.
332
316
  parent: this.schemaComponent
333
317
  })
@@ -112,6 +112,8 @@
112
112
  }
113
113
 
114
114
  .dito-buttons {
115
+ // TODO: BEM
116
+
115
117
  &.dito-buttons-large {
116
118
  --button-margin: 3px;
117
119
 
@@ -133,19 +135,18 @@
133
135
 
134
136
  &.dito-buttons-main {
135
137
  border-top: $border-style;
138
+ }
136
139
 
137
- .dito-scroll > &:not(:empty) {
138
- position: sticky;
139
- bottom: 0;
140
- width: 100%;
141
- align-self: flex-end;
142
- z-index: $z-index-main-buttons;
143
- margin-bottom: -$content-padding;
144
- margin-top: 2 * $content-padding;
145
- box-shadow: 0 (-$content-padding) $content-padding (-$content-padding)
146
- $color-shadow;
147
- background: $content-color-background;
148
- }
140
+ &.dito-buttons-sticky {
141
+ align-self: flex-end;
142
+ position: sticky;
143
+ bottom: 0;
144
+ z-index: $z-index-main-buttons;
145
+ margin-bottom: -$content-padding;
146
+ margin-top: 2 * $content-padding;
147
+ box-shadow: 0 (-$content-padding) $content-padding (-$content-padding)
148
+ $color-shadow;
149
+ background: $content-color-background;
149
150
  }
150
151
 
151
152
  &.dito-buttons-round,
@@ -56,8 +56,8 @@ $header-padding: $header-padding-ver $header-padding-hor;
56
56
  $header-height: $header-font-size + 2 * $header-padding-ver;
57
57
 
58
58
  // Tabs
59
- $tab-margin: 6px;
60
- $tab-padding-hor: 12px;
59
+ $tab-margin: $form-spacing;
60
+ $tab-padding-hor: 2 * $tab-margin;
61
61
  $tab-padding-ver: calc(($header-padding-ver * 2 - $tab-margin) / 2);
62
62
  $tab-radius: 4px;
63
63
 
@@ -137,7 +137,7 @@
137
137
  :createPath="path"
138
138
  )
139
139
  //- Render create buttons outside table when in a single component view:
140
- DitoEditButtons.dito-buttons-main.dito-buttons-large(
140
+ DitoEditButtons.dito-buttons-large.dito-buttons-main.dito-buttons-sticky(
141
141
  v-if="hasListButtons && single"
142
142
  :buttons="buttonSchemas"
143
143
  :schema="schema"
@@ -38,6 +38,7 @@
38
38
  :disabled="disabled"
39
39
  @click="clear"
40
40
  )
41
+ //- Edit button is never disabled, even if the field is disabled.
41
42
  DitoEditButtons(
42
43
  v-if="editable"
43
44
  :schema="schema"
@@ -45,7 +46,7 @@
45
46
  :data="data"
46
47
  :meta="meta"
47
48
  :store="store"
48
- :disabled="disabled"
49
+ :disabled="false"
49
50
  :editable="editable"
50
51
  :editPath="editPath"
51
52
  )
@@ -67,7 +68,6 @@ export default DitoTypeComponent.register('multiselect', {
67
68
 
68
69
  data() {
69
70
  return {
70
- isMounted: false,
71
71
  searchedOptions: null,
72
72
  populate: false
73
73
  }
@@ -145,7 +145,6 @@ export default DitoTypeComponent.register('multiselect', {
145
145
  },
146
146
 
147
147
  mounted() {
148
- this.isMounted = true
149
148
  if (this.autofocus) {
150
149
  // vue-multiselect doesn't support the autofocus attribute. We need to
151
150
  // handle it here.
@@ -18,7 +18,11 @@
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
24
  :accumulatedBasis="accumulatedBasis"
25
+ @delete="deleteItem(objectData)"
22
26
  )
23
27
  component(
24
28
  v-else-if="schema.component"
@@ -35,6 +39,8 @@
35
39
  v-else
36
40
  v-html="getItemLabel(schema, objectData)"
37
41
  )
42
+ //- NOTE: `DitoEditButtons` here only handle the create button outside of the
43
+ //- schema, the edit buttons inside are handled by `DitoSchemaInlined`.
38
44
  DitoEditButtons(
39
45
  :buttons="buttonSchemas"
40
46
  :schema="schema"
@@ -45,11 +51,7 @@
45
51
  :store="store"
46
52
  :disabled="disabled || isLoading"
47
53
  :creatable="creatable"
48
- :deletable="objectData && deletable"
49
- :editable="objectData && editable"
50
54
  :createPath="path"
51
- :editPath="path"
52
- @delete="deleteItem(objectData)"
53
55
  )
54
56
  </template>
55
57
 
@@ -6,7 +6,7 @@ export default DitoTypeComponent.register('panel', {
6
6
  defaultValue: () => undefined, // Callback to override `defaultValue: null`
7
7
  excludeValue: true,
8
8
  generateLabel: false,
9
- omitPadding: true,
9
+ omitSpacing: true,
10
10
 
11
11
  getPanelSchema(api, schema) {
12
12
  // For a TypePanel, the component schema is also the panel schema, but
@@ -1,17 +1,22 @@
1
1
  <template lang="pug">
2
- .dito-section(:class="{ 'dito-section-labelled': !!schema.label }")
3
- DitoPane.dito-section__pane(
2
+ .dito-section(:class="{ 'dito-section--labelled': !!schema.label }")
3
+ DitoSchemaInlined.dito-section__schema(
4
+ :label="label"
4
5
  :schema="getItemFormSchema(schema, item, context)"
5
6
  :dataPath="dataPath"
6
7
  :data="item"
7
8
  :meta="meta"
8
9
  :store="store"
9
10
  :disabled="disabled"
11
+ :collapsed="collapsed"
12
+ :collapsible="collapsible"
13
+ :labelNode="labelNode"
10
14
  )
11
15
  </template>
12
16
 
13
17
  <script>
14
18
  import DitoTypeComponent from '../DitoTypeComponent.js'
19
+ import { getSchemaAccessor } from '../utils/accessor.js'
15
20
  import { getItemFormSchema, processSchemaComponents } from '../utils/schema.js'
16
21
 
17
22
  // @vue/component
@@ -24,7 +29,20 @@ export default DitoTypeComponent.register('section', {
24
29
  computed: {
25
30
  item() {
26
31
  return this.nested ? this.value : this.data
27
- }
32
+ },
33
+
34
+ collapsible: getSchemaAccessor('collapsible', {
35
+ type: Boolean,
36
+ default: null, // so that `??` below can do its thing:
37
+ get(collapsible) {
38
+ return !!(collapsible ?? this.collapsed !== null)
39
+ }
40
+ }),
41
+
42
+ collapsed: getSchemaAccessor('collapsed', {
43
+ type: Boolean,
44
+ default: null
45
+ })
28
46
  },
29
47
 
30
48
  methods: {
@@ -42,15 +60,25 @@ export default DitoTypeComponent.register('section', {
42
60
  @import '../styles/_imports';
43
61
 
44
62
  .dito-section {
45
- &.dito-section-labelled {
46
- border: $border-style;
63
+ &--labelled {
64
+ border: $border-width solid transparent;
47
65
  border-radius: $border-radius;
48
- padding: $form-spacing;
49
- box-sizing: border-box;
50
- }
66
+ transition: border-color 0.2s $ease-out-quart;
67
+ margin-top: $form-spacing-half;
51
68
 
52
- .dito-section__pane {
53
- padding: 0;
69
+ &:has(.dito-schema--open) {
70
+ border-color: $border-color;
71
+ }
72
+
73
+ // For animation purposes, move the padding to the contained panes.
74
+ .dito-pane {
75
+ padding: $form-spacing;
76
+
77
+ &:has(> .dito-container--label-vertical:first-of-type) {
78
+ // Reduce top spacing when the first row has labels.
79
+ padding-top: $form-spacing-half;
80
+ }
81
+ }
54
82
  }
55
83
  }
56
84
  </style>
@@ -37,6 +37,7 @@
37
37
  :disabled="disabled"
38
38
  @click="clear"
39
39
  )
40
+ //- Edit button is never disabled, even if the field is disabled.
40
41
  DitoEditButtons(
41
42
  v-if="editable"
42
43
  :schema="schema"
@@ -44,7 +45,7 @@
44
45
  :data="data"
45
46
  :meta="meta"
46
47
  :store="store"
47
- :disabled="disabled"
48
+ :disabled="false"
48
49
  :editable="editable"
49
50
  :editPath="editPath"
50
51
  )
@@ -481,9 +481,9 @@ function asFiles(value) {
481
481
  white-space: nowrap;
482
482
  }
483
483
 
484
- &__input {
484
+ & &__input {
485
485
  // See `onClickUpload()` method for details.
486
- position: absolute;
486
+ display: block;
487
487
  pointer-events: none;
488
488
  }
489
489
 
@@ -36,7 +36,7 @@ const ditoOptionKeys = [
36
36
  'generateLabel',
37
37
  'excludeValue',
38
38
  'ignoreMissingValue',
39
- 'omitPadding',
39
+ 'omitSpacing',
40
40
  'formatValue',
41
41
  'parseValue',
42
42
  'processValue',
@@ -483,11 +483,13 @@ export function getFormSchemas(schema, context, modifyForm) {
483
483
  throw new Error(`Unknown view: '${schema.view}'`)
484
484
  }
485
485
 
486
- let { form, forms, components, compact } = schema
486
+ let { form, forms } = schema
487
487
  if (!form && !forms) {
488
- if (components) {
489
- // Convert inlined components to forms, supporting `compact` setting.
490
- form = { components, compact }
488
+ const { components, tabs, compact, clipboard } = schema
489
+ if (components || tabs) {
490
+ // Convert inlined components to forms, also supporting `tabs`, `compact`
491
+ // and `clipboard` settings.
492
+ form = { components, tabs, compact, clipboard }
491
493
  } else {
492
494
  // No `forms`, `form` or `components`, return and empty `forms` object.
493
495
  return {}
@@ -538,8 +540,8 @@ export function hasLabel(schema, generateLabels) {
538
540
  )
539
541
  }
540
542
 
541
- export function omitPadding(schema) {
542
- return !!getTypeOptions(schema)?.omitPadding
543
+ export function omitSpacing(schema) {
544
+ return !!getTypeOptions(schema)?.omitSpacing
543
545
  }
544
546
 
545
547
  export function getSchemaValue(
package/types/index.d.ts CHANGED
@@ -171,7 +171,7 @@ export interface BaseSchema<$Item>
171
171
  default?: OrItemAccessor<$Item>
172
172
  compute?: ItemAccessor<$Item>
173
173
  data?: OrItemAccessor<$Item, {}, Record<string, any>>
174
- omitPadding?: boolean
174
+ omitSpacing?: boolean
175
175
  break?: 'before' | 'after'
176
176
  }
177
177