@ditojs/admin 3.2.1 → 3.2.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": "3.2.1",
3
+ "version": "3.2.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/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": "c81ea5a206255d00588ea8b23f9df34af52cae01"
134
+ "gitHead": "04e5e45534cf76e1472d62c5fb9203d37d0161f7"
135
135
  }
package/src/DitoAdmin.js CHANGED
@@ -177,6 +177,7 @@ export default class DitoAdmin {
177
177
  $sourceComponent: () => null,
178
178
  $resourceComponent: () => null,
179
179
  $dialogComponent: () => null,
180
+ $dataModel: () => null,
180
181
  $panelComponent: () => null,
181
182
  $tabComponent: () => null
182
183
  },
@@ -36,7 +36,7 @@ import { clone } from '@ditojs/utils'
36
36
  import DitoComponent from '../DitoComponent.js'
37
37
  import DomMixin from '../mixins/DomMixin.js'
38
38
  import { getButtonSchemas } from '../utils/schema/lookup.js'
39
- import { FormModel } from '../utils/FormModel.js'
39
+ import { DataModel } from '../utils/DataModel.js'
40
40
  import { UseFocusTrap } from '@vueuse/integrations/useFocusTrap/component'
41
41
 
42
42
  // @vue/component
@@ -47,7 +47,8 @@ export default DitoComponent.component('DitoDialog', {
47
47
 
48
48
  provide() {
49
49
  return {
50
- $dialogComponent: () => this
50
+ $dialogComponent: () => this,
51
+ $dataModel: () => this.ownDataModel
51
52
  }
52
53
  },
53
54
 
@@ -79,6 +80,11 @@ export default DitoComponent.component('DitoDialog', {
79
80
  },
80
81
 
81
82
  computed: {
83
+ // @override DitoMixin.dataModel
84
+ dataModel() {
85
+ return this.ownDataModel
86
+ },
87
+
82
88
  dialogComponent() {
83
89
  return this
84
90
  },
@@ -129,8 +135,8 @@ export default DitoComponent.component('DitoDialog', {
129
135
 
130
136
  created() {
131
137
  // Writes defaults and computed values into the dialog's data and resolves
132
- // options, see `FormModel`:
133
- this.formModel = new FormModel({
138
+ // options, see `DataModel`:
139
+ this.ownDataModel = new DataModel({
134
140
  component: this,
135
141
  getSchema: () => this.schema,
136
142
  getData: () => this.dialogData
@@ -149,8 +155,8 @@ export default DitoComponent.component('DitoDialog', {
149
155
 
150
156
  beforeUnmount() {
151
157
  // Stop the model before the component's own watchers are stopped, as the
152
- // model's watchers aren't part of the component, see `FormModel`.
153
- this.formModel.stop()
158
+ // model's watchers aren't part of the component, see `DataModel`.
159
+ this.ownDataModel.stop()
154
160
  },
155
161
 
156
162
  methods: {
@@ -179,7 +185,7 @@ export default DitoComponent.component('DitoDialog', {
179
185
 
180
186
  async submit() {
181
187
  // Wait for computed values that depend on pending loads, e.g. options.
182
- await this.waitForPendingLoadsOf(this.formModel)
188
+ await this.waitForPendingLoadsOf(this.dataModel)
183
189
  this.resolve(this.dialogData)
184
190
  },
185
191
 
@@ -60,12 +60,18 @@ import { isObjectSource } from '../utils/schema/structure.js'
60
60
  import { getButtonSchemas } from '../utils/schema/lookup.js'
61
61
  import { getComponentPathByDataPath } from '../utils/schema/data.js'
62
62
  import { resolvePath } from '../utils/path.js'
63
- import { FormModel } from '../utils/FormModel.js'
63
+ import { DataModel } from '../utils/DataModel.js'
64
64
 
65
65
  // @vue/component
66
66
  export default DitoComponent.component('DitoForm', {
67
67
  mixins: [RouteMixin, ResourceMixin],
68
68
 
69
+ provide() {
70
+ return {
71
+ $dataModel: () => this.ownDataModel
72
+ }
73
+ },
74
+
69
75
  data() {
70
76
  return {
71
77
  createdData: null,
@@ -74,6 +80,11 @@ export default DitoComponent.component('DitoForm', {
74
80
  },
75
81
 
76
82
  computed: {
83
+ // @override DitoMixin.dataModel
84
+ dataModel() {
85
+ return this.ownDataModel
86
+ },
87
+
77
88
  isForm() {
78
89
  return true
79
90
  },
@@ -149,7 +160,7 @@ export default DitoComponent.component('DitoForm', {
149
160
  isDirty() {
150
161
  // Forms that mutate the inherited data directly aren't dirty themselves,
151
162
  // their changes make the form that owns the data dirty instead.
152
- return !this.isMutating && this.formModel.isDirty
163
+ return !this.isMutating && this.dataModel.isDirty
153
164
  },
154
165
 
155
166
  isMutating() {
@@ -354,8 +365,8 @@ export default DitoComponent.component('DitoForm', {
354
365
 
355
366
  created() {
356
367
  // Writes defaults and computed values into the form's data, resolves
357
- // options and tracks whether the data is dirty, see `FormModel`:
358
- this.formModel = new FormModel({
368
+ // options and tracks whether the data is dirty, see `DataModel`:
369
+ this.ownDataModel = new DataModel({
359
370
  component: this,
360
371
  getSchema: () => this.schema,
361
372
  getData: () => this.data,
@@ -365,8 +376,8 @@ export default DitoComponent.component('DitoForm', {
365
376
 
366
377
  beforeUnmount() {
367
378
  // Stop the model before the component's own watchers are stopped, as the
368
- // model's watchers aren't part of the component, see `FormModel`.
369
- this.formModel.stop()
379
+ // model's watchers aren't part of the component, see `DataModel`.
380
+ this.ownDataModel.stop()
370
381
  },
371
382
 
372
383
  methods: {
@@ -437,9 +448,9 @@ export default DitoComponent.component('DitoForm', {
437
448
 
438
449
  // Makes clean changes to the data, which don't make the form dirty, e.g. to
439
450
  // apply what an action on the server already saved, see
440
- // `FormModel.applyCleanChanges()`.
451
+ // `DataModel.applyCleanChanges()`.
441
452
  applyCleanChanges(makeChanges) {
442
- return this.formModel.applyCleanChanges(makeChanges)
453
+ return this.dataModel.applyCleanChanges(makeChanges)
443
454
  },
444
455
 
445
456
  async cancel() {
@@ -462,7 +473,7 @@ export default DitoComponent.component('DitoForm', {
462
473
 
463
474
  async submit(button, { validate = true, closeForm = false } = {}) {
464
475
  // Wait for computed values that depend on pending loads, e.g. options.
465
- await this.waitForPendingLoadsOf(this.formModel)
476
+ await this.waitForPendingLoadsOf(this.dataModel)
466
477
  if (validate && !this.validateAll()) {
467
478
  return false
468
479
  }
@@ -545,7 +556,7 @@ export default DitoComponent.component('DitoForm', {
545
556
  this.resetValidation()
546
557
  // The saved or applied data isn't dirty, also when it isn't replaced,
547
558
  // e.g. by a response without data, and before the closing below:
548
- this.formModel
559
+ this.dataModel
549
560
  .takeProcessedDataSnapshot()
550
561
  .catch(console.error)
551
562
  if (closeForm || button.closeForm) {
@@ -35,7 +35,7 @@ import {
35
35
  someNestedSchemaComponent
36
36
  } from '../utils/schema/structure.js'
37
37
  import { hasResource } from '../utils/resource.js'
38
- import { FormModel } from '../utils/FormModel.js'
38
+ import { DataModel } from '../utils/DataModel.js'
39
39
 
40
40
  // @vue/component
41
41
  export default DitoComponent.component('DitoView', {
@@ -45,7 +45,8 @@ export default DitoComponent.component('DitoView', {
45
45
  // Redirect $sourceComponent and $resourceComponent to the main component:
46
46
  return {
47
47
  $sourceComponent: () => this.mainComponent?.sourceComponent || null,
48
- $resourceComponent: () => this.mainComponent?.resourceComponent || null
48
+ $resourceComponent: () => this.mainComponent?.resourceComponent || null,
49
+ $dataModel: () => this.ownDataModel
49
50
  }
50
51
  },
51
52
 
@@ -60,6 +61,11 @@ export default DitoComponent.component('DitoView', {
60
61
  },
61
62
 
62
63
  computed: {
64
+ // @override DitoMixin.dataModel
65
+ dataModel() {
66
+ return this.ownDataModel
67
+ },
68
+
63
69
  schema() {
64
70
  return this.meta.schema ?? {}
65
71
  },
@@ -119,8 +125,8 @@ export default DitoComponent.component('DitoView', {
119
125
 
120
126
  created() {
121
127
  // Writes defaults and computed values into the view's data and resolves
122
- // options, see `FormModel`:
123
- this.formModel = new FormModel({
128
+ // options, see `DataModel`:
129
+ this.ownDataModel = new DataModel({
124
130
  component: this,
125
131
  getSchema: () => this.viewSchema,
126
132
  getData: () => this.data
@@ -136,8 +142,8 @@ export default DitoComponent.component('DitoView', {
136
142
 
137
143
  beforeUnmount() {
138
144
  // Stop the model before the component's own watchers are stopped, as the
139
- // model's watchers aren't part of the component, see `FormModel`.
140
- this.formModel.stop()
145
+ // model's watchers aren't part of the component, see `DataModel`.
146
+ this.ownDataModel.stop()
141
147
  },
142
148
 
143
149
  methods: {
@@ -32,6 +32,7 @@ export default {
32
32
  '$sourceComponent',
33
33
  '$resourceComponent',
34
34
  '$dialogComponent',
35
+ '$dataModel',
35
36
  '$panelComponent',
36
37
  '$tabComponent'
37
38
  ],
@@ -57,6 +58,12 @@ export default {
57
58
  },
58
59
 
59
60
  computed: {
61
+ // The data model, provided by the form, view or dialog that owns it, which
62
+ // override it with their own.
63
+ dataModel() {
64
+ return this.$dataModel()
65
+ },
66
+
60
67
  providesData() {
61
68
  // NOTE: This is overridden in ResourceMixin, used by lists.
62
69
  return false
@@ -491,12 +498,12 @@ export default {
491
498
  : parentComponentPath
492
499
  },
493
500
 
494
- // Waits for the pending loads of the form model, e.g. before submitting,
501
+ // Waits for the pending loads of the data model, e.g. before submitting,
495
502
  // showing the spinner in the header in the meantime, like requests do.
496
- async waitForPendingLoadsOf(formModel) {
503
+ async waitForPendingLoadsOf(dataModel) {
497
504
  this.rootComponent.registerLoading(true)
498
505
  try {
499
- await formModel.waitForPendingLoads()
506
+ await dataModel.waitForPendingLoads()
500
507
  } finally {
501
508
  this.rootComponent.registerLoading(false)
502
509
  }
@@ -615,17 +622,23 @@ export default {
615
622
  : handleParentListeners(undefined)
616
623
 
617
624
  return ['load', 'change'].includes(event)
618
- ? // The effects of some events need time to propagate through Vue.
619
- // Use $nextTick() to make sure our handlers see these changes.
620
- // For example, `processedItem` is only correct after components
621
- // that are newly rendered due to data changes have registered.
622
- // NOTE: The result of `handleListeners()` makes it through the
623
- // `$nextTick()` call and will be returned as expected.
624
- this.$nextTick(handleListeners)
625
+ ? // The effects of these events need time to propagate, so that the
626
+ // handlers see them: Components that are newly rendered due to
627
+ // data changes register, e.g. for `processedItem`, and the form
628
+ // model writes the values that it derives from the data.
629
+ // NOTE: The result of `handleListeners()` is returned as expected.
630
+ this.waitForDataModel().then(handleListeners)
625
631
  : handleListeners()
626
632
  }
627
633
  },
628
634
 
635
+ // Waits until the data model settled: its loads finished, and it wrote the
636
+ // values that it derives from the data.
637
+ async waitForDataModel() {
638
+ await this.$nextTick()
639
+ await this.dataModel?.waitForPendingLoads()
640
+ },
641
+
629
642
  emitSchemaEvent(event, params) {
630
643
  return this.schemaComponent.emitEvent(event, params)
631
644
  }
@@ -82,12 +82,12 @@ export default {
82
82
  return this.getOptionForValue(this.selectedValue)
83
83
  },
84
84
 
85
- // The resolver of the options in the form model of the data, which loads
86
- // them and shares them with the computes of the form. It is looked up with
87
- // the entry of this component, in the shape of the entries of
88
- // `processSchemaData()`, see `FormModel`.
85
+ // The resolver of the options in the data model, which loads them and
86
+ // shares them with the computes of the form. It is looked up with the entry
87
+ // of this component, in the shape of the entries of `processSchemaData()`,
88
+ // see `DataModel`.
89
89
  optionsResolver() {
90
- return this.formModel.getOptionsResolver({
90
+ return this.dataModel.getOptionsResolver({
91
91
  schema: this.schema,
92
92
  data: this.data,
93
93
  name: this.name,
@@ -247,7 +247,7 @@ export default {
247
247
  // made as a clean change, which doesn't make the form dirty.
248
248
  replaceValueWithSelectedValue() {
249
249
  const { selectedValue } = this
250
- this.formModel
250
+ this.dataModel
251
251
  .applyCleanChanges(() => {
252
252
  this.selectedValue = selectedValue
253
253
  })
@@ -129,7 +129,7 @@ export default {
129
129
  // `DitoRouterView`. Records of other paths, or none, mean that
130
130
  // the component is being left, and it keeps its record until it's
131
131
  // unmounted, as its schema and meta are still read in the meantime,
132
- // e.g. by the watchers of its form model. Records of the same path
132
+ // e.g. by the watchers of its data model. Records of the same path
133
133
  // replace it, e.g. when the routes are set up again.
134
134
  const isOwnRouteRecord = (
135
135
  !!routeRecord &&
@@ -481,7 +481,7 @@ export default {
481
481
  // their order key with the offset of the loaded page, so that their order
482
482
  // can be stored even if it never changes, e.g. by a button that saves the
483
483
  // current order. Lists in the data of forms, views and dialogs are
484
- // numbered by `FormModel`, see `initializeData()`.
484
+ // numbered by `DataModel`, see `initializeData()`.
485
485
  setLoadedListItems(items) {
486
486
  this.setLoadedValue(
487
487
  updateOrder(this.sourceSchema, items, this.paginationRange)
@@ -491,9 +491,9 @@ export default {
491
491
  // Writes the value loaded through the source's resource. Loading isn't an
492
492
  // edit of the data, so the value is written as a clean change, which
493
493
  // doesn't make the form dirty, e.g. for sources whose value is stored,
494
- // like the order of their items, see `FormModel.applyCleanChanges()`.
494
+ // like the order of their items, see `DataModel.applyCleanChanges()`.
495
495
  setLoadedValue(value) {
496
- this.formModel
496
+ this.dataModel
497
497
  .applyCleanChanges(() => {
498
498
  this.value = value
499
499
  })
@@ -37,11 +37,6 @@ export default {
37
37
  return this.schema.name
38
38
  },
39
39
 
40
- // The form model of the data, owned by the dialog or route component.
41
- formModel() {
42
- return (this.dialogComponent ?? this.routeComponent).formModel
43
- },
44
-
45
40
  type() {
46
41
  return this.schema.type
47
42
  },
@@ -197,17 +192,17 @@ export default {
197
192
  },
198
193
 
199
194
  methods: {
200
- // Only the form model calls `compute()`, see `computeValue()`. Components
195
+ // Only the data model calls `compute()`, see `computeValue()`. Components
201
196
  // that it doesn't visit, e.g. in panels with their own data, never get
202
197
  // their values computed.
203
198
  warnAboutUncomputedValue() {
204
199
  if (
205
200
  this.schema.compute &&
206
- !this.formModel.hasComputedValueEntry(this.componentPath)
201
+ !this.dataModel.hasComputedValueEntry(this.componentPath)
207
202
  ) {
208
203
  console.warn(
209
204
  `The value of the component at '${this.componentPath}' isn't ` +
210
- `computed, as its data isn't part of a form model.`
205
+ `computed, as its data isn't part of a data model.`
211
206
  )
212
207
  }
213
208
  },
@@ -6,7 +6,7 @@ export default {
6
6
  computed: {
7
7
  value: {
8
8
  get() {
9
- // `FormModel` writes computed values into the data, see
9
+ // `DataModel` writes computed values into the data, see
10
10
  // `computeValue()`.
11
11
  const value = getValueOrDefault(
12
12
  this.schema,
@@ -20,7 +20,7 @@ export default DitoTypeComponent.register(
20
20
  defaultValue: () => undefined, // Callback to override `defaultValue: null`
21
21
  defaultVisible: false,
22
22
  // The value is resolved from `schema.data` or `schema.dataPath` by
23
- // `FormModel`, see `hasValueFromDataSchema()`:
23
+ // `DataModel`, see `hasValueFromDataSchema()`:
24
24
  valueFromDataSchema: true
25
25
  }
26
26
  )
@@ -22,7 +22,7 @@ import {
22
22
  processData
23
23
  } from './schema/data.js'
24
24
 
25
- // FormModel holds the state of the data edited by a form, view or dialog that
25
+ // DataModel holds the state of the data edited by a form, view or dialog that
26
26
  // is derived from schema and data, independently of what is rendered:
27
27
  //
28
28
  // - Missing values are set to their defaults when the data is set up, and when
@@ -39,7 +39,7 @@ import {
39
39
  // - Options, `schema.options`, are resolved per component when they are first
40
40
  // read, by `compute()` through `context.options` or by the component that
41
41
  // displays them, see `getOptions()`. Both get the same option objects, and
42
- // computes that read options run again when they are loaded.
42
+ // computes that read options only run once they are loaded.
43
43
  // - Submitting waits for the loads of data schemas and options that are still
44
44
  // pending, see `waitForPendingLoads()`.
45
45
  // - The data is dirty when its processed data differs from a snapshot taken
@@ -60,7 +60,7 @@ import {
60
60
  // `dataPath`, `componentPath`, `rootData` and `mainSchemaComponent` are used
61
61
  // when present. The model needs to be stopped before the component unmounts.
62
62
 
63
- export class FormModel {
63
+ export class DataModel {
64
64
  // The entries of the components with computed values and the scopes of the
65
65
  // watchers that write these values into the data, by component path:
66
66
  computedValueRecords = new Map()
@@ -651,7 +651,7 @@ const notFound = Symbol('notFound')
651
651
  const modelWatchOptions = { immediate: true, flush: 'post' }
652
652
 
653
653
  // Aborts `compute()` when it reads options that aren't loaded, see
654
- // `FormModel.getComputedValueResult()`.
654
+ // `DataModel.getComputedValueResult()`.
655
655
  const optionsNotLoaded = Symbol('optionsNotLoaded')
656
656
 
657
657
  // Returns whether the schema is a source of computed values, through
@@ -129,7 +129,7 @@ export function shouldIgnoreMissingValue(schema, context) {
129
129
  // the type treats that as missing, e.g. nested sections.
130
130
  export function isMissingValue(schema, data, name, context) {
131
131
  // Only read the value if needed, so that callers that are tracked, e.g. the
132
- // walk of the form model's data, don't depend on all values.
132
+ // walk of the data model's data, don't depend on all values.
133
133
  return (
134
134
  !(name in data) || (
135
135
  !!getTypeOptions(schema)?.treatNullAsMissing?.(getContext(context)) &&
@@ -142,7 +142,7 @@ export function isMissingValue(schema, data, name, context) {
142
142
  * Returns whether the value of the component described by `schema` is
143
143
  * resolved from its data schema, `schema.data` or `schema.dataPath`, as for
144
144
  * the types with the `valueFromDataSchema` option, e.g. `computed`. See
145
- * `FormModel`.
145
+ * `DataModel`.
146
146
  */
147
147
  export function hasValueFromDataSchema(schema) {
148
148
  return (
@@ -227,7 +227,7 @@ export function initializeData(schema, data = {}, component, {
227
227
  * Returns the value of the component described by `schema` and `name` in
228
228
  * `data`: the result of `schema.compute()` if it returns a value, else the
229
229
  * value in `data`, or its default if it's missing. Never writes into `data`:
230
- * `FormModel` is the only caller and writes the computed values, including the
230
+ * `DataModel` is the only caller and writes the computed values, including the
231
231
  * defaults of components with `compute()`. Other defaults are written when the
232
232
  * data is set up, see `initializeData()`, or when they go missing later. If
233
233
  * provided, `getOptions()` returns the options for `context.options`, called
@@ -265,7 +265,7 @@ export function computeValue(schema, data, name, dataPath, {
265
265
  return properties
266
266
  })
267
267
  const { compute } = schema
268
- // Like the form model, only compute values of components that are shown
268
+ // Like the data model, only compute values of components that are shown
269
269
  // through their `if`, as `compute()` may rely on the same conditions, e.g.
270
270
  // `if: ({ item }) => item.preview` with `compute: ({ item }) =>
271
271
  // item.preview.name`.
@@ -288,7 +288,7 @@ export function computeValue(schema, data, name, dataPath, {
288
288
 
289
289
  // Returns the value in `data`, or its default if it's missing. `context` can be
290
290
  // a function that creates the context, called only when it's needed. Computed
291
- // values are in `data` already, as `FormModel` writes them, see
291
+ // values are in `data` already, as `DataModel` writes them, see
292
292
  // `computeValue()`.
293
293
  export function getValueOrDefault(schema, data, name, context) {
294
294
  const shouldUseDefault = (
@@ -325,11 +325,11 @@ export function processData(schema, sourceSchema, data, dataPath, {
325
325
  schemaOnly, // whether to only include data covered by the schema, or all data
326
326
  target,
327
327
  // Whether to call the schema callbacks `process()`. Data that is compared
328
- // while it's edited, e.g. by `FormModel.isDirty`, isn't validated yet, which
328
+ // while it's edited, e.g. by `DataModel.isDirty`, isn't validated yet, which
329
329
  // the callbacks may rely on. Without them, the types still process the values
330
330
  // through `processValue()`, and the excluded values of components with
331
331
  // `process()` are kept, as `process()` may store them elsewhere through
332
- // `processedItem`. Computed values are in the data already, as `FormModel`
332
+ // `processedItem`. Computed values are in the data already, as `DataModel`
333
333
  // writes them, see `computeValue()`.
334
334
  shouldCallProcess = true
335
335
  } = {}) {
@@ -558,7 +558,7 @@ export function processSchemaData(schema, data, {
558
558
  before?.(entry)
559
559
 
560
560
  // Only read the values of sources with forms, so that callers that
561
- // are tracked, e.g. the walk of the form model's data, don't depend
561
+ // are tracked, e.g. the walk of the data model's data, don't depend
562
562
  // on all values.
563
563
  if (hasFormSchema(componentSchema)) {
564
564
  let value = processedData ? processedData[name] : data[name]
@@ -112,7 +112,7 @@ export function getFormSchemas(schema, context, modifyForm) {
112
112
  // Returns the form with the components that its `components()` callback
113
113
  // creates for the data of `context`. The forms are cached per data, and the
114
114
  // callback only creates new components when the data that it reads changes,
115
- // so that rendering and the walks of the form model, see `FormModel`, share
115
+ // so that rendering and the walks of the data model, see `DataModel`, share
116
116
  // the same component schemas. `components()` callbacks therefore derive the
117
117
  // components from the data of their context, not from its components.
118
118
  function getFormWithCreatedComponents(form, context) {
package/types/index.d.ts CHANGED
@@ -1092,14 +1092,17 @@ export interface SchemaFields<$Item> {
1092
1092
  onDestroy?: ItemEventHandler<$Item>
1093
1093
  /**
1094
1094
  * Called after data has been fetched from the API.
1095
- * Fires after all reactive updates have propagated.
1095
+ * Fires after all reactive updates have propagated,
1096
+ * once the values derived from the data are written,
1097
+ * including the ones that depend on options to load.
1096
1098
  */
1097
1099
  onLoad?: ItemEventHandler<$Item>
1098
1100
  /**
1099
1101
  * Called after a value change is committed. Fires
1100
- * after all reactive updates have propagated. Bubbles
1101
- * to parent schemas — return `false` to stop
1102
- * propagation.
1102
+ * after all reactive updates have propagated, once the
1103
+ * values derived from the change are written, including
1104
+ * the ones that depend on options to load. Bubbles to
1105
+ * parent schemas — return `false` to stop propagation.
1103
1106
  */
1104
1107
  onChange?: ItemEventHandler<$Item>
1105
1108
  }