@ditojs/admin 2.7.5 → 2.8.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.7.5",
3
+ "version": "2.8.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",
@@ -33,8 +33,8 @@
33
33
  "not ie_mob > 0"
34
34
  ],
35
35
  "dependencies": {
36
- "@ditojs/ui": "^2.7.3",
37
- "@ditojs/utils": "^2.7.1",
36
+ "@ditojs/ui": "^2.8.0",
37
+ "@ditojs/utils": "^2.8.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.5.0",
77
+ "@ditojs/build": "^2.8.0",
78
78
  "@vitejs/plugin-vue": "^4.2.1",
79
79
  "@vue/compiler-sfc": "^3.2.47",
80
80
  "pug": "^3.0.2",
@@ -83,7 +83,7 @@
83
83
  "vite": "^4.3.5"
84
84
  },
85
85
  "types": "types",
86
- "gitHead": "d88c47b18f85103460099db95afb067c8c244253",
86
+ "gitHead": "6f589b645e8841b1875330d9918a7d952af49b31",
87
87
  "scripts": {
88
88
  "build": "vite build",
89
89
  "watch": "yarn build --mode 'development' --watch",
@@ -216,6 +216,10 @@ export default class DitoContext {
216
216
  return get(this, 'options', undefined)
217
217
  }
218
218
 
219
+ get open() {
220
+ return get(this, 'open', undefined)
221
+ }
222
+
219
223
  // TODO: Rename this to `searchTerm` or `searchQuery`, to perhaps free `query`
220
224
  // for the actual `resourceComponent.query` object?
221
225
  get query() {
@@ -21,7 +21,7 @@ export default {
21
21
  generateLabel: true,
22
22
  excludeValue: false,
23
23
  ignoreMissingValue: null,
24
- omitPadding: false,
24
+ omitSpacing: false,
25
25
 
26
26
  component: DitoComponent.component,
27
27
 
@@ -165,3 +165,25 @@ export default DitoComponent.component('DitoClipboard', {
165
165
  }
166
166
  })
167
167
  </script>
168
+
169
+ <style lang="scss">
170
+ @import '../styles/_imports';
171
+
172
+ .dito-clipboard {
173
+ display: flex;
174
+
175
+ .dito-schema & {
176
+ // Push clipboard to the right in the flex layout, see:
177
+ // https://codepen.io/tholex/pen/hveBx/
178
+ margin-left: auto;
179
+ }
180
+
181
+ .dito-header & {
182
+ margin-left: 0;
183
+
184
+ .dito-button {
185
+ margin: 0 0 $tab-margin $tab-margin;
186
+ }
187
+ }
188
+ }
189
+ </style>
@@ -51,7 +51,7 @@ import {
51
51
  getAllPanelEntries,
52
52
  getTypeComponent,
53
53
  hasLabel,
54
- omitPadding
54
+ omitSpacing
55
55
  } from '../utils/schema.js'
56
56
  import { parseFraction } from '../utils/math.js'
57
57
 
@@ -73,8 +73,7 @@ export default DitoComponent.component('DitoContainer', {
73
73
 
74
74
  data() {
75
75
  return {
76
- errors: null,
77
- isMounted: false
76
+ errors: null
78
77
  }
79
78
  },
80
79
 
@@ -184,7 +183,7 @@ export default DitoComponent.component('DitoContainer', {
184
183
  'dito-disabled': this.componentDisabled,
185
184
  'dito-has-errors': !!this.errors,
186
185
  [`${prefix}--label-vertical`]: this.verticalLabels,
187
- [`${prefix}--omit-padding`]: omitPadding(this.schema),
186
+ [`${prefix}--omit-spacing`]: omitSpacing(this.schema),
188
187
  ...(isString(classes) ? { [classes]: true } : classes)
189
188
  }
190
189
  },
@@ -228,10 +227,6 @@ export default DitoComponent.component('DitoContainer', {
228
227
  }
229
228
  },
230
229
 
231
- mounted() {
232
- this.isMounted = true
233
- },
234
-
235
230
  methods: {
236
231
  onErrors(errors) {
237
232
  this.errors = errors
@@ -258,7 +253,7 @@ export default DitoComponent.component('DitoContainer', {
258
253
  max-width: 100%;
259
254
  // Cannot use margin here as it needs to be part of box-sizing for
260
255
  // percentages in flex-basis to work.
261
- padding: $form-spacing $form-spacing-half;
256
+ padding: $form-spacing-half;
262
257
 
263
258
  .dito-page .dito-pane > & {
264
259
  @container (width < #{calc($content-width * 0.8)}) {
@@ -300,15 +295,15 @@ export default DitoComponent.component('DitoContainer', {
300
295
  // have labels, add some spacing to the top to align with the other
301
296
  // components (e.g. buttons):
302
297
  > .dito-component:first-child:not(.dito-section, .dito-list, .dito-object) {
303
- margin-top: $line-height * $font-size + $form-spacing-half;
298
+ margin-top: $input-height;
304
299
  }
305
300
  }
306
301
 
307
- &--omit-padding {
302
+ &--omit-spacing {
308
303
  padding: 0;
309
304
 
310
305
  > .dito-label {
311
- margin: $form-spacing $form-spacing-half 0;
306
+ margin: $form-spacing-half $form-spacing-half 0;
312
307
  }
313
308
  }
314
309
  }
@@ -40,6 +40,10 @@ export default DitoComponent.component('DitoCreateButton', {
40
40
 
41
41
  props: {
42
42
  schema: { type: Object, required: true },
43
+ dataPath: { type: String, required: true },
44
+ data: { type: [Object, Array], default: null },
45
+ meta: { type: Object, required: true },
46
+ store: { type: Object, required: true },
43
47
  path: { type: String, required: true },
44
48
  verb: { type: String, required: true },
45
49
  text: { type: String, default: null },
@@ -11,6 +11,11 @@
11
11
  form.dito-scroll-parent(
12
12
  @submit.prevent="submit"
13
13
  )
14
+ // Add an invisible button that prevents the clearable buttons from being
15
+ // pressed when the user presses the Enter key:
16
+ button(
17
+ v-show="false"
18
+ )
14
19
  DitoSchema(
15
20
  :schema="schema"
16
21
  :data="dialogData"
@@ -23,6 +23,10 @@ DitoButtons.dito-edit-buttons.dito-buttons-round(
23
23
  DitoCreateButton(
24
24
  v-if="hasCreatable"
25
25
  :schema="schema"
26
+ :dataPath="dataPath"
27
+ :data="data"
28
+ :meta="meta"
29
+ :store="store"
26
30
  :path="createPath"
27
31
  :verb="verbs.create"
28
32
  :text="createButtonText"
@@ -12,9 +12,10 @@
12
12
  v-show="!isActive"
13
13
  )
14
14
  //- Use a <div> for inlined forms, as we shouldn't nest actual <form> tags.
15
- component.dito-scroll-parent(
15
+ component(
16
16
  v-show="isActive"
17
17
  :is="isNestedRoute ? 'div' : 'form'"
18
+ :class="{ 'dito-scroll-parent': isRootForm }"
18
19
  @submit.prevent
19
20
  )
20
21
  //- Prevent implicit submission of the form, for example when typing enter
@@ -32,12 +33,12 @@
32
33
  :meta="meta"
33
34
  :store="store"
34
35
  :disabled="isLoading"
35
- :scrollable="!isNestedRoute"
36
- headerInMenu
36
+ :scrollable="isRootForm"
37
37
  generateLabels
38
38
  )
39
39
  template(#buttons)
40
- DitoButtons.dito-buttons-round.dito-buttons-main.dito-buttons-large(
40
+ DitoButtons.dito-buttons-round.dito-buttons-large.dito-buttons-main(
41
+ :class="{ 'dito-buttons-sticky': isRootForm }"
41
42
  :buttons="buttonSchemas"
42
43
  :dataPath="dataPath"
43
44
  :data="data"
@@ -122,6 +123,10 @@ export default DitoComponent.component('DitoForm', {
122
123
  )
123
124
  },
124
125
 
126
+ isRootForm() {
127
+ return this.dataPath === '' && !this.isNestedRoute
128
+ },
129
+
125
130
  isActive() {
126
131
  return this.isLastRoute || this.isLastUnnestedRoute
127
132
  },
@@ -85,13 +85,10 @@ export default DitoComponent.component('DitoHeader', {
85
85
  @include user-select(none);
86
86
 
87
87
  &::after {
88
- // Cover the gaps between the different headers that might appear due to
89
- // layout rounding imprecisions.
88
+ // Set the full-width header background to the header color.
90
89
  content: '';
91
- top: 0;
92
- bottom: 0;
93
- left: calc(100% - 1px);
94
- width: 2px;
90
+ inset: 0;
91
+ width: 100vw;
95
92
  position: absolute;
96
93
  background: inherit;
97
94
  z-index: -1;
@@ -175,5 +172,24 @@ export default DitoComponent.component('DitoHeader', {
175
172
  border-radius: 100%;
176
173
  }
177
174
  }
175
+
176
+ &__teleport {
177
+ // Align the teleported schema headers on top of to the header menu.
178
+ position: absolute;
179
+ inset: 0;
180
+ display: flex;
181
+ justify-content: flex-end;
182
+ padding: 0 $header-padding-hor;
183
+ // Turn off pointer events so that DitoTrail keeps receiving events...
184
+ pointer-events: none;
185
+ // ...but move them to the children.
186
+ > * {
187
+ pointer-events: auto;
188
+ }
189
+
190
+ .dito-button {
191
+ margin: 0 0 $tab-margin $tab-margin;
192
+ }
193
+ }
178
194
  }
179
195
  </style>
@@ -7,20 +7,23 @@ component.dito-label(
7
7
  )
8
8
  .dito-chevron(
9
9
  v-if="collapsible"
10
- :class="{ 'dito-opened': !collapsed }"
10
+ :class="{ 'dito-open': !collapsed }"
11
11
  )
12
- .dito-label__inner
13
- DitoElement.dito-label-prefix(
12
+ .dito-label__inner(
13
+ v-if="text || prefixes.length > 0 || suffixes.length > 0"
14
+ )
15
+ DitoElement.dito-label__prefix(
14
16
  v-for="(prefix, index) of prefixes"
15
17
  :key="`prefix-${index}`"
16
18
  tag="span"
17
19
  :content="prefix"
18
20
  )
19
21
  label(
22
+ v-if="text"
20
23
  :for="dataPath"
21
24
  v-html="text"
22
25
  )
23
- DitoElement.dito-label-suffix(
26
+ DitoElement.dito-label__suffix(
24
27
  v-for="(suffix, index) of suffixes"
25
28
  :key="`suffix-${index}`"
26
29
  tag="span"
@@ -30,7 +33,6 @@ component.dito-label(
30
33
  v-if="info"
31
34
  :data-info="info"
32
35
  )
33
- slot(name="edit-buttons")
34
36
  </template>
35
37
 
36
38
  <script>
@@ -39,7 +41,7 @@ import { isObject, asArray } from '@ditojs/utils'
39
41
 
40
42
  // @vue/component
41
43
  export default DitoComponent.component('DitoLabel', {
42
- emits: ['expand'],
44
+ emits: ['open'],
43
45
 
44
46
  props: {
45
47
  label: { type: [String, Object], default: null },
@@ -79,7 +81,7 @@ export default DitoComponent.component('DitoLabel', {
79
81
  methods: {
80
82
  onClick() {
81
83
  this.appState.activeLabel = this
82
- this.$emit('expand', this.collapsed)
84
+ this.$emit('open', this.collapsed)
83
85
  }
84
86
  }
85
87
  })
@@ -96,35 +98,44 @@ export default DitoComponent.component('DitoLabel', {
96
98
  position: relative;
97
99
  // Vertically center all items in the label, e.g. chevron, edit-buttons.
98
100
  align-items: center;
99
- margin: 0 $form-spacing-half $form-spacing-half 0;
101
+ min-height: $input-height;
102
+ margin-right: $form-spacing-half; // When inlined.
103
+
104
+ &:has(.dito-schema-header) {
105
+ // The container's label is used as teleport for a nested section or object
106
+ // label. Hide `&__inner`, as it is be duplicated inside the nested label.
107
+ > #{$self}__inner {
108
+ display: none;
109
+ }
110
+ }
100
111
 
101
- .dito-container:not(.dito-container--label-vertical) > & {
102
- // When labels are not vertical (e.g. compact layout, next to component),
103
- // clear bottom padding for better vertical alignment.
104
- margin-bottom: 0;
112
+ &:not(:has(+ .dito-tabs)) {
113
+ // Take full width but only if there aren't also tabs to be centered.
114
+ flex: 1;
105
115
  }
106
116
 
107
117
  &__inner {
108
118
  display: flex;
109
- // Stretch to full available width so that buttons appear right-aligned:
110
- flex: 1 1 auto;
119
+ flex: 1 0 0%;
120
+ overflow: hidden;
111
121
  }
112
122
 
113
123
  label {
114
124
  cursor: inherit;
115
125
  font-weight: bold;
116
126
  white-space: nowrap;
127
+ line-height: $input-height;
117
128
  }
118
129
 
119
130
  label,
120
- .dito-label-prefix,
121
- .dito-label-suffix {
131
+ &__prefix,
132
+ &__suffix {
122
133
  @include user-select(none);
123
134
  @include ellipsis;
124
135
  }
125
136
 
126
- .dito-label-prefix + label,
127
- label + .dito-label-suffix {
137
+ &__prefix + label,
138
+ label + &__suffix {
128
139
  &::before {
129
140
  content: '\a0'; // &nbsp;
130
141
  }
@@ -140,11 +151,17 @@ export default DitoComponent.component('DitoLabel', {
140
151
 
141
152
  &.dito-width-fill {
142
153
  width: 100%;
154
+
143
155
  // In order for ellipsis to work on labels without affecting other layout,
144
- // we need to position it absolutely inside its container.
145
- #{$self}__inner {
146
- position: absolute;
147
- inset: 0;
156
+ // we need to position it absolutely inside its container. But we can only
157
+ // do so if there is't also a chevron or other UX elements besides it.
158
+ &:has(> #{$self}__inner:only-child) {
159
+ flex: 1; // When in `.dito-schema-header`.
160
+
161
+ > #{$self}__inner {
162
+ position: absolute;
163
+ inset: 0;
164
+ }
148
165
  }
149
166
 
150
167
  &::after {
@@ -2,7 +2,6 @@
2
2
  <template lang="pug">
3
3
  .dito-pane(
4
4
  v-if="isPopulated && componentSchemas.length > 0"
5
- v-show="visible"
6
5
  :class=`{
7
6
  'dito-pane--single': isSingleComponent
8
7
  }`
@@ -16,7 +15,9 @@
16
15
  store
17
16
  }, index in componentSchemas`
18
17
  )
19
- .dito-break(
18
+ // -Use <span> for .dito-break so we can use `.dito-container:first-of-type`
19
+ // selector.
20
+ span.dito-break(
20
21
  v-if="schema.break === 'before'"
21
22
  )
22
23
  DitoContainer(
@@ -35,7 +36,7 @@
35
36
  :verticalLabels="isInLabeledRow(index)"
36
37
  :accumulatedBasis="accumulatedBasis"
37
38
  )
38
- .dito-break(
39
+ span.dito-break(
39
40
  v-if="schema.break === 'after'"
40
41
  )
41
42
  </template>
@@ -61,7 +62,6 @@ export default DitoComponent.component('DitoPane', {
61
62
  store: { type: Object, required: true },
62
63
  tab: { type: String, default: null },
63
64
  single: { type: Boolean, default: false },
64
- visible: { type: Boolean, default: true },
65
65
  disabled: { type: Boolean, default: false },
66
66
  generateLabels: { type: Boolean, default: false },
67
67
  accumulatedBasis: { type: Number, default: null }
@@ -102,7 +102,7 @@ export default DitoComponent.component('DitoPane', {
102
102
  : this.dataPath,
103
103
  nestedDataPath,
104
104
  nested,
105
- store: nested ? this.getChildStore(name) : this.store
105
+ store: this.getChildStore(name)
106
106
  }
107
107
  }
108
108
  )
@@ -149,8 +149,12 @@ export default DitoComponent.component('DitoPane', {
149
149
  for (const index of row) {
150
150
  const position = this.positions[index]
151
151
  if (
152
- position?.height > 3 &&
153
- position.node.querySelector(':scope > .dito-label')
152
+ position?.height > 2 && (
153
+ position.node.matches(':has(> .dito-label)') ||
154
+ position.node
155
+ .closest('.dito-container')
156
+ .matches('.dito-container--label-vertical')
157
+ )
154
158
  ) {
155
159
  // TODO: Handle nested schemas, e.g. 'section' or 'object' and
156
160
  // detect labels there too.
@@ -221,51 +225,55 @@ export default DitoComponent.component('DitoPane', {
221
225
  flex-flow: row wrap;
222
226
  align-items: flex-start;
223
227
  align-content: flex-start;
224
- padding: $content-padding;
225
228
  // Remove the padding added by `.dito-container` inside `.dito-pane`:
226
- margin: (-$form-spacing) (-$form-spacing-half);
227
- max-width: calc(var(--max-content-width) + $form-spacing);
228
- // Use `flex: 0%` for all `.dito-pane` except `.dito-pane-main`,
229
+ margin: -$form-spacing-half;
230
+ // Use `flex: 0%` for all `.dito-pane` except `.dito-pane__main`,
229
231
  // so that the `.dito-buttons-main` can be moved all the way to the bottom.
230
232
  flex: 0%;
231
233
 
232
- &--single {
233
- // Clear negative margin from above.
234
- margin: 0;
234
+ &__main {
235
+ flex: 100%;
235
236
  }
236
237
 
237
238
  .dito-scroll > & {
238
- &,
239
- .dito-container {
240
- min-width: min-content;
241
- }
239
+ // A root-level pane inside a scroll view. Clear negative margin from above.
240
+ margin: 0;
241
+ // Move the negative margin used to remove the padding added by
242
+ // `.dito-container` inside `.dito-pane` to the padding:
243
+ padding: $content-padding - $form-spacing-half;
242
244
 
243
- &:not(#{$self}--single) {
244
- // Root-level panes inside scroll views need to move the negative margin
245
- // used to remove the padding added by `.dito-container` inside
246
- // `.dito-pane` to the padding:
247
- padding: ($content-padding - $form-spacing)
248
- ($content-padding - $form-spacing-half);
249
- margin: 0;
245
+ &#{$self}--single {
246
+ padding: $content-padding;
250
247
  }
251
- }
252
248
 
253
- &.dito-pane-main {
254
- flex: 100%;
249
+ &:has(> .dito-container--label-vertical:first-of-type) {
250
+ // Reduce top spacing when the first row has labels.
251
+ margin-top: -$form-spacing-half;
252
+ }
255
253
  }
256
254
 
257
- .dito-schema-header + & {
258
- // Clear top-margin if the components are preceded by a schema header.
259
- margin-top: 0;
255
+ // Display a ruler between tabbed components and towards the .dito-buttons
256
+ &__tab + &__main {
257
+ &::before {
258
+ // Use a pseudo element to display a ruler with proper margins
259
+ display: block;
260
+ content: '';
261
+ width: 100%;
262
+ 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;
266
+ }
260
267
  }
261
268
 
262
- .dito-container--omit-padding > & {
263
- // Clear margins set above again if parent is omitting padding.
264
- margin: 0;
265
- max-width: unset;
269
+ &__main + .dito-buttons-main {
270
+ // Needed forms with sticky main buttons.
271
+ margin: $content-padding;
272
+ margin-bottom: 0;
266
273
  }
267
274
 
268
275
  .dito-break {
276
+ display: block;
269
277
  flex: 100%;
270
278
  height: 0;
271
279
  }
@@ -37,8 +37,6 @@
37
37
  )
38
38
  span Login
39
39
  .dito-fill
40
- .dito-header
41
- span
42
40
  </template>
43
41
 
44
42
  <script>
@@ -478,6 +476,7 @@ function addRoutes(router, routes) {
478
476
  flex: 0 1 var(--max-page-width);
479
477
  background: $content-color-background;
480
478
  max-width: var(--max-page-width);
479
+ overflow: visible; // For .dito-header full-width background.
481
480
  // For the `@container` rule in `.dito-container` to work:
482
481
  container-type: inline-size;
483
482