@ditojs/admin 3.0.0 → 3.1.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/dist/dito-admin.es.js +2024 -1947
- package/dist/dito-admin.umd.js +5 -6
- package/package.json +8 -6
- package/src/DitoAdmin.js +1 -65
- package/src/DitoTypeComponent.js +17 -0
- package/src/components/DitoButtons.vue +14 -1
- package/src/components/DitoDialog.vue +1 -1
- package/src/components/DitoForm.vue +3 -8
- package/src/components/DitoPanel.vue +3 -2
- package/src/components/DitoRoot.vue +1 -8
- package/src/components/DitoRouterView.vue +27 -0
- package/src/components/DitoView.vue +2 -7
- package/src/components/index.js +1 -0
- package/src/mixins/DitoMixin.js +28 -17
- package/src/mixins/OptionsMixin.js +14 -4
- package/src/mixins/RouteMixin.js +1 -1
- package/src/mixins/SourceMixin.js +13 -1
- package/src/types/DitoTypeMultiselect.vue +23 -12
- package/src/types/DitoTypePanel.vue +2 -1
- package/src/types/DitoTypeTreeList.vue +1 -8
- package/src/utils/DataSchemaResolver.js +13 -0
- package/src/utils/FormModel.js +81 -22
- package/src/utils/request.js +70 -0
- package/src/utils/schema/data.js +77 -35
- package/src/utils/schema/lookup.js +9 -25
- package/types/index.d.ts +25 -0
- package/src/utils/DataSchemaResolver.test.js +0 -132
- package/src/utils/FormModel.test.js +0 -808
- package/src/utils/data.test.js +0 -28
- package/src/utils/schema/data.test.js +0 -471
- package/src/utils/schema/lookup.test.js +0 -61
- package/src/utils/schema/structure.test.js +0 -57
- package/src/utils/schema/validation.test.js +0 -395
- package/src/utils/units.test.js +0 -71
package/src/utils/FormModel.js
CHANGED
|
@@ -12,7 +12,9 @@ import { DataSchemaResolver } from './DataSchemaResolver.js'
|
|
|
12
12
|
import { isNested, isSourceWithResource } from './schema/structure.js'
|
|
13
13
|
import { isEmptySchema } from './schema/lookup.js'
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
getSchemaValue,
|
|
16
|
+
shouldSetDefaultValue,
|
|
17
|
+
getDefaultValue,
|
|
16
18
|
initializeData,
|
|
17
19
|
computeValue,
|
|
18
20
|
hasValueFromDataSchema,
|
|
@@ -23,9 +25,10 @@ import {
|
|
|
23
25
|
// FormModel holds the state of the data edited by a form, view or dialog that
|
|
24
26
|
// is derived from schema and data, independently of what is rendered:
|
|
25
27
|
//
|
|
26
|
-
// - Missing values are set to their defaults when the data is set up,
|
|
27
|
-
//
|
|
28
|
-
//
|
|
28
|
+
// - Missing values are set to their defaults when the data is set up, and when
|
|
29
|
+
// they go missing later, e.g. in items that code adds, except the values of
|
|
30
|
+
// components with `compute()`, which fall back to their defaults when
|
|
31
|
+
// `compute()` doesn't return a value, unlike in new data, see
|
|
29
32
|
// `initializeData()`.
|
|
30
33
|
// - The items of lists with `orderKey` are numbered when the data is set up,
|
|
31
34
|
// which doesn't make the data dirty.
|
|
@@ -94,12 +97,19 @@ export class FormModel {
|
|
|
94
97
|
// The entries are read through a computed property, so that the watchers
|
|
95
98
|
// of the entries can check synchronously whether their entry is still
|
|
96
99
|
// current, see `createComputedValueScope()`.
|
|
97
|
-
this.
|
|
100
|
+
this.dataEntries = computed(() => this.getDataEntries())
|
|
98
101
|
watch(
|
|
99
|
-
() => this.
|
|
102
|
+
() => this.dataEntries.value.computedValueEntries,
|
|
100
103
|
entries => this.updateComputedValueRecords(entries),
|
|
101
104
|
{ immediate: true }
|
|
102
105
|
)
|
|
106
|
+
// Values that go missing after the data was set up, e.g. in items that
|
|
107
|
+
// code adds, get their defaults too:
|
|
108
|
+
watch(
|
|
109
|
+
() => this.dataEntries.value.entriesWithMissingValues,
|
|
110
|
+
entries => this.setDefaultValues(entries),
|
|
111
|
+
{ immediate: true }
|
|
112
|
+
)
|
|
103
113
|
if (getSourceSchema) {
|
|
104
114
|
// Data that is set up, e.g. loaded, saved or applied, isn't dirty:
|
|
105
115
|
watch(
|
|
@@ -293,32 +303,79 @@ export class FormModel {
|
|
|
293
303
|
}
|
|
294
304
|
}
|
|
295
305
|
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
306
|
+
// Walks the data and returns the entries of `processSchemaData()` of the
|
|
307
|
+
// components whose `if` doesn't evaluate to `false`: the ones with computed
|
|
308
|
+
// values, by component path, and the ones whose values are missing. Called
|
|
309
|
+
// by a computed property, so that it runs again when the data structure
|
|
310
|
+
// changes, e.g. when list items are added or removed, values go missing, or
|
|
311
|
+
// `if` conditions change.
|
|
312
|
+
getDataEntries() {
|
|
302
313
|
const schema = this.getSchema()
|
|
303
314
|
const data = this.getData()
|
|
304
|
-
const
|
|
315
|
+
const computedValueEntries = new Map()
|
|
316
|
+
const entriesWithMissingValues = []
|
|
305
317
|
if (data && !isEmptySchema(schema)) {
|
|
306
318
|
processSchemaData(schema, data, {
|
|
307
319
|
dataPath: this.dataPath,
|
|
308
320
|
componentPath: this.componentPath,
|
|
309
321
|
shouldProcess: entry => (
|
|
310
|
-
!isSourceWithResource(entry.schema) &&
|
|
311
|
-
shouldRenderSchema(entry.schema, this.createEntryContext(entry))
|
|
322
|
+
!isSourceWithResource(entry.schema) && this.isEntryShown(entry)
|
|
312
323
|
),
|
|
313
324
|
before: entry => {
|
|
314
325
|
if (hasComputedValueSource(entry.schema)) {
|
|
315
|
-
|
|
326
|
+
computedValueEntries.set(entry.componentPath, entry)
|
|
327
|
+
}
|
|
328
|
+
if (this.shouldSetDefaultValue(entry)) {
|
|
329
|
+
entriesWithMissingValues.push(entry)
|
|
316
330
|
}
|
|
317
331
|
},
|
|
318
332
|
options: { component: this.component, rootData: this.rootData }
|
|
319
333
|
})
|
|
320
334
|
}
|
|
321
|
-
return
|
|
335
|
+
return { computedValueEntries, entriesWithMissingValues }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Returns whether the `if` of the entry's component doesn't evaluate to
|
|
339
|
+
// `false`. Unlike `shouldRenderSchema()`, it doesn't evaluate the components
|
|
340
|
+
// of sections and tabs, as the walk of the data visits them anyway, see
|
|
341
|
+
// `getDataEntries()`, which leads to the same entries.
|
|
342
|
+
isEntryShown(entry) {
|
|
343
|
+
return (
|
|
344
|
+
entry.schema.if === undefined ||
|
|
345
|
+
getSchemaValue('if', {
|
|
346
|
+
type: Boolean,
|
|
347
|
+
schema: entry.schema,
|
|
348
|
+
context: this.createEntryContext(entry),
|
|
349
|
+
default: true
|
|
350
|
+
})
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Returns whether the value of the entry is missing and needs its default.
|
|
355
|
+
// The model writes the values of components with `compute()`, including
|
|
356
|
+
// their defaults, see `computeValue()`.
|
|
357
|
+
shouldSetDefaultValue(entry) {
|
|
358
|
+
const { schema, data, name } = entry
|
|
359
|
+
return shouldSetDefaultValue(
|
|
360
|
+
schema,
|
|
361
|
+
data,
|
|
362
|
+
name,
|
|
363
|
+
() => this.createEntryContext(entry),
|
|
364
|
+
{ shouldSetDefaultsOfComponentsWithCompute: false }
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Sets the defaults of the values of the entries, unless they were set in
|
|
369
|
+
// the meantime.
|
|
370
|
+
setDefaultValues(entries) {
|
|
371
|
+
for (const entry of entries) {
|
|
372
|
+
if (this.shouldSetDefaultValue(entry)) {
|
|
373
|
+
const { schema, data, name } = entry
|
|
374
|
+
data[name] = getDefaultValue(schema, () =>
|
|
375
|
+
this.createEntryContext(entry)
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
322
379
|
}
|
|
323
380
|
|
|
324
381
|
// Keeps the records of the components that are still present with the same
|
|
@@ -395,9 +452,10 @@ export class FormModel {
|
|
|
395
452
|
}
|
|
396
453
|
|
|
397
454
|
// Returns whether the entry is still one of the current entries, with the
|
|
398
|
-
// same schema and data, see `
|
|
455
|
+
// same schema and data, see `getDataEntries()`.
|
|
399
456
|
isEntryCurrent({ componentPath, schema, data }) {
|
|
400
|
-
const currentEntry =
|
|
457
|
+
const currentEntry =
|
|
458
|
+
this.dataEntries.value.computedValueEntries.get(componentPath)
|
|
401
459
|
return currentEntry?.schema === schema && currentEntry.data === data
|
|
402
460
|
}
|
|
403
461
|
|
|
@@ -466,7 +524,7 @@ export class FormModel {
|
|
|
466
524
|
const tokens = isNested(schema)
|
|
467
525
|
? parseDataPath(dataPath)
|
|
468
526
|
: [...parseDataPath(dataPath), name]
|
|
469
|
-
return tokens.slice(parseDataPath(this.dataPath).length)
|
|
527
|
+
return tokens.slice(parseDataPath(this.dataPath).length).join('/')
|
|
470
528
|
}
|
|
471
529
|
|
|
472
530
|
// Returns the context for `if`, data schemas and options, with the component
|
|
@@ -497,8 +555,9 @@ function takeOverValuesAtDataPaths(
|
|
|
497
555
|
}
|
|
498
556
|
const result = clone(processedData)
|
|
499
557
|
for (const dataPath of dataPaths) {
|
|
500
|
-
const
|
|
501
|
-
const
|
|
558
|
+
const tokens = parseDataPath(dataPath)
|
|
559
|
+
const parentDataPath = tokens.slice(0, -1)
|
|
560
|
+
const key = tokens.at(-1)
|
|
502
561
|
const parent = getValueAtDataPath(result, parentDataPath, () => null)
|
|
503
562
|
if (parent && typeof parent === 'object') {
|
|
504
563
|
const value = getValueAtDataPath(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isAbsoluteUrl } from '@ditojs/utils'
|
|
2
|
+
import { formatQuery } from './route.js'
|
|
3
|
+
|
|
4
|
+
export class RequestError extends Error {
|
|
5
|
+
constructor(response) {
|
|
6
|
+
super(
|
|
7
|
+
`Request failed with status code: ${response.status} (${
|
|
8
|
+
response.statusText
|
|
9
|
+
})`
|
|
10
|
+
)
|
|
11
|
+
this.response = response
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function request(api, {
|
|
16
|
+
url,
|
|
17
|
+
method = 'get',
|
|
18
|
+
query = null,
|
|
19
|
+
headers = null,
|
|
20
|
+
data = null,
|
|
21
|
+
signal = null,
|
|
22
|
+
timeout = api.timeout ?? null
|
|
23
|
+
}) {
|
|
24
|
+
const isApiUrl = api.isApiUrl(url)
|
|
25
|
+
// Abort requests that take longer than the timeout, if there is one, so that
|
|
26
|
+
// nothing waits for them forever, e.g. submitting for pending loads:
|
|
27
|
+
const signals = [signal, timeout && AbortSignal.timeout(timeout)]
|
|
28
|
+
signal = AbortSignal.any(signals.filter(Boolean))
|
|
29
|
+
|
|
30
|
+
const response = await fetch(api.getApiUrl({ url, query }), {
|
|
31
|
+
method: method.toUpperCase(),
|
|
32
|
+
...(data && { body: JSON.stringify(data) }),
|
|
33
|
+
headers: {
|
|
34
|
+
...(isApiUrl && api.headers),
|
|
35
|
+
...headers
|
|
36
|
+
},
|
|
37
|
+
credentials:
|
|
38
|
+
isApiUrl && api.cors?.credentials
|
|
39
|
+
? 'include'
|
|
40
|
+
: 'same-origin',
|
|
41
|
+
signal
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (response.headers.get('Content-Type')?.includes('application/json')) {
|
|
45
|
+
response.data = await response.json()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new RequestError(response)
|
|
50
|
+
}
|
|
51
|
+
return response
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isApiUrl(api, url) {
|
|
55
|
+
return !isAbsoluteUrl(url) || url.startsWith(api.url)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getApiUrl(api, { url, query }) {
|
|
59
|
+
if (!url.startsWith(api.url) && !isAbsoluteUrl(url)) {
|
|
60
|
+
url = combineUrls(api.url, url)
|
|
61
|
+
}
|
|
62
|
+
// Support optional query parameters, to be are added to the URL.
|
|
63
|
+
const search = formatQuery(query)
|
|
64
|
+
return search ? `${url}${url.includes('?') ? '&' : '?'}${search}` : url
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function combineUrls(baseUrl, relativeUrl) {
|
|
68
|
+
// Use same approach as axios `combineURLs()` to join baseUrl & relativeUrl:
|
|
69
|
+
return `${baseUrl.replace(/\/+$/, '')}/${relativeUrl.replace(/^\/+/, '')}`
|
|
70
|
+
}
|
package/src/utils/schema/data.js
CHANGED
|
@@ -126,10 +126,12 @@ export function shouldIgnoreMissingValue(schema, context) {
|
|
|
126
126
|
// is missing in `data`: if there's no value, or if the value is `null` and
|
|
127
127
|
// the type treats that as missing, e.g. nested sections.
|
|
128
128
|
export function isMissingValue(schema, data, name, context) {
|
|
129
|
+
// Only read the value if needed, so that callers that are tracked, e.g. the
|
|
130
|
+
// walk of the form model's data, don't depend on all values.
|
|
129
131
|
return (
|
|
130
132
|
!(name in data) ||
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
!!getTypeOptions(schema)?.treatNullAsMissing?.(getContext(context)) &&
|
|
134
|
+
data[name] === null
|
|
133
135
|
)
|
|
134
136
|
}
|
|
135
137
|
|
|
@@ -150,6 +152,22 @@ export function getMultipleValue(schema) {
|
|
|
150
152
|
return schema.multiple ?? !!getTypeOptions(schema)?.defaultMultiple
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Returns whether the value of the component described by `schema` and `name`
|
|
157
|
+
* is missing in `data` and needs its default, see `initializeData()`.
|
|
158
|
+
* `shouldSetDefaultsOfComponentsWithCompute` decides for components with
|
|
159
|
+
* `compute()`, which may derive their missing values instead.
|
|
160
|
+
*/
|
|
161
|
+
export function shouldSetDefaultValue(schema, data, name, context, {
|
|
162
|
+
shouldSetDefaultsOfComponentsWithCompute = true
|
|
163
|
+
} = {}) {
|
|
164
|
+
return (
|
|
165
|
+
(shouldSetDefaultsOfComponentsWithCompute || !schema.compute) &&
|
|
166
|
+
isMissingValue(schema, data, name, context) &&
|
|
167
|
+
!shouldIgnoreMissingValue(schema, context)
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
/**
|
|
154
172
|
* Initializes `data` for the components of `schema`: Sets missing values to
|
|
155
173
|
* their defaults, so they can be correctly watched for changes, and numbers
|
|
@@ -177,11 +195,14 @@ export function initializeData(schema, data = {}, component, {
|
|
|
177
195
|
dataPath,
|
|
178
196
|
rootData
|
|
179
197
|
})
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
198
|
+
const shouldSetDefault = shouldSetDefaultValue(
|
|
199
|
+
schema,
|
|
200
|
+
data,
|
|
201
|
+
name,
|
|
202
|
+
context,
|
|
203
|
+
{ shouldSetDefaultsOfComponentsWithCompute }
|
|
204
|
+
)
|
|
205
|
+
if (shouldSetDefault) {
|
|
185
206
|
data[name] = getDefaultValue(schema, context)
|
|
186
207
|
}
|
|
187
208
|
if (hasItemsNumberedByOrderKey(schema) && isArray(data[name])) {
|
|
@@ -203,8 +224,9 @@ export function initializeData(schema, data = {}, component, {
|
|
|
203
224
|
* value in `data`, or its default if it's missing. Never writes into `data`:
|
|
204
225
|
* Computed values, including the defaults of components with `compute()`, are
|
|
205
226
|
* written by `FormModel`, and other defaults when the data is set up, see
|
|
206
|
-
* `initializeData()
|
|
207
|
-
* options for `context.options`, called
|
|
227
|
+
* `initializeData()`, or when they go missing later, see `FormModel`. If
|
|
228
|
+
* provided, `getOptions()` returns the options for `context.options`, called
|
|
229
|
+
* only when they're read.
|
|
208
230
|
*/
|
|
209
231
|
export function computeValue(schema, data, name, dataPath, {
|
|
210
232
|
component = null,
|
|
@@ -406,11 +428,19 @@ export function processData(schema, sourceSchema, data, dataPath, {
|
|
|
406
428
|
* returns `false` are skipped along with their content. The entries passed to
|
|
407
429
|
* them contain the `componentPath` of the component, tab or panel, continuing
|
|
408
430
|
* `componentPath` like `DitoMixin.componentPath` does.
|
|
431
|
+
*
|
|
432
|
+
* The primitive values of lists with `wrapPrimitives` are walked wrapped in
|
|
433
|
+
* objects under the `wrapPrimitives` key, like the admin edits them, see
|
|
434
|
+
* `SourceMixin`. `wrappedPrimitiveName` is that key while walking such an
|
|
435
|
+
* object, and its component keeps the data path of the item, where the admin
|
|
436
|
+
* maps errors of the value to. Values written into the objects don't reach
|
|
437
|
+
* the data, e.g. defaults, as the primitive values themselves are the data.
|
|
409
438
|
*/
|
|
410
439
|
export function processSchemaData(schema, data, {
|
|
411
440
|
dataPath = null,
|
|
412
441
|
componentPath = '',
|
|
413
442
|
processedData = null,
|
|
443
|
+
wrappedPrimitiveName = null,
|
|
414
444
|
before = null,
|
|
415
445
|
after = null,
|
|
416
446
|
shouldProcess = () => true,
|
|
@@ -426,9 +456,11 @@ export function processSchemaData(schema, data, {
|
|
|
426
456
|
if (components) {
|
|
427
457
|
for (const [name, componentSchema] of Object.entries(components)) {
|
|
428
458
|
const isNestedComponent = isNested(componentSchema)
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
459
|
+
const isWrappedPrimitive = name === wrappedPrimitiveName
|
|
460
|
+
const componentDataPath =
|
|
461
|
+
isNestedComponent && !isWrappedPrimitive
|
|
462
|
+
? getDataPath(dataPath, name)
|
|
463
|
+
: dataPath
|
|
432
464
|
const entry = {
|
|
433
465
|
schema: componentSchema,
|
|
434
466
|
data,
|
|
@@ -453,12 +485,12 @@ export function processSchemaData(schema, data, {
|
|
|
453
485
|
processedData
|
|
454
486
|
})
|
|
455
487
|
} else {
|
|
488
|
+
const { wrapPrimitives } = componentSchema
|
|
456
489
|
const processItem = (item, index = null) => {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return item
|
|
490
|
+
// Data that isn't processed holds the primitive values themselves,
|
|
491
|
+
// processed data holds them wrapped already, see `processData()`.
|
|
492
|
+
if (wrapPrimitives && !isObject(item)) {
|
|
493
|
+
item = { [wrapPrimitives]: item }
|
|
462
494
|
}
|
|
463
495
|
const itemDataPath =
|
|
464
496
|
index !== null
|
|
@@ -489,7 +521,8 @@ export function processSchemaData(schema, data, {
|
|
|
489
521
|
...walkOptions,
|
|
490
522
|
dataPath: itemDataPath,
|
|
491
523
|
componentPath: itemComponentPath,
|
|
492
|
-
processedData: processedItem
|
|
524
|
+
processedData: processedItem,
|
|
525
|
+
wrappedPrimitiveName: wrapPrimitives ?? null
|
|
493
526
|
})
|
|
494
527
|
} else {
|
|
495
528
|
// Items without a matching form, e.g. of an unknown type, are
|
|
@@ -500,29 +533,38 @@ export function processSchemaData(schema, data, {
|
|
|
500
533
|
|
|
501
534
|
before?.(entry)
|
|
502
535
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
536
|
+
// Only read the values of sources with forms, so that callers that
|
|
537
|
+
// are tracked, e.g. the walk of the form model's data, don't depend
|
|
538
|
+
// on all values.
|
|
539
|
+
if (hasFormSchema(componentSchema)) {
|
|
540
|
+
let value = processedData ? processedData[name] : data[name]
|
|
541
|
+
if (value != null) {
|
|
542
|
+
// Recursively process data on nested form items.
|
|
543
|
+
if (isArray(value)) {
|
|
544
|
+
// Optimization: No need to collect values if not cloning!
|
|
545
|
+
value = processedData
|
|
546
|
+
? value.map(processItem)
|
|
547
|
+
: value.forEach(processItem)
|
|
548
|
+
} else {
|
|
549
|
+
value = processItem(value)
|
|
550
|
+
}
|
|
551
|
+
if (processedData) {
|
|
552
|
+
processedData[name] = value
|
|
553
|
+
}
|
|
516
554
|
}
|
|
517
555
|
}
|
|
518
556
|
|
|
519
557
|
after?.(entry)
|
|
558
|
+
// The panels of nested components display the data that contains
|
|
559
|
+
// the components, like the ones of unnested components, see
|
|
560
|
+
// `DitoContainer.panelEntries`.
|
|
561
|
+
processTabOrPanelSchemas(componentSchema.panels, entry.componentPath)
|
|
520
562
|
}
|
|
521
563
|
}
|
|
522
564
|
}
|
|
523
565
|
}
|
|
524
566
|
|
|
525
|
-
const processTabOrPanelSchemas = schemas => {
|
|
567
|
+
const processTabOrPanelSchemas = (schemas, parentComponentPath) => {
|
|
526
568
|
// Tabs and panels add their names to component paths, not to data paths.
|
|
527
569
|
for (const [name, tabOrPanelSchema] of Object.entries(schemas || {})) {
|
|
528
570
|
const entry = {
|
|
@@ -530,7 +572,7 @@ export function processSchemaData(schema, data, {
|
|
|
530
572
|
data,
|
|
531
573
|
name: null,
|
|
532
574
|
dataPath,
|
|
533
|
-
componentPath: appendDataPath(
|
|
575
|
+
componentPath: appendDataPath(parentComponentPath, name),
|
|
534
576
|
processedData
|
|
535
577
|
}
|
|
536
578
|
// The components of panels with their own data don't edit `data`.
|
|
@@ -541,8 +583,8 @@ export function processSchemaData(schema, data, {
|
|
|
541
583
|
}
|
|
542
584
|
|
|
543
585
|
processComponents(schema.components, componentPath)
|
|
544
|
-
processTabOrPanelSchemas(schema.tabs)
|
|
545
|
-
processTabOrPanelSchemas(schema.panels)
|
|
586
|
+
processTabOrPanelSchemas(schema.tabs, componentPath)
|
|
587
|
+
processTabOrPanelSchemas(schema.panels, componentPath)
|
|
546
588
|
|
|
547
589
|
return processedData || data
|
|
548
590
|
}
|
|
@@ -176,27 +176,10 @@ export function getButtonSchemas(buttons) {
|
|
|
176
176
|
)
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
componentPath
|
|
183
|
-
tabComponent = null
|
|
184
|
-
) {
|
|
185
|
-
return schema
|
|
186
|
-
? {
|
|
187
|
-
schema,
|
|
188
|
-
// If the panel provides its own name, append it to the paths. This is
|
|
189
|
-
// used e.g. for $filters panels.
|
|
190
|
-
dataPath:
|
|
191
|
-
dataPath != null && schema.name
|
|
192
|
-
? appendDataPath(dataPath, schema.name)
|
|
193
|
-
: dataPath,
|
|
194
|
-
componentPath: schema.name
|
|
195
|
-
? appendDataPath(componentPath, schema.name)
|
|
196
|
-
: componentPath,
|
|
197
|
-
tabComponent
|
|
198
|
-
}
|
|
199
|
-
: null
|
|
179
|
+
// Returns the entry of the panel described by `schema`, which `DitoPanels`
|
|
180
|
+
// displays at `dataPath` and `componentPath`.
|
|
181
|
+
function getPanelEntry(schema, dataPath, componentPath, tabComponent) {
|
|
182
|
+
return schema ? { schema, dataPath, componentPath, tabComponent } : null
|
|
200
183
|
}
|
|
201
184
|
|
|
202
185
|
export function getPanelEntries(
|
|
@@ -224,8 +207,9 @@ export function getPanelEntries(
|
|
|
224
207
|
|
|
225
208
|
// Returns the entries of the panel that the type of the component described by
|
|
226
209
|
// `schema` provides, and of the panels in `schema.panels`. The type's panel is
|
|
227
|
-
// addressed relative to `dataPath` and its
|
|
228
|
-
// `dataPathComponentPath`, while the panels in `schema.panels`
|
|
210
|
+
// addressed by its name, e.g. `$filters`, relative to `dataPath` and its
|
|
211
|
+
// component path, `dataPathComponentPath`, while the panels in `schema.panels`
|
|
212
|
+
// are addressed by their keys, see `getPanelEntries()`, continuing
|
|
229
213
|
// `schemaComponentPath`, the component path of `schema` itself, which also
|
|
230
214
|
// contains the names of unnested components.
|
|
231
215
|
export function getAllPanelEntries(
|
|
@@ -247,8 +231,8 @@ export function getAllPanelEntries(
|
|
|
247
231
|
? [
|
|
248
232
|
getPanelEntry(
|
|
249
233
|
panelSchema,
|
|
250
|
-
dataPath,
|
|
251
|
-
dataPathComponentPath,
|
|
234
|
+
appendDataPath(dataPath, panelSchema.name),
|
|
235
|
+
appendDataPath(dataPathComponentPath, panelSchema.name),
|
|
252
236
|
tabComponent
|
|
253
237
|
)
|
|
254
238
|
]
|
package/types/index.d.ts
CHANGED
|
@@ -52,6 +52,12 @@ export type RequestMethod = <T>(options: {
|
|
|
52
52
|
headers?: Record<string, string> | null
|
|
53
53
|
/** Abort signal to cancel the request. */
|
|
54
54
|
signal?: AbortSignal | null
|
|
55
|
+
/**
|
|
56
|
+
* Milliseconds after which the request is aborted.
|
|
57
|
+
*
|
|
58
|
+
* @defaultValue {@link ApiConfig.timeout}
|
|
59
|
+
*/
|
|
60
|
+
timeout?: number | null
|
|
55
61
|
}) => Promise<RequestMethodResponse<T>>
|
|
56
62
|
|
|
57
63
|
/**
|
|
@@ -170,6 +176,14 @@ export interface ApiConfig {
|
|
|
170
176
|
*/
|
|
171
177
|
durationFactor: number
|
|
172
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Milliseconds after which requests are aborted, so that
|
|
181
|
+
* nothing waits for them forever, e.g. submitting forms for
|
|
182
|
+
* pending loads of options. Requests can set their own.
|
|
183
|
+
*
|
|
184
|
+
* @defaultValue `null`, no timeout
|
|
185
|
+
*/
|
|
186
|
+
timeout?: number | null
|
|
173
187
|
/**
|
|
174
188
|
* CORS settings applied to API requests (where
|
|
175
189
|
* {@link ApiConfig.isApiUrl} returns `true`).
|
|
@@ -330,6 +344,11 @@ export interface BaseSchema<$Item>
|
|
|
330
344
|
* when the field isn't rendered, unless its `if`
|
|
331
345
|
* evaluates to `false`. If the callback returns
|
|
332
346
|
* `undefined`, the current value is preserved.
|
|
347
|
+
*
|
|
348
|
+
* As it doesn't depend on the field's component, the
|
|
349
|
+
* context's component properties, e.g. `component`
|
|
350
|
+
* and `schemaComponent`, refer to the form, view or
|
|
351
|
+
* dialog that holds the data, not to the field.
|
|
333
352
|
*/
|
|
334
353
|
compute?: ItemAccessor<$Item>
|
|
335
354
|
/**
|
|
@@ -797,6 +816,12 @@ export type SchemaOptions<$Item, $Option = any> =
|
|
|
797
816
|
* receives the `DitoContext` and returns an inner
|
|
798
817
|
* function that is called to fetch the actual data,
|
|
799
818
|
* enabling reactive dependency tracking.
|
|
819
|
+
*
|
|
820
|
+
* The options are loaded independently of the
|
|
821
|
+
* field's component, also for computes, so the
|
|
822
|
+
* context's component properties, e.g. `component`
|
|
823
|
+
* and `schemaComponent`, refer to the form, view or
|
|
824
|
+
* dialog that holds the data, not to the field.
|
|
800
825
|
*/
|
|
801
826
|
data?: OrItemAccessor<
|
|
802
827
|
$Item,
|