@ditojs/admin 2.2.11 → 2.2.12

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.11",
3
+ "version": "2.2.12",
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",
@@ -82,7 +82,7 @@
82
82
  "vite": "^4.3.1"
83
83
  },
84
84
  "types": "types",
85
- "gitHead": "52aada1f67b13f9b30b745777c64649c6c7b171f",
85
+ "gitHead": "062d8d229de60f7c81677b927c5c66ce9f2d2337",
86
86
  "scripts": {
87
87
  "build": "vite build",
88
88
  "watch": "yarn build --mode 'development' --watch",
package/src/DitoAdmin.js CHANGED
@@ -13,6 +13,7 @@ import * as components from './components/index.js'
13
13
  import * as types from './types/index.js'
14
14
  import DitoRoot from './components/DitoRoot.vue'
15
15
  import DitoTypeComponent from './DitoTypeComponent.js'
16
+ import ResizeDirective from './directives/resize.js'
16
17
  import { getResource } from './utils/resource.js'
17
18
  import { deprecate } from './utils/deprecate.js'
18
19
  import { formatQuery } from './utils/route.js'
@@ -198,7 +199,7 @@ export default class DitoAdmin {
198
199
  componentName: 'VueNotifications'
199
200
  })
200
201
 
201
- // root.component('vue-modal', VueModal)
202
+ app.directive('resize', ResizeDirective)
202
203
 
