@ditojs/admin 3.2.2 → 3.2.4

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": "3.2.2",
3
+ "version": "3.2.4",
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/main/packages/admin",
@@ -131,5 +131,5 @@
131
131
  "vue-router": "^5.3.1",
132
132
  "vue-upload-component": "^3.2.2"
133
133
  },
134
- "gitHead": "04e5e45534cf76e1472d62c5fb9203d37d0161f7"
134
+ "gitHead": "32147a4ee71f5bca6c051af04be9c15a16f37567"
135
135
  }
@@ -149,16 +149,16 @@ export default class DitoContext {
149
149
  }
150
150
 
151
151
  get value() {
152
- return get(
153
- this,
154
- 'value',
155
- () =>
156
- // If the value is not defined on the underlying object, it's not a type
157
- // component. If it is nested, we can still get the value from the root.
158
- this.nested
159
- ? getValueAtDataPath(this.rootItem, this.dataPath)
160
- : undefined
161
- )
152
+ const object = toObject(this)
153
+ // Type components hold their value, also while it's `undefined`, e.g. a
154
+ // computed value that the data model hasn't written yet. Other components
155
+ // don't hold a value, but if they're nested, we can get it from the root,
156
+ // or `undefined` if it's missing there.
157
+ return 'value' in object
158
+ ? object.value
159
+ : this.nested
160
+ ? getValueAtDataPath(this.rootItem, this.dataPath, () => undefined)
161
+ : undefined
162
162
  }
163
163
 
