@ditojs/admin 3.1.3 → 3.2.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.
- package/dist/dito-admin.es.js +935 -910
- package/dist/dito-admin.umd.js +5 -5
- package/package.json +5 -5
- package/src/mixins/TypeMixin.js +16 -0
- package/src/mixins/ValueMixin.js +12 -4
- package/src/utils/FormModel.js +62 -20
- package/src/utils/schema/data.js +29 -29
- package/types/index.d.ts +9 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/admin",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
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",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"not ie_mob > 0"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@ditojs/ui": "^3.
|
|
45
|
-
"@ditojs/utils": "^3.
|
|
44
|
+
"@ditojs/ui": "^3.2.0",
|
|
45
|
+
"@ditojs/utils": "^3.2.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"vue-upload-component": "^3.2.2"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@ditojs/build": "^3.
|
|
90
|
+
"@ditojs/build": "^3.2.0",
|
|
91
91
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
92
92
|
"@tiptap/core": "^3.31.4",
|
|
93
93
|
"@tiptap/extension-blockquote": "^3.31.4",
|
|
@@ -131,5 +131,5 @@
|
|
|
131
131
|
"vue-router": "^5.3.1",
|
|
132
132
|
"vue-upload-component": "^3.2.2"
|
|
133
133
|
},
|
|
134
|
-
"gitHead": "
|
|
134
|
+
"gitHead": "c81ea5a206255d00588ea8b23f9df34af52cae01"
|
|
135
135
|
}
|
package/src/mixins/TypeMixin.js
CHANGED
|
@@ -189,6 +189,7 @@ export default {
|
|
|
189
189
|
created() {
|
|
190
190
|
this._register(true)
|
|
191
191
|
this.setupSchemaFields()
|
|
192
|
+
this.warnAboutUncomputedValue()
|
|
192
193
|
},
|
|
193
194
|
|
|
194
195
|
unmounted() {
|
|
@@ -196,6 +197,21 @@ export default {
|
|
|
196
197
|
},
|
|
197
198
|
|
|
198
199
|
methods: {
|
|
200
|
+
// Only the form model calls `compute()`, see `computeValue()`. Components
|
|
201
|
+
// that it doesn't visit, e.g. in panels with their own data, never get
|
|
202
|
+
// their values computed.
|
|
203
|
+
warnAboutUncomputedValue() {
|
|
204
|
+
if (
|
|
205
|
+
this.schema.compute &&
|
|
206
|
+
!this.formModel.hasComputedValueEntry(this.componentPath)
|
|
207
|
+
) {
|
|
208
|
+
console.warn(
|
|
209
|
+
`The value of the component at '${this.componentPath}' isn't ` +
|
|
210
|
+
`computed, as its data isn't part of a form model.`
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
199
215
|
_register(add) {
|
|
200
216
|
// Provide component to container for schema accessor evaluation.
|
|
201
217
|
this.$emit('update:component', add ? this : null)
|
package/src/mixins/ValueMixin.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { isFunction } from '@ditojs/utils'
|
|
2
2
|
import DitoContext from '../DitoContext.js'
|
|
3
|
-
import {
|
|
3
|
+
import { getValueOrDefault } from '../utils/schema/data.js'
|
|
4
4
|
|
|
5
5
|
export default {
|
|
6
6
|
computed: {
|
|
7
7
|
value: {
|
|
8
8
|
get() {
|
|
9
|
-
|
|
9
|
+
// `FormModel` writes computed values into the data, see
|
|
10
|
+
// `computeValue()`.
|
|
11
|
+
const value = getValueOrDefault(
|
|
10
12
|
this.schema,
|
|
11
13
|
this.data,
|
|
12
14
|
this.name,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
() =>
|
|
16
|
+
DitoContext.createForSchema(this, {
|
|
17
|
+
schema: this.schema,
|
|
18
|
+
name: this.name,
|
|
19
|
+
data: this.data,
|
|
20
|
+
dataPath: this.dataPath,
|
|
21
|
+
rootData: this.rootData
|
|
22
|
+
})
|
|
15
23
|
)
|
|
16
24
|
// Only call `format()` if it's a function, as some types use `format`
|
|
17
25
|
// for other purposes, e.g. `DitoTypeColor` for the color format.
|
package/src/utils/FormModel.js
CHANGED
|
@@ -92,7 +92,7 @@ export class FormModel {
|
|
|
92
92
|
watch(
|
|
93
93
|
[getSchema, getData],
|
|
94
94
|
([schema, data]) => this.initializeData(schema, data),
|
|
95
|
-
|
|
95
|
+
modelWatchOptions
|
|
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,21 +101,23 @@ export class FormModel {
|
|
|
101
101
|
watch(
|
|
102
102
|
() => this.dataEntries.value.computedValueEntries,
|
|
103
103
|
entries => this.updateComputedValueRecords(entries),
|
|
104
|
-
|
|
104
|
+
modelWatchOptions
|
|
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
|
-
|
|
111
|
+
modelWatchOptions
|
|
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
117
|
watch(
|
|
116
118
|
getData,
|
|
117
119
|
() => this.takeProcessedDataSnapshot().catch(console.error),
|
|
118
|
-
|
|
120
|
+
modelWatchOptions
|
|
119
121
|
)
|
|
120
122
|
}
|
|
121
123
|
})
|
|
@@ -234,14 +236,14 @@ export class FormModel {
|
|
|
234
236
|
const data = this.getData()
|
|
235
237
|
return data && !isEmptySchema(schema)
|
|
236
238
|
? processData(schema, this.getSourceSchema(), data, this.dataPath, {
|
|
237
|
-
// Like `
|
|
239
|
+
// Like `getComputedValueResult()`:
|
|
238
240
|
component: this.component.mainSchemaComponent ?? this.component,
|
|
239
241
|
rootData: this.rootData,
|
|
240
242
|
schemaOnly: true,
|
|
241
243
|
target: 'clipboard',
|
|
242
|
-
// Reading the dirty state doesn't call `
|
|
243
|
-
//
|
|
244
|
-
|
|
244
|
+
// Reading the dirty state doesn't call `process()`, see
|
|
245
|
+
// `processData()`:
|
|
246
|
+
shouldCallProcess: false
|
|
245
247
|
})
|
|
246
248
|
: null
|
|
247
249
|
}
|
|
@@ -417,15 +419,13 @@ export class FormModel {
|
|
|
417
419
|
// Return a new object each time, so that the value is also written
|
|
418
420
|
// when only the value in the data changed, e.g. through user input.
|
|
419
421
|
() =>
|
|
420
|
-
isEntryCurrent.value
|
|
421
|
-
? { value: this.getComputedValue(entry) }
|
|
422
|
-
: null,
|
|
422
|
+
isEntryCurrent.value ? this.getComputedValueResult(entry) : null,
|
|
423
423
|
computedResult => {
|
|
424
424
|
if (computedResult) {
|
|
425
425
|
this.writeComputedValue(entry, computedResult.value)
|
|
426
426
|
}
|
|
427
427
|
},
|
|
428
|
-
|
|
428
|
+
modelWatchOptions
|
|
429
429
|
)
|
|
430
430
|
}
|
|
431
431
|
if (hasValueFromDataSchema(entry.schema)) {
|
|
@@ -443,13 +443,19 @@ export class FormModel {
|
|
|
443
443
|
this.writeComputedValue(entry, resolved.value)
|
|
444
444
|
}
|
|
445
445
|
},
|
|
446
|
-
|
|
446
|
+
modelWatchOptions
|
|
447
447
|
)
|
|
448
448
|
}
|
|
449
449
|
})
|
|
450
450
|
return scope
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
// Returns whether the model computes the value of the component at the
|
|
454
|
+
// component path, i.e. whether its walk of the data visits the component.
|
|
455
|
+
hasComputedValueEntry(componentPath) {
|
|
456
|
+
return this.dataEntries.value.computedValueEntries.has(componentPath)
|
|
457
|
+
}
|
|
458
|
+
|
|
453
459
|
// Returns whether the entry is still one of the current entries, with the
|
|
454
460
|
// same schema and data, see `getDataEntries()`.
|
|
455
461
|
isEntryCurrent({ componentPath, schema, data }) {
|
|
@@ -460,14 +466,37 @@ export class FormModel {
|
|
|
460
466
|
|
|
461
467
|
// Calls `schema.compute()` with the main schema component of the data, like
|
|
462
468
|
// `processData()`, and with the options resolved by the model, so that the
|
|
463
|
-
// result doesn't depend on whether the component is rendered.
|
|
464
|
-
|
|
469
|
+
// result doesn't depend on whether the component is rendered. Returns the
|
|
470
|
+
// value as `{ value }`, or `null` if `compute()` reads options that aren't
|
|
471
|
+
// loaded, which aborts it, so that the current value is kept until they are
|
|
472
|
+
// and `compute()` can rely on them. The watcher that calls it depends on the
|
|
473
|
+
// options through reading them, and calls it again once they're loaded.
|
|
474
|
+
getComputedValueResult(entry) {
|
|
465
475
|
const { schema, data, name, dataPath } = entry
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
476
|
+
try {
|
|
477
|
+
const value = computeValue(schema, data, name, dataPath, {
|
|
478
|
+
component: this.component.mainSchemaComponent ?? this.component,
|
|
479
|
+
rootData: this.rootData,
|
|
480
|
+
getOptions: schema.options ? () => this.getLoadedOptions(entry) : null
|
|
481
|
+
})
|
|
482
|
+
return { value }
|
|
483
|
+
} catch (error) {
|
|
484
|
+
if (error === optionsNotLoaded) {
|
|
485
|
+
return null
|
|
486
|
+
}
|
|
487
|
+
throw error
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Returns the options of the component of the entry, or aborts the
|
|
492
|
+
// `compute()` that reads them, while they aren't loaded, see
|
|
493
|
+
// `getComputedValueResult()`.
|
|
494
|
+
getLoadedOptions(entry) {
|
|
495
|
+
const options = this.getOptions(entry)
|
|
496
|
+
if (options === undefined) {
|
|
497
|
+
throw optionsNotLoaded
|
|
498
|
+
}
|
|
499
|
+
return options
|
|
471
500
|
}
|
|
472
501
|
|
|
473
502
|
// Returns the options of the component of the entry, resolved from
|
|
@@ -612,6 +641,19 @@ function takeOverChangedValues(target, before, after) {
|
|
|
612
641
|
|
|
613
642
|
const notFound = Symbol('notFound')
|
|
614
643
|
|
|
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.
|
|
651
|
+
const modelWatchOptions = { immediate: true, flush: 'post' }
|
|
652
|
+
|
|
653
|
+
// Aborts `compute()` when it reads options that aren't loaded, see
|
|
654
|
+
// `FormModel.getComputedValueResult()`.
|
|
655
|
+
const optionsNotLoaded = Symbol('optionsNotLoaded')
|
|
656
|
+
|
|
615
657
|
// Returns whether the schema is a source of computed values, through
|
|
616
658
|
// `schema.compute()` or a data schema of the `computed` types.
|
|
617
659
|
function hasComputedValueSource(schema) {
|
package/src/utils/schema/data.js
CHANGED
|
@@ -227,9 +227,9 @@ 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
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* `initializeData()`, or when they go missing later
|
|
230
|
+
* `FormModel` is the only caller and writes the computed values, including the
|
|
231
|
+
* defaults of components with `compute()`. Other defaults are written when the
|
|
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
|
|
234
234
|
* only when they're read.
|
|
235
235
|
*/
|
|
@@ -287,8 +287,10 @@ export function computeValue(schema, data, name, dataPath, {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
// Returns the value in `data`, or its default if it's missing. `context` can be
|
|
290
|
-
// a function that creates the context, called only when it's needed.
|
|
291
|
-
|
|
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
|
|
292
|
+
// `computeValue()`.
|
|
293
|
+
export function getValueOrDefault(schema, data, name, context) {
|
|
292
294
|
const shouldUseDefault = (
|
|
293
295
|
isMissingValue(schema, data, name, context) &&
|
|
294
296
|
!shouldIgnoreMissingValue(schema, context)
|
|
@@ -322,35 +324,33 @@ export function processData(schema, sourceSchema, data, dataPath, {
|
|
|
322
324
|
rootData,
|
|
323
325
|
schemaOnly, // whether to only include data covered by the schema, or all data
|
|
324
326
|
target,
|
|
325
|
-
// Whether to call the schema callbacks `
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
// `
|
|
331
|
-
//
|
|
332
|
-
|
|
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
|
|
329
|
+
// the callbacks may rely on. Without them, the types still process the values
|
|
330
|
+
// through `processValue()`, and the excluded values of components with
|
|
331
|
+
// `process()` are kept, as `process()` may store them elsewhere through
|
|
332
|
+
// `processedItem`. Computed values are in the data already, as `FormModel`
|
|
333
|
+
// writes them, see `computeValue()`.
|
|
334
|
+
shouldCallProcess = true
|
|
333
335
|
} = {}) {
|
|
334
336
|
const options = { component, rootData, schemaOnly, target }
|
|
335
337
|
const processedData = cloneItem(sourceSchema, data, options)
|
|
336
338
|
const graph = new SchemaGraph()
|
|
337
339
|
|
|
338
340
|
const before = ({ schema, data, name, dataPath, processedData }) => {
|
|
339
|
-
let value =
|
|
340
|
-
|
|
341
|
-
|
|
341
|
+
let value = getValueOrDefault(
|
|
342
|
+
schema,
|
|
343
|
+
data,
|
|
344
|
+
name,
|
|
345
|
+
() =>
|
|
346
|
+
DitoContext.createForSchema(component, {
|
|
342
347
|
schema,
|
|
343
|
-
data,
|
|
344
348
|
name,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
dataPath,
|
|
351
|
-
rootData: options.rootData
|
|
352
|
-
})
|
|
353
|
-
)
|
|
349
|
+
data,
|
|
350
|
+
dataPath,
|
|
351
|
+
rootData: options.rootData
|
|
352
|
+
})
|
|
353
|
+
)
|
|
354
354
|
// The schema expects the `wrapPrimitives` transformations to be present on
|
|
355
355
|
// the data that it is applied on, so warp before and unwrap after.
|
|
356
356
|
if (isArray(value)) {
|
|
@@ -400,13 +400,13 @@ export function processData(schema, sourceSchema, data, dataPath, {
|
|
|
400
400
|
|
|
401
401
|
// Handle the user's `process()` callback next, if one is provided, so that
|
|
402
402
|
// it can modify data in `processedData` even if it provides `exclude: true`
|
|
403
|
-
if (process &&
|
|
403
|
+
if (process && shouldCallProcess) {
|
|
404
404
|
value = process(getContext(context))
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
// Without calling `process()`, keep the excluded values that it may store
|
|
408
|
-
// elsewhere, see `
|
|
409
|
-
const shouldKeepExcludedValue = !!process && !
|
|
408
|
+
// elsewhere, see `shouldCallProcess`:
|
|
409
|
+
const shouldKeepExcludedValue = !!process && !shouldCallProcess
|
|
410
410
|
if (!shouldKeepExcludedValue && shouldExcludeValue(schema, context)) {
|
|
411
411
|
delete processedData[name]
|
|
412
412
|
} else {
|
package/types/index.d.ts
CHANGED
|
@@ -343,7 +343,11 @@ export interface BaseSchema<$Item>
|
|
|
343
343
|
* Computes and sets the field value reactively, also
|
|
344
344
|
* when the field isn't rendered, unless its `if`
|
|
345
345
|
* evaluates to `false`. If the callback returns
|
|
346
|
-
* `undefined`, the current value is preserved.
|
|
346
|
+
* `undefined`, the current value is preserved. The
|
|
347
|
+
* value is computed into the data, which is what is
|
|
348
|
+
* displayed and saved. As the callback runs whenever
|
|
349
|
+
* the data it reads changes, it should only return a
|
|
350
|
+
* value and not change data.
|
|
347
351
|
*
|
|
348
352
|
* As it doesn't depend on the field's component, the
|
|
349
353
|
* context's component properties, e.g. `component`
|
|
@@ -1995,9 +1999,10 @@ export type DitoContext<$Item = any> = {
|
|
|
1995
1999
|
option: any
|
|
1996
2000
|
/**
|
|
1997
2001
|
* All available options in a select. In `compute()`, they are
|
|
1998
|
-
* loaded when first read, also when the field isn't rendered
|
|
1999
|
-
*
|
|
2000
|
-
* once they are loaded
|
|
2002
|
+
* loaded when first read, also when the field isn't rendered.
|
|
2003
|
+
* Reading them while they load ends the callback, which keeps
|
|
2004
|
+
* the current value and runs again once they are loaded, so
|
|
2005
|
+
* they are always available in it.
|
|
2001
2006
|
*/
|
|
2002
2007
|
options: any
|
|
2003
2008
|
/**
|