@json-layout/core 0.24.1 → 0.25.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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/src/compile/index.js +5 -1
  3. package/src/compile/serialize.js +1 -0
  4. package/src/compile/skeleton-node.js +175 -130
  5. package/src/compile/skeleton-tree.js +23 -17
  6. package/src/compile/types.ts +5 -4
  7. package/src/state/state-node.js +20 -9
  8. package/src/state/state-tree.js +12 -2
  9. package/types/compile/index.d.ts +0 -16
  10. package/types/compile/index.d.ts.map +0 -1
  11. package/types/compile/serialize.d.ts +0 -6
  12. package/types/compile/serialize.d.ts.map +0 -1
  13. package/types/compile/skeleton-node.d.ts +0 -21
  14. package/types/compile/skeleton-node.d.ts.map +0 -1
  15. package/types/compile/skeleton-tree.d.ts +0 -16
  16. package/types/compile/skeleton-tree.d.ts.map +0 -1
  17. package/types/compile/types.d.ts +0 -58
  18. package/types/compile/types.d.ts.map +0 -1
  19. package/types/compile/utils/resolve-refs.d.ts +0 -17
  20. package/types/compile/utils/resolve-refs.d.ts.map +0 -1
  21. package/types/i18n/en.d.ts +0 -3
  22. package/types/i18n/en.d.ts.map +0 -1
  23. package/types/i18n/fr.d.ts +0 -3
  24. package/types/i18n/fr.d.ts.map +0 -1
  25. package/types/i18n/index.d.ts +0 -3
  26. package/types/i18n/index.d.ts.map +0 -1
  27. package/types/i18n/types.d.ts +0 -36
  28. package/types/i18n/types.d.ts.map +0 -1
  29. package/types/index.d.ts +0 -5
  30. package/types/index.d.ts.map +0 -1
  31. package/types/state/index.d.ts +0 -278
  32. package/types/state/index.d.ts.map +0 -1
  33. package/types/state/state-node.d.ts +0 -36
  34. package/types/state/state-node.d.ts.map +0 -1
  35. package/types/state/state-tree.d.ts +0 -13
  36. package/types/state/state-tree.d.ts.map +0 -1
  37. package/types/state/types.d.ts +0 -173
  38. package/types/state/types.d.ts.map +0 -1
  39. package/types/state/utils/display.d.ts +0 -51
  40. package/types/state/utils/display.d.ts.map +0 -1
  41. package/types/state/utils/immutable.d.ts +0 -21
  42. package/types/state/utils/immutable.d.ts.map +0 -1
  43. package/types/utils/build.d.ts +0 -5
  44. package/types/utils/build.d.ts.map +0 -1
  45. package/types/utils/clone.d.ts +0 -3
  46. package/types/utils/clone.d.ts.map +0 -1
  47. package/types/utils/doc-options.d.ts +0 -14
  48. package/types/utils/doc-options.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@json-layout/core",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "Compilation and state management utilities for JSON Layout.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -60,7 +60,7 @@ const fillOptions = (partialOptions) => {
60
60
  let ajv = partialOptions.ajv
61
61
  if (!ajv) {
62
62
  /** @type {import('ajv').Options} */
63
- const ajvOpts = { allErrors: true, strict: false }
63
+ const ajvOpts = { allErrors: true, strict: false, verbose: true }
64
64
  if (partialOptions.ajvOptions) Object.assign(ajvOpts, partialOptions.ajvOptions)
65
65
  if (partialOptions.code) ajvOpts.code = { source: true, esm: true, lines: true }
66
66
  const newAjv = new Ajv(ajvOpts)
@@ -126,6 +126,8 @@ export function compile (_schema, partialOptions = {}) {
126
126
  const validationErrors = {}
127
127
  /** @type {Record<string, import('./types.js').SkeletonTree>} */
128
128
  const skeletonTrees = {}
129
+ /** @type {Record<string, import('./types.js').SkeletonNode>} */
130
+ const skeletonNodes = {}
129
131
 
130
132
  // makeSkeletonTree also mutates the schema (adding some error messages)
131
133
  const mainTreePointer = `${schema.$id}#`
@@ -137,6 +139,7 @@ export function compile (_schema, partialOptions = {}) {
137
139
  options,
138
140
  getJSONRef,
139
141
  skeletonTrees,
142
+ skeletonNodes,
140
143
  validatePointers,
141
144
  validationErrors,
142
145
  normalizedLayouts,
@@ -187,6 +190,7 @@ export function compile (_schema, partialOptions = {}) {
187
190
  schema,
188
191
  mainTree: mainTreePointer,
189
192
  skeletonTrees,
193
+ skeletonNodes,
190
194
  validates,
191
195
  validationErrors,
192
196
  normalizedLayouts,
@@ -81,6 +81,7 @@ export const exportLocalizeErrors = localizeErrors;\n` + code
81
81
  ast.exports.compiledLayout = {
82
82
  mainTree: compiledLayout.mainTree,
83
83
  skeletonTrees: clone(compiledLayout.skeletonTrees),
84
+ skeletonNodes: clone(compiledLayout.skeletonNodes),
84
85
  normalizedLayouts: clone(compiledLayout.normalizedLayouts),
85
86
  validates: {},
86
87
  validationErrors: compiledLayout.validationErrors,
@@ -9,13 +9,13 @@ import { partialResolveRefs } from './utils/resolve-refs.js'
9
9
  * @param {import('./index.js').CompileOptions} options
10
10
  * @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
11
11
  * @param {Record<string, import('./types.js').SkeletonTree>} skeletonTrees
12
+ * @param {Record<string, import('./types.js').SkeletonNode>} skeletonNodes
12
13
  * @param {string[]} validates
13
14
  * @param {Record<string, string[]>} validationErrors
14
15
  * @param {Record<string, import('@json-layout/vocabulary').NormalizedLayout>} normalizedLayouts
15
16
  * @param {import('@json-layout/vocabulary').Expression[]} expressions
16
17
  * @param {string | number} key
17
- * @param {string} currentPointer
18
- * @param {string | null} parentPointer
18
+ * @param {string} pointer
19
19
  * @param {boolean} required
20
20
  * @param {string} [condition]
21
21
  * @param {boolean} [dependent]
@@ -28,13 +28,13 @@ export function makeSkeletonNode (
28
28
  options,
29
29
  getJSONRef,
30
30
  skeletonTrees,
31
+ skeletonNodes,
31
32
  validates,
32
33
  validationErrors,
33
34
  normalizedLayouts,
34
35
  expressions,
35
36
  key,
36
- currentPointer,
37
- parentPointer,
37
+ pointer,
38
38
  required,
39
39
  condition,
40
40
  dependent,
@@ -42,21 +42,23 @@ export function makeSkeletonNode (
42
42
  ) {
43
43
  let schemaId = sourceSchemaId
44
44
  let schema = rawSchema
45
- let pointer = currentPointer
45
+ let refPointer = pointer
46
46
  let refFragment
47
+ rawSchema.__pointer = pointer
47
48
  if (schema.$ref) {
48
- [refFragment, schemaId, pointer] = getJSONRef(sourceSchemaId, schema.$ref)
49
- schema = {...rawSchema, ...refFragment}
49
+ [refFragment, schemaId, refPointer] = getJSONRef(sourceSchemaId, schema.$ref)
50
+ refFragment.__pointer = refPointer
51
+ schema = { ...rawSchema, ...refFragment }
50
52
  delete schema.$ref
51
53
  }
52
- schema = partialResolveRefs(schema, schemaId, getJSONRef)
53
- const { type, nullable } = knownType ? { type: knownType, nullable: false } : getSchemaFragmentType(schema)
54
+ const resolvedSchema = partialResolveRefs(schema, schemaId, getJSONRef)
55
+ const { type, nullable } = knownType ? { type: knownType, nullable: false } : getSchemaFragmentType(resolvedSchema)
54
56
 
55
57
  // improve on ajv error messages based on ajv-errors (https://ajv.js.org/packages/ajv-errors.html)
56
58
  rawSchema.errorMessage = rawSchema.errorMessage ?? {}
57
59
  if (!normalizedLayouts[pointer]) {
58
60
  const normalizationResult = normalizeLayoutFragment(
59
- /** @type {import('@json-layout/vocabulary').SchemaFragment} */(schema),
61
+ /** @type {import('@json-layout/vocabulary').SchemaFragment} */(resolvedSchema),
60
62
  pointer,
61
63
  options.components,
62
64
  options.markdown,
@@ -132,7 +134,7 @@ export function makeSkeletonNode (
132
134
  const node = {
133
135
  key: key ?? '',
134
136
  pointer,
135
- parentPointer,
137
+ refPointer,
136
138
  pure,
137
139
  propertyKeys: [],
138
140
  roPropertyKeys: [],
@@ -153,72 +155,88 @@ export function makeSkeletonNode (
153
155
  node.propertyKeys.push(propertyKey)
154
156
  if (schema.properties[propertyKey].readOnly) node.roPropertyKeys.push(propertyKey)
155
157
  const dependent = schema.dependentRequired && Object.values(schema.dependentRequired).some(dependentProperties => dependentProperties.includes(propertyKey))
156
- node.children.push(makeSkeletonNode(
157
- schema.properties[propertyKey],
158
- schemaId,
159
- options,
160
- getJSONRef,
161
- skeletonTrees,
162
- validates,
163
- validationErrors,
164
- normalizedLayouts,
165
- expressions,
166
- propertyKey,
167
- `${pointer}/properties/${propertyKey}`,
168
- pointer,
169
- schema.required?.includes(propertyKey),
170
- undefined,
171
- dependent
172
- ))
158
+ const childPointer = `${refPointer}/properties/${propertyKey}`
159
+ if (!skeletonNodes[childPointer]) {
160
+ // @ts-ignore
161
+ skeletonNodes[childPointer] = 'recursing'
162
+ skeletonNodes[childPointer] = makeSkeletonNode(
163
+ schema.properties[propertyKey],
164
+ schemaId,
165
+ options,
166
+ getJSONRef,
167
+ skeletonTrees,
168
+ skeletonNodes,
169
+ validates,
170
+ validationErrors,
171
+ normalizedLayouts,
172
+ expressions,
173
+ propertyKey,
174
+ childPointer,
175
+ schema.required?.includes(propertyKey),
176
+ undefined,
177
+ dependent
178
+ )
179
+ }
180
+ node.children.push(childPointer)
173
181
 
174
182
  if (schema.dependentSchemas?.[propertyKey] || (schema.dependencies?.[propertyKey] && !Array.isArray(schema.dependencies[propertyKey]))) {
175
183
  const dependentSchema = schema.dependentSchemas?.[propertyKey] ?? schema.dependencies[propertyKey]
176
- const dependentPointer = schema.dependentSchemas?.[propertyKey] ? `${pointer}/dependentSchemas/${propertyKey}` : `${pointer}/dependencies/${propertyKey}`
177
- node.children.push(makeSkeletonNode(
178
- dependentSchema,
184
+ const dependentPointer = schema.dependentSchemas?.[propertyKey] ? `${refPointer}/dependentSchemas/${propertyKey}` : `${refPointer}/dependencies/${propertyKey}`
185
+ if (!skeletonNodes[dependentPointer]) {
186
+ // @ts-ignore
187
+ skeletonNodes[dependentPointer] = 'recursing'
188
+ skeletonNodes[dependentPointer] = makeSkeletonNode(
189
+ dependentSchema,
190
+ schemaId,
191
+ options,
192
+ getJSONRef,
193
+ skeletonTrees,
194
+ skeletonNodes,
195
+ validates,
196
+ validationErrors,
197
+ normalizedLayouts,
198
+ expressions,
199
+ `$deps-${propertyKey}`,
200
+ dependentPointer,
201
+ false,
202
+ `data["${propertyKey}"] !== undefined`,
203
+ undefined,
204
+ 'object'
205
+ )
206
+ }
207
+ node.children.push(dependentPointer)
208
+ }
209
+ }
210
+ }
211
+ if (schema.allOf) {
212
+ for (let i = 0; i < schema.allOf.length; i++) {
213
+ const childPointer = `${refPointer}/allOf/${i}`
214
+ if (!skeletonNodes[childPointer]) {
215
+ // @ts-ignore
216
+ skeletonNodes[childPointer] = 'recursing'
217
+ skeletonNodes[childPointer] = makeSkeletonNode(
218
+ schema.allOf[i],
179
219
  schemaId,
180
220
  options,
181
221
  getJSONRef,
182
222
  skeletonTrees,
223
+ skeletonNodes,
183
224
  validates,
184
225
  validationErrors,
185
226
  normalizedLayouts,
186
227
  expressions,
187
- `$deps-${propertyKey}`,
188
- dependentPointer,
189
- pointer,
228
+ `$allOf-${i}`,
229
+ childPointer,
190
230
  false,
191
- `data["${propertyKey}"] !== undefined`,
231
+ undefined,
192
232
  undefined,
193
233
  'object'
194
- ))
234
+ )
195
235
  }
196
- }
197
- }
198
- if (schema.allOf) {
199
- node.children = node.children ?? []
200
- for (let i = 0; i < schema.allOf.length; i++) {
201
- const allOfNode = makeSkeletonNode(
202
- schema.allOf[i],
203
- schemaId,
204
- options,
205
- getJSONRef,
206
- skeletonTrees,
207
- validates,
208
- validationErrors,
209
- normalizedLayouts,
210
- expressions,
211
- `$allOf-${i}`,
212
- `${pointer}/allOf/${i}`,
213
- pointer,
214
- false,
215
- undefined,
216
- undefined,
217
- 'object'
218
- )
219
- node.propertyKeys = node.propertyKeys.concat(allOfNode.propertyKeys)
220
- node.roPropertyKeys = node.roPropertyKeys.concat(allOfNode.roPropertyKeys)
221
- node.children.push(allOfNode)
236
+ node.propertyKeys = node.propertyKeys.concat(skeletonNodes[childPointer].propertyKeys)
237
+ node.roPropertyKeys = node.roPropertyKeys.concat(skeletonNodes[childPointer].roPropertyKeys)
238
+ node.children = node.children ?? []
239
+ node.children.push(childPointer)
222
240
  }
223
241
  }
224
242
  if (schema.oneOf) {
@@ -256,6 +274,7 @@ export function makeSkeletonNode (
256
274
  options,
257
275
  getJSONRef,
258
276
  skeletonTrees,
277
+ skeletonNodes,
259
278
  validates,
260
279
  validationErrors,
261
280
  normalizedLayouts,
@@ -266,95 +285,119 @@ export function makeSkeletonNode (
266
285
  }
267
286
  childrenTrees.push(childTreePointer)
268
287
  }
288
+ if (!skeletonNodes[oneOfPointer]) {
289
+ skeletonNodes[oneOfPointer] = {
290
+ key: '$oneOf',
291
+ pointer: oneOfPointer,
292
+ refPointer: oneOfPointer,
293
+ childrenTrees,
294
+ pure: skeletonNodes[skeletonTrees[childrenTrees[0]]?.root].pure,
295
+ propertyKeys: [],
296
+ roPropertyKeys: []
297
+ }
298
+ }
269
299
  node.children = node.children ?? []
270
- node.children.push({
271
- key: '$oneOf',
272
- pointer: `${pointer}/oneOf`,
273
- parentPointer: pointer,
274
- childrenTrees,
275
- pure: skeletonTrees[childrenTrees[0]]?.root.pure,
276
- propertyKeys: [],
277
- roPropertyKeys: []
278
- })
300
+ node.children.push(oneOfPointer)
279
301
 
280
- schema.errorMessage.oneOf = options.messages.errorOneOf
302
+ rawSchema.errorMessage.oneOf = options.messages.errorOneOf
281
303
  }
282
304
  if (schema.if) {
283
305
  validates.push(`${pointer}/if`)
284
306
  if (schema.then) {
307
+ const childPointer = `${refPointer}/then`
308
+ if (!skeletonNodes[childPointer]) {
309
+ // @ts-ignore
310
+ skeletonNodes[childPointer] = 'recursing'
311
+ skeletonNodes[childPointer] = makeSkeletonNode(
312
+ schema.then,
313
+ schemaId,
314
+ options,
315
+ getJSONRef,
316
+ skeletonTrees,
317
+ skeletonNodes,
318
+ validates,
319
+ validationErrors,
320
+ normalizedLayouts,
321
+ expressions,
322
+ '$then',
323
+ childPointer,
324
+ false,
325
+ `validates["${pointer}/if"](data)`,
326
+ undefined,
327
+ 'object'
328
+ )
329
+ }
285
330
  node.children = node.children ?? []
286
- node.children.push(makeSkeletonNode(
287
- schema.then,
288
- schemaId,
289
- options,
290
- getJSONRef,
291
- skeletonTrees,
292
- validates,
293
- validationErrors,
294
- normalizedLayouts,
295
- expressions,
296
- '$then',
297
- `${pointer}/then`,
298
- pointer,
299
- false,
300
- `validates["${pointer}/if"](data)`,
301
- undefined,
302
- 'object'
303
- ))
331
+ node.children.push(childPointer)
304
332
  }
305
333
  if (schema.else) {
334
+ const childPointer = `${refPointer}/else`
335
+ if (!skeletonNodes[childPointer]) {
336
+ // @ts-ignore
337
+ skeletonNodes[childPointer] = 'recursing'
338
+ skeletonNodes[childPointer] = makeSkeletonNode(
339
+ schema.else,
340
+ schemaId,
341
+ options,
342
+ getJSONRef,
343
+ skeletonTrees,
344
+ skeletonNodes,
345
+ validates,
346
+ validationErrors,
347
+ normalizedLayouts,
348
+ expressions,
349
+ '$else',
350
+ childPointer,
351
+ false,
352
+ `!validates["${pointer}/if"](data)`,
353
+ undefined,
354
+ 'object'
355
+ )
356
+ }
306
357
  node.children = node.children ?? []
307
- node.children.push(makeSkeletonNode(
308
- schema.else,
309
- schemaId,
310
- options,
311
- getJSONRef,
312
- skeletonTrees,
313
- validates,
314
- validationErrors,
315
- normalizedLayouts,
316
- expressions,
317
- '$else',
318
- `${pointer}/else`,
319
- pointer,
320
- false,
321
- `!validates["${pointer}/if"](data)`,
322
- undefined,
323
- 'object'
324
- ))
358
+ node.children.push(childPointer)
325
359
  }
326
360
  }
327
361
 
328
362
  for (const propertyKey of node.propertyKeys) {
329
363
  if (schema?.required?.includes(propertyKey)) {
330
- schema.errorMessage.required = schema.errorMessage.required ?? {}
331
- schema.errorMessage.required[propertyKey] = options.messages.errorRequired
364
+ rawSchema.errorMessage.required = rawSchema.errorMessage.required ?? {}
365
+ rawSchema.errorMessage.required[propertyKey] = options.messages.errorRequired
332
366
  }
333
367
  if (schema.dependentRequired && Object.keys(schema.dependentRequired).includes(propertyKey)) {
334
- schema.errorMessage.dependentRequired = options.messages.errorRequired
368
+ rawSchema.errorMessage.dependentRequired = options.messages.errorRequired
335
369
  }
336
370
  }
337
371
  }
338
372
 
339
373
  if (type === 'array' && schema.items) {
340
374
  if (Array.isArray(schema.items)) {
341
- node.children = schema.items.map((/** @type {any} */ itemSchema, /** @type {number} */ i) => {
342
- return makeSkeletonNode(
343
- itemSchema,
344
- schemaId,
345
- options,
346
- getJSONRef,
347
- skeletonTrees,
348
- validates,
349
- validationErrors,
350
- normalizedLayouts,
351
- expressions,
352
- i,
353
- `${pointer}/items/${i}`,
354
- pointer,
355
- true
356
- )
357
- })
375
+ node.children = node.children ?? []
376
+ for (let i = 0; i < schema.items.length; i++) {
377
+ /** @type {any} */
378
+ const itemSchema = schema.items[i]
379
+ const childPointer = `${refPointer}/items/${i}`
380
+ if (!skeletonNodes[childPointer]) {
381
+ // @ts-ignore
382
+ skeletonNodes[childPointer] = 'recursing'
383
+ skeletonNodes[childPointer] = makeSkeletonNode(
384
+ itemSchema,
385
+ schemaId,
386
+ options,
387
+ getJSONRef,
388
+ skeletonTrees,
389
+ skeletonNodes,
390
+ validates,
391
+ validationErrors,
392
+ normalizedLayouts,
393
+ expressions,
394
+ i,
395
+ childPointer,
396
+ true
397
+ )
398
+ }
399
+ node.children.push(childPointer)
400
+ }
358
401
  } else {
359
402
  const childTreePointer = `${pointer}/items`
360
403
  if (!skeletonTrees[childTreePointer]) {
@@ -366,6 +409,7 @@ export function makeSkeletonNode (
366
409
  options,
367
410
  getJSONRef,
368
411
  skeletonTrees,
412
+ skeletonNodes,
369
413
  validates,
370
414
  validationErrors,
371
415
  normalizedLayouts,
@@ -378,11 +422,12 @@ export function makeSkeletonNode (
378
422
  }
379
423
  }
380
424
 
381
- for (const child of node.children || []) {
425
+ for (const childPointer of node.children || []) {
426
+ const child = skeletonNodes[childPointer]
382
427
  if (!child.pure) node.pure = false
383
428
  }
384
429
  for (const childTree of node.childrenTrees || []) {
385
- if (!skeletonTrees[childTree]?.root?.pure) node.pure = false
430
+ if (!skeletonNodes[skeletonTrees[childTree]?.root]?.pure) node.pure = false
386
431
  }
387
432
 
388
433
  return node
@@ -9,6 +9,7 @@ import { makeSkeletonNode } from './skeleton-node.js'
9
9
  * @param {import('./index.js').CompileOptions} options
10
10
  * @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
11
11
  * @param {Record<string, import('./types.js').SkeletonTree>} skeletonTrees
12
+ * @param {Record<string, import('./types.js').SkeletonNode>} skeletonNodes
12
13
  * @param {string[]} validates
13
14
  * @param {Record<string, string[]>} validationErrors
14
15
  * @param {Record<string, import('@json-layout/vocabulary').NormalizedLayout>} normalizedLayouts
@@ -23,6 +24,7 @@ export function makeSkeletonTree (
23
24
  options,
24
25
  getJSONRef,
25
26
  skeletonTrees,
27
+ skeletonNodes,
26
28
  validates,
27
29
  validationErrors,
28
30
  normalizedLayouts,
@@ -30,21 +32,25 @@ export function makeSkeletonTree (
30
32
  pointer,
31
33
  title
32
34
  ) {
33
- const root = makeSkeletonNode(
34
- schema,
35
- schemaId,
36
- options,
37
- getJSONRef,
38
- skeletonTrees,
39
- validates,
40
- validationErrors,
41
- normalizedLayouts,
42
- expressions,
43
- '',
44
- pointer,
45
- null,
46
- true
47
- )
48
- validates.push(root.pointer)
49
- return { title, root }
35
+ if (!skeletonNodes[pointer]) {
36
+ // @ts-ignore
37
+ skeletonNodes[pointer] = 'recursing'
38
+ skeletonNodes[pointer] = makeSkeletonNode(
39
+ schema,
40
+ schemaId,
41
+ options,
42
+ getJSONRef,
43
+ skeletonTrees,
44
+ skeletonNodes,
45
+ validates,
46
+ validationErrors,
47
+ normalizedLayouts,
48
+ expressions,
49
+ '',
50
+ pointer,
51
+ true
52
+ )
53
+ validates.push(pointer)
54
+ }
55
+ return { title, root: pointer }
50
56
  }
@@ -41,8 +41,9 @@ export type PartialCompileOptions = Partial<Omit<CompileOptions, 'messages'>> &
41
41
  export interface CompiledLayout {
42
42
  options?: CompileOptions
43
43
  schema?: SchemaObject
44
- mainTree: string,
44
+ mainTree: string
45
45
  skeletonTrees: Record<string, SkeletonTree>
46
+ skeletonNodes: Record<string, SkeletonNode>
46
47
  validates: Record<string, ValidateFunction>
47
48
  validationErrors: Record<string, string[]>
48
49
  normalizedLayouts: Record<string, NormalizedLayout>
@@ -57,7 +58,7 @@ export interface CompiledLayout {
57
58
  // it will be used to instantiate a StateLayoutTree with 1 validation context
58
59
  export interface SkeletonTree {
59
60
  title: string
60
- root: SkeletonNode
61
+ root: string
61
62
  }
62
63
 
63
64
  // a skeleton node is a light recursive structure
@@ -65,12 +66,12 @@ export interface SkeletonTree {
65
66
  export interface SkeletonNode {
66
67
  key: string | number
67
68
  pointer: string
68
- parentPointer: string | null
69
+ refPointer: string
69
70
  pure: boolean
70
71
  propertyKeys: string[]
71
72
  roPropertyKeys: string[]
72
73
  condition?: Expression
73
- children?: SkeletonNode[] // optional children in the case of arrays and object nodes
74
+ children?: string[] // optional children in the case of arrays and object nodes
74
75
  childrenTrees?: string[] // other trees that can be instantiated with separate validation (for example in the case of new array items of oneOfs, etc)
75
76
  required?: boolean
76
77
  nullable?: boolean
@@ -138,6 +138,7 @@ const produceCompositeChildrenOptions = produce((draft, section) => {
138
138
  })
139
139
 
140
140
  /**
141
+ * should match if an error belongs to this exact node
141
142
  * @param {import('ajv').ErrorObject} error
142
143
  * @param {import('../index.js').SkeletonNode} skeleton
143
144
  * @param {string} dataPath
@@ -147,11 +148,12 @@ const produceCompositeChildrenOptions = produce((draft, section) => {
147
148
  const matchError = (error, skeleton, dataPath, parentDataPath) => {
148
149
  const originalError = error.params?.errors?.[0] ?? error
149
150
  if (parentDataPath === originalError.instancePath && originalError.params?.missingProperty === skeleton.key) return true
150
- if (originalError.instancePath === dataPath && originalError.schemaPath === skeleton.pointer) return true
151
+ if (originalError.instancePath === dataPath && (originalError.schemaPath === skeleton.pointer || originalError.schemaPath === skeleton.refPointer)) return true
151
152
  return false
152
153
  }
153
154
 
154
155
  /**
156
+ * should match if an error belongs to a child of the current node but the child was not displayed
155
157
  * @param {import('ajv').ErrorObject} error
156
158
  * @param {import('../index.js').SkeletonNode} skeleton
157
159
  * @param {string} dataPath
@@ -159,11 +161,15 @@ const matchError = (error, skeleton, dataPath, parentDataPath) => {
159
161
  * @returns {boolean}
160
162
  */
161
163
  const matchChildError = (error, skeleton, dataPath, parentDataPath) => {
164
+ const originalError = error.params?.errors?.[0] ?? error
162
165
  if (!(
163
- error.schemaPath === skeleton.pointer ||
164
- error.schemaPath.startsWith(skeleton.pointer + '/')
166
+ originalError.schemaPath === skeleton.pointer ||
167
+ originalError.schemaPath.startsWith(skeleton.pointer + '/')
168
+ ) && !(
169
+ originalError.schemaPath === skeleton.refPointer ||
170
+ originalError.schemaPath.startsWith(skeleton.refPointer + '/')
165
171
  )) return false
166
- if (error.instancePath.startsWith(dataPath)) return true
172
+ if (originalError.instancePath.startsWith(dataPath)) return true
167
173
  return false
168
174
  }
169
175
 
@@ -303,7 +309,9 @@ export function createStateNode (
303
309
  ['remove', 'hide'].includes(options.readOnlyPropertiesMode) &&
304
310
  skeleton.roPropertyKeys?.includes(/** @type {string} */(childLayout.key))
305
311
  ) continue
306
- const childSkeleton = skeleton.children?.find(c => c.key === childLayout.key) ?? skeleton
312
+ let childSkeleton = skeleton
313
+ const childSkeletonKey = skeleton.children?.find(c => compiledLayout.skeletonNodes[c].key === childLayout.key)
314
+ if (childSkeletonKey !== undefined) childSkeleton = compiledLayout.skeletonNodes[childSkeletonKey]
307
315
  if (childSkeleton.condition) {
308
316
  if (!evalExpression(compiledLayout.expressions, childSkeleton.condition, objectData, parentOptions, display, layout, compiledLayout.validates, context.rootData, parentContext)) {
309
317
  continue
@@ -337,16 +345,19 @@ export function createStateNode (
337
345
  if (key === '$oneOf' && skeleton.childrenTrees) {
338
346
  // find the oneOf child that was either previously selected, if none were selected select the child that is valid with current data
339
347
  /** @type {number} */
340
- const activeChildTreeIndex = fullKey in context.activatedItems ? context.activatedItems[fullKey] : skeleton.childrenTrees?.findIndex((childTree) => compiledLayout.validates[compiledLayout.skeletonTrees[childTree].root.pointer](data))
348
+ const activeChildTreeIndex = fullKey in context.activatedItems ? context.activatedItems[fullKey] : skeleton.childrenTrees?.findIndex((childTree) => compiledLayout.validates[compiledLayout.skeletonTrees[childTree].root](data))
341
349
  if (activeChildTreeIndex !== -1) {
342
350
  context.errors = context.errors?.filter(error => {
343
351
  const originalError = error.params?.errors?.[0] ?? error
352
+ // console.log(originalError.schemaPath, skeleton.pointer, skeleton.refPointer)
344
353
  // if an item was selected, remove the oneOf error
345
- if (originalError.schemaPath === skeleton.pointer && originalError.keyword === 'oneOf') return false
354
+ if ((originalError.schemaPath === skeleton.pointer || originalError.schemaPath === skeleton.refPointer) && originalError.keyword === 'oneOf') return false
346
355
  // also remove the errors from other children of the oneOf
347
356
  if (originalError.schemaPath.startsWith(skeleton.pointer) && !originalError.schemaPath.startsWith(skeleton.pointer + '/' + activeChildTreeIndex)) return false
357
+ if (originalError.schemaPath.startsWith(skeleton.refPointer) && !originalError.schemaPath.startsWith(skeleton.refPointer + '/' + activeChildTreeIndex)) return false
348
358
  return true
349
359
  })
360
+ // console.log(context.errors?.map(error => error.params?.errors?.[0] ?? error))
350
361
  const activeChildKey = `${fullKey}/${activeChildTreeIndex}`
351
362
  if (context.autofocusTarget === fullKey) context.autofocusTarget = activeChildKey
352
363
  const activeChildTree = compiledLayout.skeletonTrees[skeleton.childrenTrees[activeChildTreeIndex]]
@@ -360,7 +371,7 @@ export function createStateNode (
360
371
  fullKey,
361
372
  dataPath,
362
373
  dataPath,
363
- activeChildTree.root,
374
+ compiledLayout.skeletonNodes[activeChildTree.root],
364
375
  null,
365
376
  display,
366
377
  data,
@@ -374,7 +385,7 @@ export function createStateNode (
374
385
 
375
386
  if (layout.comp === 'list') {
376
387
  const arrayData = /** @type {unknown[]} */(data ?? [])
377
- const childSkeleton = /** @type {import('../index.js').SkeletonNode} */(skeleton?.childrenTrees?.[0] && compiledLayout.skeletonTrees[skeleton?.childrenTrees?.[0]]?.root)
388
+ const childSkeleton = /** @type {import('../index.js').SkeletonNode} */(skeleton?.childrenTrees?.[0] && compiledLayout.skeletonNodes[compiledLayout.skeletonTrees[skeleton?.childrenTrees?.[0]]?.root])
378
389
  const listItemOptions = layout.listEditMode === 'inline' ? options : produceReadonlyArrayItemOptions(options)
379
390
  children = []
380
391
  let focusChild = context.autofocusTarget === fullKey