164
164
  get dataPath() {
@@ -32,6 +32,7 @@
32
32
  </template>
33
33
 
34
34
  <script>
35
+ import { markRaw } from 'vue'
35
36
  import { clone } from '@ditojs/utils'
36
37
  import DitoComponent from '../DitoComponent.js'
37
38
  import DomMixin from '../mixins/DomMixin.js'
@@ -47,8 +48,7 @@ export default DitoComponent.component('DitoDialog', {
47
48
 
48
49
  provide() {
49
50
  return {
50
- $dialogComponent: () => this,
51
- $dataModel: () => this.ownDataModel
51
+ $dialogComponent: () => this
52
52
  }
53
53
  },
54
54
 
@@ -80,11 +80,6 @@ export default DitoComponent.component('DitoDialog', {
80
80
  },
81
81
 
82
82
  computed: {
83
- // @override DitoMixin.dataModel
84
- dataModel() {
85
- return this.ownDataModel
86
- },
87
-
88
83
  dialogComponent() {
89
84
  return this
90
85
  },
@@ -136,11 +131,13 @@ export default DitoComponent.component('DitoDialog', {
136
131
  created() {
137
132
  // Writes defaults and computed values into the dialog's data and resolves
138
133
  // options, see `DataModel`:
139
- this.ownDataModel = new DataModel({
140
- component: this,
141
- getSchema: () => this.schema,
142
- getData: () => this.dialogData
143
- })
134
+ this.ownDataModel = markRaw(
135
+ new DataModel({
136
+ component: this,
137
+ getSchema: () => this.schema,
138
+ getData: () => this.dialogData
139
+ })
140
+ )
144
141
  },
145
142
 
146
143
  mounted() {
@@ -51,6 +51,7 @@
51
51
  </template>
52
52
 
53
53
  <script>
54
+ import { markRaw } from 'vue'
54
55
  import { clone, capitalize, parseDataPath, assignDeeply } from '@ditojs/utils'
55
56
  import DitoComponent from '../DitoComponent.js'
56
57
  import RouteMixin from '../mixins/RouteMixin.js'
@@ -66,12 +67,6 @@ import { DataModel } from '../utils/DataModel.js'
66
67
  export default DitoComponent.component('DitoForm', {
67
68
  mixins: [RouteMixin, ResourceMixin],
68
69
 
69
- provide() {
70
- return {
71
- $dataModel: () => this.ownDataModel
72
- }
73
- },
74
-
75
70
  data() {
76
71
  return {
77
72
  createdData: null,
@@ -80,11 +75,6 @@ export default DitoComponent.component('DitoForm', {
80
75
  },
81
76
 
82
77
  computed: {
83
- // @override DitoMixin.dataModel
84
- dataModel() {
85
- return this.ownDataModel
86
- },
87
-
88
78
  isForm() {
89
79
  return true
90
80
  },
@@ -366,12 +356,14 @@ export default DitoComponent.component('DitoForm', {
366
356
  created() {
367
357
  // Writes defaults and computed values into the form's data, resolves
368
358
  // options and tracks whether the data is dirty, see `DataModel`:
369
- this.ownDataModel = new DataModel({
370
- component: this,
371
- getSchema: () => this.schema,
372
- getData: () => this.data,
373
- getSourceSchema: () => this.sourceSchema
374
- })
359
+ this.ownDataModel = markRaw(
360
+ new DataModel({
361
+ component: this,
362
+ getSchema: () => this.schema,
363
+ getData: () => this.data,
364
+ getSourceSchema: () => this.sourceSchema
365
+ })
366
+ )
375
367
  },
376
368
 
377
369
  beforeUnmount() {
@@ -28,6 +28,7 @@ template(
28
28
  </template>
29
29
 
30
30
  <script>
31
+ import { markRaw } from 'vue'
31
32
  import DitoComponent from '../DitoComponent.js'
32
33
  import RouteMixin from '../mixins/RouteMixin.js'
33
34
  import {
@@ -45,8 +46,7 @@ export default DitoComponent.component('DitoView', {
45
46
  // Redirect $sourceComponent and $resourceComponent to the main component:
46
47
  return {
47
48
  $sourceComponent: () => this.mainComponent?.sourceComponent || null,
48
- $resourceComponent: () => this.mainComponent?.resourceComponent || null,
49
- $dataModel: () => this.ownDataModel
49
+ $resourceComponent: () => this.mainComponent?.resourceComponent || null
50
50
  }
51
51
  },
52
52
 
@@ -61,11 +61,6 @@ export default DitoComponent.component('DitoView', {
61
61
  },
62
62
 
63
63
  computed: {
64
- // @override DitoMixin.dataModel
65
- dataModel() {
66
- return this.ownDataModel
67
- },
68
-
69
64
  schema() {
70
65
  return this.meta.schema ?? {}
71
66
  },
@@ -126,11 +121,13 @@ export default DitoComponent.component('DitoView', {
126
121
  created() {
127
122
  // Writes defaults and computed values into the view's data and resolves
128
123
  // options, see `DataModel`:
129
- this.ownDataModel = new DataModel({
130
- component: this,
131
- getSchema: () => this.viewSchema,
132
- getData: () => this.data
133
- })
124
+ this.ownDataModel = markRaw(
125
+ new DataModel({
126
+ component: this,
127
+ getSchema: () => this.viewSchema,
128
+ getData: () => this.data
129
+ })
130
+ )
134
131
  },
135
132
 
136
133
  mounted() {
@@ -39,29 +39,28 @@ export default {
39
39
 
40
40
  provide() {
41
41
  const self = () => this
42
- return this.providesData
43
- ? {
44
- $parentComponent: self,
45
- $dataComponent: self
46
- }
47
- : {
48
- $parentComponent: self
49
- }
42
+ return {
43
+ $parentComponent: self,
44
+ ...(this.providesData && { $dataComponent: self }),
45
+ $dataModel: () => this.dataModel
46
+ }
50
47
  },
51
48
 
52
49
  data() {
53
50
  return {
54
51
  appState,
55
52
  isMounted: false,
56
- overrides: null // See accessor.js
53
+ overrides: null, // See accessor.js
54
+ // The data model that the component owns, see `dataModel`.
55
+ ownDataModel: null
57
56
  }
58
57
  },
59
58
 
60
59
  computed: {
61
- // The data model, provided by the form, view or dialog that owns it, which
62
- // override it with their own.
60
+ // The data model of the data: the component's own, e.g. of a form, view
61
+ // or dialog, or else the one of its closest ancestor that owns one.
63
62
  dataModel() {
64
- return this.$dataModel()
63
+ return this.ownDataModel ?? this.$dataModel()
65
64
  },
66
65
 
67
66
  providesData() {
@@ -68,10 +68,12 @@ export default {
68
68
  // Whether the value needs to be replaced with the selected value, once the
69
69
  // options are available: if the value is forced to `null` because its
70
70
  // option disappeared, or if the value is a reference, so that it'll hold
71
- // actual data, not just a reference id. See `watch`.
71
+ // actual data, not just a reference id. See `watch`. Filtered options, e.g.
72
+ // the results of a search, don't tell whether the value's option exists.
72
73
  shouldReplaceValueWithSelectedValue() {
73
74
  return (
74
- this.hasOptions && (
75
+ this.hasOptions &&
76
+ !this.areOptionsFiltered && (
75
77
  this.selectedValue === null && this.value !== null ||
76
78
  isReference(this.value)
77
79
  )
@@ -114,6 +116,12 @@ export default {
114
116
  return this.options
115
117
  },
116
118
 
119
+ // Whether the active options are a filtered subset of the options, e.g.
120
+ // the results of a search, see `activeOptions`.
121
+ areOptionsFiltered() {
122
+ return this.activeOptions !== this.options
123
+ },
124
+
117
125
  hasOptions() {
118
126
  return this.activeOptions.length > 0
119
127
  },
@@ -304,8 +312,16 @@ export default {
304
312
  }
305
313
  }
306
314
 
307
- return this.optionValue
308
- ? findOption(this.activeOptions, value, this.groupBy)
315
+ const { optionValue, groupBy } = this
316
+ return optionValue
317
+ ? (findOption(this.activeOptions, value, groupBy) ??
318
+ // Options filtered out of the active options, e.g. by a search, are
319
+ // still options of the value.
320
+ (
321
+ this.areOptionsFiltered
322
+ ? findOption(this.options, value, groupBy)
323
+ : undefined
324
+ ))
309
325
  : value
310
326
  },
311
327
 
@@ -92,7 +92,7 @@ export class DataModel {
92
92
  watch(
93
93
  [getSchema, getData],
94
94
  ([schema, data]) => this.initializeData(schema, data),
95
- modelWatchOptions
95
+ modelSetupWatchOptions
96
96
  )
97
97
  // The entries are read through a computed property, so that the watchers
98
98
  // of the entries can check synchronously whether their entry is still
@@ -101,19 +101,18 @@ export class DataModel {
101
101
  watch(
102
102
  () => this.dataEntries.value.computedValueEntries,
103
103
  entries => this.updateComputedValueRecords(entries),
104
- modelWatchOptions
104
+ modelSetupWatchOptions
105
105
  )
106
106
  // Values that go missing after the data was set up, e.g. in items that
107
107
  // code adds, get their defaults too:
108
108
  watch(
109
109
  () => this.dataEntries.value.entriesWithMissingValues,
110
110
  entries => this.setDefaultValues(entries),
111
- modelWatchOptions
111
+ modelSetupWatchOptions
112
112
  )
113
113
  if (getSourceSchema) {
114
114
  // Data that is set up, e.g. loaded, saved or applied, isn't dirty:
115
- // After the data is set up, as the watchers run in the order in which
116
- // they're created, see `modelWatchOptions`.
115
+ // After the data is set up, see `modelSetupWatchOptions`.
117
116
  watch(
118
117
  getData,
119
118
  () => this.takeProcessedDataSnapshot().catch(console.error),
@@ -641,13 +640,20 @@ function takeOverChangedValues(target, before, after) {
641
640
 
642
641
  const notFound = Symbol('notFound')
643
642
 
644
- // The watchers of the model run after the post-flush hooks, so that they write
645
- // the values that they derive from the data after the components that display
646
- // them are mounted. Otherwise, the `mounted` hooks of `v-model` directives
647
- // would restore the values that inputs had when they were mounted, see
648
- // https://github.com/vuejs/core/issues/15774. Vue processes the updates that
649
- // the written values cause in the same flush, before the browser renders them.
650
- // The watchers run in the order in which they're created.
643
+ // The watchers of the model that set up the data and the scopes of its
644
+ // components run before the components render, so that components of new
645
+ // data, e.g. loaded data or added items, receive their defaults and computed
646
+ // values from the start: The scopes compute their values right away, see
647
+ // `createComputedValueScope()`.
648
+ const modelSetupWatchOptions = { immediate: true, flush: 'pre' }
649
+
650
+ // The other watchers of the model run after the post-flush hooks, so that they
651
+ // write the values that they derive from the data after the components that
652
+ // display them are mounted. Otherwise, the `mounted` hooks of `v-model`
653
+ // directives would restore the values that inputs had when they were mounted,
654
+ // see https://github.com/vuejs/core/issues/15774. Vue processes the updates
655
+ // that the written values cause in the same flush, before the browser renders
656
+ // them.
651
657
  const modelWatchOptions = { immediate: true, flush: 'post' }
652
658
 
653
659
  // Aborts `compute()` when it reads options that aren't loaded, see