203
204
  app.use(
204
205
  createRouter({
@@ -45,7 +45,9 @@ export default DitoComponent.component('DitoContainer', {
45
45
  single: { type: Boolean, default: false },
46
46
  nested: { type: Boolean, default: true },
47
47
  disabled: { type: Boolean, required: true },
48
- generateLabels: { type: Boolean, default: false }
48
+ generateLabels: { type: Boolean, default: false },
49
+ firstInRow: { type: Boolean, default: false },
50
+ lastInRow: { type: Boolean, default: false }
49
51
  },
50
52
 
51
53
  data() {
@@ -129,6 +131,9 @@ export default DitoComponent.component('DitoContainer', {
129
131
  [`${prefix}--has-label`]: this.hasLabel,
130
132
  [`${prefix}--aligned`]: keepAligned(this.schema),
131
133
  [`${prefix}--omit-padding`]: omitPadding(this.schema),
134
+ [`${prefix}--first-in-row`]: this.firstInRow,
135
+ [`${prefix}--last-in-row`]: this.lastInRow,
136
+ [`${prefix}--alone-in-row`]: this.firstInRow && this.lastInRow,
132
137
  ...(
133
138
  isString(containerClass)
134
139
  ? { [containerClass]: true }
@@ -212,19 +217,22 @@ export default DitoComponent.component('DitoContainer', {
212
217
  }
213
218
 
214
219
  &--aligned {
215
- // To align components with and without labels.
216
- justify-content: space-between;
220
+ // For components with labels, align the label at the top and the component
221
+ // at the bottom.
222
+ --justify: space-between;
217
223
 
218
224
  &:has(> :only-child) {
219
- justify-content: flex-end;
225
+ // But if there is no label, still align the component to the bottom.
226
+ --justify: flex-end;
220
227
  }
221
228
 
222
- // Don't align if neighbouring components aren't aligned either.
229
+ // Now only apply alignment if there are neighbouring components no the same
230
+ // row that also align.
223
231
  // Look ahead:
224
- #{$self}:not(#{&}) + &,
232
+ &:not(#{$self}--last-in-row) + #{&}:not(#{$self}--first-in-row),
225
233
  // Look behind:
226
- &:has(+ #{$self}:not(#{&})) {
227
- justify-content: flex-start;
234
+ &:not(#{$self}--last-in-row):has(+ #{&}:not(#{$self}--first-in-row)) {
235
+ justify-content: var(--justify);
228
236
  }
229
237
  }
230
238
 
@@ -14,7 +14,7 @@
14
14
  nestedDataPath,
15
15
  nested,
16
16
  store
17
- } in componentSchemas`
17
+ }, index in componentSchemas`
18
18
  )
19
19
  .dito-break(
20
20
  v-if="schema.break === 'before'"
@@ -22,6 +22,7 @@
22
22
  DitoContainer(
23
23
  v-if="shouldRender(schema)"
24
24
  :key="nestedDataPath"
25
+ v-resize="event => onResize(index, event)"
25
26
  :schema="schema"
26
27
  :dataPath="dataPath"
27
28
  :data="data"
@@ -31,6 +32,8 @@
31
32
  :nested="nested"
32
33
  :disabled="disabled"
33
34
  :generateLabels="generateLabels"
35
+ :firstInRow="schema.break === 'before' || isFirstInRow(index)"
36
+ :lastInRow="schema.break === 'after' || isLastInRow(index)"
34
37
  )
35
38
  .dito-break(
36
39
  v-if="schema.break === 'after'"
@@ -63,6 +66,12 @@ export default DitoComponent.component('DitoPane', {
63
66
  generateLabels: { type: Boolean, default: false }
64
67
  },
65
68
 
69
+ data() {
70
+ return {
71
+ positions: []
72
+ }
73
+ },
74
+
66
75
  computed: {
67
76
  tabComponent() {
68
77
  return this.tab ? this : this.$tabComponent()
@@ -135,9 +144,42 @@ export default DitoComponent.component('DitoPane', {
135
144
  if (this.tab) {
136
145
  this.$router.push({ hash: `#${this.tab}` })
137
146
  }
147
+ },
148
+
149
+ onResize(index, { target }) {
150
+ const { y, width, height } = target.getBoundingClientRect()
151
+ this.positions[index] = width > 0 && height > 0 ? y : null
152
+ },
153
+
154
+ isFirstInRow(index) {
155
+ const { positions } = this
156
+ return (
157
+ positions[index] !== null && (
158
+ index === 0 ||
159
+ (findNextPosition(positions, index, -1, Infinity) < positions[index])
160
+ )
161
+ )
162
+ },
163
+
164
+ isLastInRow(index) {
165
+ const { positions } = this
166
+ return (
167
+ positions[index] !== null && (
168
+ index === positions.length - 1 ||
169
+ findNextPosition(positions, index, +1, 0) > positions[index]
170
+ )
171
+ )
138
172
  }
139
173
  }
140
174
  })
175
+
176
+ function findNextPosition(positions, index, step, fallback) {
177
+ for (let i = index + step; i >= 0 && i < positions.length; i += step) {
178
+ const position = positions[i]
179
+ if (position) return position
180
+ }
181
+ return fallback
182
+ }
141
183
  </script>
142
184
 
143
185
  <style lang="scss">
@@ -164,8 +164,8 @@ export default DitoComponent.component('DitoPanel', {
164
164
  @import '../styles/_imports';
165
165
 
166
166
  .dito-panel {
167
- & + & {
168
- margin-top: $content-padding;
167
+ &:not(:last-child) {
168
+ margin-bottom: $content-padding;
169
169
  }
170
170
 
171
171
  &__header {
@@ -0,0 +1,83 @@
1
+ import { asArray } from '@ditojs/utils'
2
+
3
+ export default {
4
+ mounted(node, binding) {
5
+ observeResize(node, binding.value, binding.arg)
6
+ },
7
+
8
+ unmounted(node, binding) {
9
+ unobserveResize(node, binding.value, binding.arg)
10
+ }
11
+ }
12
+
13
+ export function observeResize(node, handler, options) {
14
+ Observer.getObserver(options).observe(node, handler)
15
+ }
16
+
17
+ export function unobserveResize(node, handler, options) {
18
+ Observer.getObserver(options).unobserve(node, handler)
19
+ }
20
+
21
+ export const isResizeSupported = typeof ResizeObserver !== 'undefined'
22
+
23
+ const observers = {}
24
+
25
+ class Observer {
26
+ constructor(key, options) {
27
+ this.key = key
28
+ this.options = options
29
+ this.observer = isResizeSupported
30
+ ? new ResizeObserver(entries => this.handle(entries))
31
+ : null
32
+ this.handlersByNode = new WeakMap()
33
+ this.nodeCount = 0
34
+ }
35
+
36
+ observe(node, handler) {
37
+ let handlers = this.handlersByNode.get(node)
38
+ if (!handlers) {
39
+ handlers = new Set()
40
+ this.handlersByNode.set(node, handlers)
41
+ this.observer?.observe(node, this.options)
42
+ this.nodeCount++
43
+ }
44
+ handlers.add(handler)
45
+ }
46
+
47
+ unobserve(node, handler) {
48
+ const handlers = this.handlersByNode.get(node)
49
+ if (handlers?.delete(handler) && handlers.size === 0) {
50
+ this.handlersByNode.delete(node)
51
+ this.observer?.unobserve(node)
52
+ if (--this.nodeCount === 0) {
53
+ delete observers[this.key]
54
+ }
55
+ }
56
+ }
57
+
58
+ handle(entries) {
59
+ for (const entry of entries) {
60
+ const handlers = this.handlersByNode.get(entry.target)
61
+ if (handlers) {
62
+ const event = {
63
+ target: entry.target,
64
+ contentRect: entry.contentRect,
65
+ // Use `asArray` since Firefox before v92 returns these as objects:
66
+ borderBoxSize: asArray(entry.borderBoxSize),
67
+ contentBoxSize: asArray(entry.contentBoxSize),
68
+ devicePixelContentBoxSize: asArray(entry.devicePixelContentBoxSize)
69
+ }
70
+ for (const handler of handlers) {
71
+ handler(event)
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ static getObserver({ box = 'content-box' } = {}) {
78
+ const options = { box }
79
+ const key = JSON.stringify(options)
80
+ observers[key] ||= new Observer(key, options)
81
+ return observers[key]
82
+ }
83
+ }