@json-layout/core 0.21.0 → 0.22.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 (50) hide show
  1. package/package.json +1 -1
  2. package/src/compile/index.js +17 -9
  3. package/src/compile/serialize.js +2 -1
  4. package/src/compile/skeleton-node.js +88 -30
  5. package/src/compile/skeleton-tree.js +22 -2
  6. package/src/compile/types.ts +3 -2
  7. package/src/compile/utils/resolve-refs.js +99 -38
  8. package/src/state/index.js +11 -7
  9. package/src/state/state-node.js +12 -9
  10. package/src/state/types.ts +2 -2
  11. package/types/compile/index.d.ts +0 -16
  12. package/types/compile/index.d.ts.map +0 -1
  13. package/types/compile/serialize.d.ts +0 -6
  14. package/types/compile/serialize.d.ts.map +0 -1
  15. package/types/compile/skeleton-node.d.ts +0 -18
  16. package/types/compile/skeleton-node.d.ts.map +0 -1
  17. package/types/compile/skeleton-tree.d.ts +0 -13
  18. package/types/compile/skeleton-tree.d.ts.map +0 -1
  19. package/types/compile/types.d.ts +0 -57
  20. package/types/compile/types.d.ts.map +0 -1
  21. package/types/compile/utils/resolve-refs.d.ts +0 -9
  22. package/types/compile/utils/resolve-refs.d.ts.map +0 -1
  23. package/types/i18n/en.d.ts +0 -3
  24. package/types/i18n/en.d.ts.map +0 -1
  25. package/types/i18n/fr.d.ts +0 -3
  26. package/types/i18n/fr.d.ts.map +0 -1
  27. package/types/i18n/index.d.ts +0 -3
  28. package/types/i18n/index.d.ts.map +0 -1
  29. package/types/i18n/types.d.ts +0 -36
  30. package/types/i18n/types.d.ts.map +0 -1
  31. package/types/index.d.ts +0 -5
  32. package/types/index.d.ts.map +0 -1
  33. package/types/state/index.d.ts +0 -285
  34. package/types/state/index.d.ts.map +0 -1
  35. package/types/state/state-node.d.ts +0 -36
  36. package/types/state/state-node.d.ts.map +0 -1
  37. package/types/state/state-tree.d.ts +0 -13
  38. package/types/state/state-tree.d.ts.map +0 -1
  39. package/types/state/types.d.ts +0 -175
  40. package/types/state/types.d.ts.map +0 -1
  41. package/types/state/utils/display.d.ts +0 -51
  42. package/types/state/utils/display.d.ts.map +0 -1
  43. package/types/state/utils/immutable.d.ts +0 -21
  44. package/types/state/utils/immutable.d.ts.map +0 -1
  45. package/types/utils/build.d.ts +0 -5
  46. package/types/utils/build.d.ts.map +0 -1
  47. package/types/utils/clone.d.ts +0 -3
  48. package/types/utils/clone.d.ts.map +0 -1
  49. package/types/utils/doc-options.d.ts +0 -14
  50. 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.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Compilation and state management utilities for JSON Layout.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,11 +9,11 @@ import MarkdownIt from 'markdown-it'
9
9
  import { produce } from 'immer'
10
10
  import i18n from '../i18n/index.js'
11
11
  import { makeSkeletonTree } from './skeleton-tree.js'
12
- import { resolveRefs } from './utils/resolve-refs.js'
12
+ import { resolveLocaleRefs } from './utils/resolve-refs.js'
13
13
  import clone from '../utils/clone.js'
14
14
  import { standardComponents } from '@json-layout/vocabulary'
15
15
 
16
- export { resolveRefs } from './utils/resolve-refs.js'
16
+ export { resolveLocaleRefs } from './utils/resolve-refs.js'
17
17
 
18
18
  /**
19
19
  * @typedef {import('./types.js').SkeletonTree} SkeletonTree
@@ -103,9 +103,8 @@ export function compile (_schema, partialOptions = {}) {
103
103
  const options = fillOptions(partialOptions)
104
104
 
105
105
  const schema = /** @type {import('ajv').SchemaObject} */(clone(_schema))
106
-
107
106
  schema.$id = schema.$id ?? '_jl'
108
- resolveRefs(schema, options.ajv, options.locale)
107
+ const getJSONRef = resolveLocaleRefs(schema, options.ajv, options.locale)
109
108
 
110
109
  /** @type {string[]} */
111
110
  const validatePointers = []
@@ -115,18 +114,26 @@ export function compile (_schema, partialOptions = {}) {
115
114
  const expressionsDefinitions = []
116
115
  /** @type {Record<string, string[]>} */
117
116
  const validationErrors = {}
118
-
119
- const skeletonTree = makeSkeletonTree(
117
+ /** @type {Record<string, import('./types.js').SkeletonTree>} */
118
+ const skeletonTrees = {}
119
+
120
+ // makeSkeletonTree also mutates the schema (adding some error messages)
121
+ const mainTreePointer = `${schema.$id}#`
122
+ // @ts-ignore
123
+ skeletonTrees[mainTreePointer] = 'recursing'
124
+ skeletonTrees[mainTreePointer] = makeSkeletonTree(
120
125
  schema,
126
+ schema.$id,
121
127
  options,
128
+ getJSONRef,
129
+ skeletonTrees,
122
130
  validatePointers,
123
131
  validationErrors,
124
132
  normalizedLayouts,
125
133
  expressionsDefinitions,
126
- `${schema.$id}#`,
134
+ mainTreePointer,
127
135
  'main'
128
136
  )
129
-
130
137
  options.ajv.addSchema(schema)
131
138
 
132
139
  const uriResolver = options.ajv.opts.uriResolver
@@ -168,7 +175,8 @@ export function compile (_schema, partialOptions = {}) {
168
175
  return {
169
176
  options,
170
177
  schema,
171
- skeletonTree,
178
+ mainTree: mainTreePointer,
179
+ skeletonTrees,
172
180
  validates,
173
181
  validationErrors,
174
182
  normalizedLayouts,
@@ -79,7 +79,8 @@ export const exportLocalizeErrors = localizeErrors;\n` + code
79
79
 
80
80
  const ast = parseModule(code)
81
81
  ast.exports.compiledLayout = {
82
- skeletonTree: compiledLayout.skeletonTree,
82
+ mainTree: compiledLayout.mainTree,
83
+ skeletonTrees: clone(compiledLayout.skeletonTrees),
83
84
  normalizedLayouts: clone(compiledLayout.normalizedLayouts),
84
85
  validates: {},
85
86
  validationErrors: compiledLayout.validationErrors,
@@ -1,16 +1,20 @@
1
1
  // import Debug from 'debug'
2
2
  import { normalizeLayoutFragment, isSwitchStruct, isGetItemsExpression, isGetItemsFetch, isItemsLayout, getSchemaFragmentType } from '@json-layout/vocabulary'
3
3
  import { makeSkeletonTree } from './skeleton-tree.js'
4
+ import { partialResolveRefs } from './utils/resolve-refs.js'
4
5
 
5
6
  /**
6
- * @param {any} schema
7
+ * @param {any} rawSchema
8
+ * @param {string} sourceSchemaId
7
9
  * @param {import('./index.js').CompileOptions} options
10
+ * @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
11
+ * @param {Record<string, import('./types.js').SkeletonTree>} skeletonTrees
8
12
  * @param {string[]} validates
9
13
  * @param {Record<string, string[]>} validationErrors
10
14
  * @param {Record<string, import('@json-layout/vocabulary').NormalizedLayout>} normalizedLayouts
11
15
  * @param {import('@json-layout/vocabulary').Expression[]} expressions
12
16
  * @param {string | number} key
13
- * @param {string} pointer
17
+ * @param {string} currentPointer
14
18
  * @param {string | null} parentPointer
15
19
  * @param {boolean} required
16
20
  * @param {string} [condition]
@@ -19,24 +23,37 @@ import { makeSkeletonTree } from './skeleton-tree.js'
19
23
  * @returns {import('./types.js').SkeletonNode}
20
24
  */
21
25
  export function makeSkeletonNode (
22
- schema,
26
+ rawSchema,
27
+ sourceSchemaId,
23
28
  options,
29
+ getJSONRef,
30
+ skeletonTrees,
24
31
  validates,
25
32
  validationErrors,
26
33
  normalizedLayouts,
27
34
  expressions,
28
35
  key,
29
- pointer,
36
+ currentPointer,
30
37
  parentPointer,
31
38
  required,
32
39
  condition,
33
40
  dependent,
34
41
  knownType
35
42
  ) {
43
+ let schemaId = sourceSchemaId
44
+ let schema = rawSchema
45
+ let pointer = currentPointer
46
+ let refFragment
47
+ if (schema.$ref) {
48
+ [refFragment, schemaId, pointer] = getJSONRef(sourceSchemaId, schema.$ref)
49
+ schema = {...rawSchema, ...refFragment}
50
+ delete schema.$ref
51
+ }
52
+ schema = partialResolveRefs(schema, schemaId, getJSONRef)
36
53
  const { type, nullable } = knownType ? { type: knownType, nullable: false } : getSchemaFragmentType(schema)
37
54
 
38
55
  // improve on ajv error messages based on ajv-errors (https://ajv.js.org/packages/ajv-errors.html)
39
- schema.errorMessage = schema.errorMessage ?? {}
56
+ rawSchema.errorMessage = rawSchema.errorMessage ?? {}
40
57
  if (!normalizedLayouts[pointer]) {
41
58
  const normalizationResult = normalizeLayoutFragment(
42
59
  /** @type {import('@json-layout/vocabulary').SchemaFragment} */(schema),
@@ -138,7 +155,10 @@ export function makeSkeletonNode (
138
155
  const dependent = schema.dependentRequired && Object.values(schema.dependentRequired).some(dependentProperties => dependentProperties.includes(propertyKey))
139
156
  node.children.push(makeSkeletonNode(
140
157
  schema.properties[propertyKey],
158
+ schemaId,
141
159
  options,
160
+ getJSONRef,
161
+ skeletonTrees,
142
162
  validates,
143
163
  validationErrors,
144
164
  normalizedLayouts,
@@ -150,20 +170,16 @@ export function makeSkeletonNode (
150
170
  undefined,
151
171
  dependent
152
172
  ))
153
- if (schema?.required?.includes(propertyKey)) {
154
- schema.errorMessage.required = schema.errorMessage.required ?? {}
155
- schema.errorMessage.required[propertyKey] = options.messages.errorRequired
156
- }
157
- if (schema.dependentRequired && Object.keys(schema.dependentRequired).includes(propertyKey)) {
158
- schema.errorMessage.dependentRequired = options.messages.errorRequired
159
- }
160
173
 
161
174
  if (schema.dependentSchemas?.[propertyKey] || (schema.dependencies?.[propertyKey] && !Array.isArray(schema.dependencies[propertyKey]))) {
162
175
  const dependentSchema = schema.dependentSchemas?.[propertyKey] ?? schema.dependencies[propertyKey]
163
176
  const dependentPointer = schema.dependentSchemas?.[propertyKey] ? `${pointer}/dependentSchemas/${propertyKey}` : `${pointer}/dependencies/${propertyKey}`
164
177
  node.children.push(makeSkeletonNode(
165
178
  dependentSchema,
179
+ schemaId,
166
180
  options,
181
+ getJSONRef,
182
+ skeletonTrees,
167
183
  validates,
168
184
  validationErrors,
169
185
  normalizedLayouts,
@@ -184,7 +200,10 @@ export function makeSkeletonNode (
184
200
  for (let i = 0; i < schema.allOf.length; i++) {
185
201
  const allOfNode = makeSkeletonNode(
186
202
  schema.allOf[i],
203
+ schemaId,
187
204
  options,
205
+ getJSONRef,
206
+ skeletonTrees,
188
207
  validates,
189
208
  validationErrors,
190
209
  normalizedLayouts,
@@ -220,23 +239,32 @@ export function makeSkeletonNode (
220
239
  validationErrors[oneOfPointer.replace('_jl#', '/')] = normalizationResult.errors
221
240
  }
222
241
  }
223
- /** @type {import('./types.js').SkeletonTree[]} */
242
+ /** @type {string[]} */
224
243
  const childrenTrees = []
225
244
  /** @type {string[]} */
226
245
  for (let i = 0; i < schema.oneOf.length; i++) {
227
246
  if (!schema.oneOf[i].type) schema.oneOf[i].type = type
228
247
  const title = schema.oneOf[i].title ?? `option ${i}`
229
248
  delete schema.oneOf[i].title
230
- childrenTrees.push(makeSkeletonTree(
231
- schema.oneOf[i],
232
- options,
233
- validates,
234
- validationErrors,
235
- normalizedLayouts,
236
- expressions,
237
- `${oneOfPointer}/${i}`,
238
- title
239
- ))
249
+ const childTreePointer = `${oneOfPointer}/${i}`
250
+ if (!skeletonTrees[childTreePointer]) {
251
+ // @ts-ignore
252
+ skeletonTrees[childTreePointer] = 'recursing'
253
+ skeletonTrees[childTreePointer] = makeSkeletonTree(
254
+ schema.oneOf[i],
255
+ schemaId,
256
+ options,
257
+ getJSONRef,
258
+ skeletonTrees,
259
+ validates,
260
+ validationErrors,
261
+ normalizedLayouts,
262
+ expressions,
263
+ childTreePointer,
264
+ title
265
+ )
266
+ }
267
+ childrenTrees.push(childTreePointer)
240
268
  }
241
269
  node.children = node.children ?? []
242
270
  node.children.push({
@@ -244,7 +272,7 @@ export function makeSkeletonNode (
244
272
  pointer: `${pointer}/oneOf`,
245
273
  parentPointer: pointer,
246
274
  childrenTrees,
247
- pure: childrenTrees[0].root.pure,
275
+ pure: skeletonTrees[childrenTrees[0]]?.root.pure,
248
276
  propertyKeys: [],
249
277
  roPropertyKeys: []
250
278
  })
@@ -257,7 +285,10 @@ export function makeSkeletonNode (
257
285
  node.children = node.children ?? []
258
286
  node.children.push(makeSkeletonNode(
259
287
  schema.then,
288
+ schemaId,
260
289
  options,
290
+ getJSONRef,
291
+ skeletonTrees,
261
292
  validates,
262
293
  validationErrors,
263
294
  normalizedLayouts,
@@ -275,7 +306,10 @@ export function makeSkeletonNode (
275
306
  node.children = node.children ?? []
276
307
  node.children.push(makeSkeletonNode(
277
308
  schema.else,
309
+ schemaId,
278
310
  options,
311
+ getJSONRef,
312
+ skeletonTrees,
279
313
  validates,
280
314
  validationErrors,
281
315
  normalizedLayouts,
@@ -290,6 +324,16 @@ export function makeSkeletonNode (
290
324
  ))
291
325
  }
292
326
  }
327
+
328
+ for (const propertyKey of node.propertyKeys) {
329
+ if (schema?.required?.includes(propertyKey)) {
330
+ schema.errorMessage.required = schema.errorMessage.required ?? {}
331
+ schema.errorMessage.required[propertyKey] = options.messages.errorRequired
332
+ }
333
+ if (schema.dependentRequired && Object.keys(schema.dependentRequired).includes(propertyKey)) {
334
+ schema.errorMessage.dependentRequired = options.messages.errorRequired
335
+ }
336
+ }
293
337
  }
294
338
 
295
339
  if (type === 'array' && schema.items) {
@@ -297,7 +341,10 @@ export function makeSkeletonNode (
297
341
  node.children = schema.items.map((/** @type {any} */ itemSchema, /** @type {number} */ i) => {
298
342
  return makeSkeletonNode(
299
343
  itemSchema,
344
+ schemaId,
300
345
  options,
346
+ getJSONRef,
347
+ skeletonTrees,
301
348
  validates,
302
349
  validationErrors,
303
350
  normalizedLayouts,
@@ -309,23 +356,34 @@ export function makeSkeletonNode (
309
356
  )
310
357
  })
311
358
  } else {
312
- node.childrenTrees = [
313
- makeSkeletonTree(
359
+ const childTreePointer = `${pointer}/items`
360
+ if (!skeletonTrees[childTreePointer]) {
361
+ // @ts-ignore
362
+ skeletonTrees[childTreePointer] = 'recursing'
363
+ skeletonTrees[childTreePointer] = makeSkeletonTree(
314
364
  schema.items,
365
+ schemaId,
315
366
  options,
367
+ getJSONRef,
368
+ skeletonTrees,
316
369
  validates,
317
370
  validationErrors,
318
371
  normalizedLayouts,
319
372
  expressions,
320
- `${pointer}/items`,
373
+ childTreePointer,
321
374
  schema.items.title
322
375
  )
323
- ]
376
+ }
377
+ node.childrenTrees = [childTreePointer]
324
378
  }
325
379
  }
326
380
 
327
- for (const child of node.children || []) if (!child.pure) node.pure = false
328
- for (const childTree of node.childrenTrees || []) if (!childTree.root.pure) node.pure = false
381
+ for (const child of node.children || []) {
382
+ if (!child.pure) node.pure = false
383
+ }
384
+ for (const childTree of node.childrenTrees || []) {
385
+ if (!skeletonTrees[childTree]?.root?.pure) node.pure = false
386
+ }
329
387
 
330
388
  return node
331
389
  }
@@ -5,7 +5,10 @@ import { makeSkeletonNode } from './skeleton-node.js'
5
5
 
6
6
  /**
7
7
  * @param {any} schema
8
+ * @param {string} schemaId
8
9
  * @param {import('./index.js').CompileOptions} options
10
+ * @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
11
+ * @param {Record<string, import('./types.js').SkeletonTree>} skeletonTrees
9
12
  * @param {string[]} validates
10
13
  * @param {Record<string, string[]>} validationErrors
11
14
  * @param {Record<string, import('@json-layout/vocabulary').NormalizedLayout>} normalizedLayouts
@@ -16,7 +19,10 @@ import { makeSkeletonNode } from './skeleton-node.js'
16
19
  */
17
20
  export function makeSkeletonTree (
18
21
  schema,
22
+ schemaId,
19
23
  options,
24
+ getJSONRef,
25
+ skeletonTrees,
20
26
  validates,
21
27
  validationErrors,
22
28
  normalizedLayouts,
@@ -24,7 +30,21 @@ export function makeSkeletonTree (
24
30
  pointer,
25
31
  title
26
32
  ) {
27
- const root = makeSkeletonNode(schema, options, validates, validationErrors, normalizedLayouts, expressions, '', pointer, null, true)
28
- validates.push(pointer)
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)
29
49
  return { title, root }
30
50
  }
@@ -41,7 +41,8 @@ export type PartialCompileOptions = Partial<Omit<CompileOptions, 'messages'>> &
41
41
  export interface CompiledLayout {
42
42
  options?: CompileOptions
43
43
  schema?: SchemaObject
44
- skeletonTree: SkeletonTree
44
+ mainTree: string,
45
+ skeletonTrees: Record<string, SkeletonTree>
45
46
  validates: Record<string, ValidateFunction>
46
47
  validationErrors: Record<string, string[]>
47
48
  normalizedLayouts: Record<string, NormalizedLayout>
@@ -70,7 +71,7 @@ export interface SkeletonNode {
70
71
  roPropertyKeys: string[]
71
72
  condition?: Expression
72
73
  children?: SkeletonNode[] // optional children in the case of arrays and object nodes
73
- childrenTrees?: SkeletonTree[] // other trees that can be instantiated with separate validation (for example in the case of new array items of oneOfs, etc)
74
+ childrenTrees?: string[] // other trees that can be instantiated with separate validation (for example in the case of new array items of oneOfs, etc)
74
75
  required?: boolean
75
76
  nullable?: boolean
76
77
  }
@@ -1,69 +1,130 @@
1
1
  import ajvModule from 'ajv/dist/2019.js'
2
-
3
- const Ajv = ajvModule.default
2
+ import clone from '../../utils/clone.js'
4
3
 
5
4
  /**
6
5
  * @param {Record<string, import('ajv').SchemaObject>} schemas
7
- * @param {string} ref
8
6
  * @param {ajvModule.default} ajv
9
- * @returns {[any, string]}
7
+ * @returns {(schemaId: string, ref: string) => [any, string, string]}
10
8
  */
11
- const getJSONRef = (schemas, ref, ajv) => {
12
- const [schemaId, pointer] = ref.split('#')
13
- schemas[schemaId] = schemas[schemaId] ?? (ajv.getSchema(schemaId)?.schema)
14
- if (!schemas[schemaId]) throw new Error(`reference not found ${schemaId}`)
15
- const pointerParts = pointer.split('/').filter(p => !!p)
16
- const { value: fragment } = pointerParts.reduce((a, pointerPart) => {
17
- a.path.push(pointerPart)
18
- if (!(pointerPart in a.value)) throw new Error(`reference not found ${schemaId}#${a.path.join('/')}`)
19
- a.value = a.value[pointerPart]
20
- return a
21
- }, { path: ['/'], value: schemas[schemaId] })
22
- return [fragment, schemaId]
9
+ const prepareGetJSONRef = (schemas, ajv) =>
10
+ {
11
+ return (sourceSchemaId, ref) => {
12
+ const fullRef = ajv.opts.uriResolver.resolve(sourceSchemaId, ref)
13
+ const [schemaId, pointer] = fullRef.split('#')
14
+ schemas[schemaId] = schemas[schemaId] ?? (ajv.getSchema(schemaId)?.schema)
15
+ if (!schemas[schemaId]) throw new Error(`reference not found ${schemaId}`)
16
+ const pointerParts = pointer.split('/').filter(p => !!p)
17
+ const { value: fragment } = pointerParts.reduce((a, pointerPart) => {
18
+ a.path.push(pointerPart)
19
+ if (!(pointerPart in a.value)) throw new Error(`reference not found ${schemaId}#${a.path.join('/')}`)
20
+ a.value = a.value[pointerPart]
21
+ return a
22
+ }, { path: /** @type {string[]} */([]), value: schemas[schemaId] })
23
+ return [fragment, schemaId, fullRef]
24
+ }
25
+ }
26
+
27
+ /**
28
+ * mutates a schema by replacing ~$locale~ in all refs
29
+ * @param {import('ajv').SchemaObject} schema
30
+ * @param {ajvModule.default} ajv
31
+ * @param {string} locale
32
+ * @returns {(schemaId: string, ref: string) => [any, string, string]}
33
+ */
34
+ export function resolveLocaleRefs (schema, ajv, locale = 'en') {
35
+ if (!schema.$id) throw new Error('missing schema id')
36
+ const getJSONRef = prepareGetJSONRef({ [schema.$id]: schema }, ajv)
37
+ /** @type {any[]} */
38
+ const recursed = []
39
+ recurseResolveLocale(schema, schema.$id, getJSONRef, locale, recursed)
40
+ return getJSONRef
23
41
  }
24
42
 
25
43
  /**
26
- * @param {Record<string, import('ajv').SchemaObject>} schemas
27
44
  * @param {import('ajv').SchemaObject} schemaFragment
28
45
  * @param {string} schemaId
29
- * @param {ajvModule.default} ajv
46
+ * @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
30
47
  * @param {string} locale
31
- * @returns {import('ajv').SchemaObject}
48
+ * @param {any[]} recursed
32
49
  */
33
- const recurse = (schemas, schemaFragment, schemaId, ajv, locale = 'en') => {
50
+
51
+ const recurseResolveLocale = (schemaFragment, schemaId, getJSONRef, locale, recursed) => {
52
+ if (recursed.includes(schemaFragment)) return
53
+ recursed.push(schemaFragment)
34
54
  for (const key of Object.keys(schemaFragment)) {
35
55
  if (schemaFragment[key] && typeof schemaFragment[key] === 'object') {
36
56
  if ('$ref' in schemaFragment[key]) {
37
- const fullRef = ajv.opts.uriResolver.resolve(schemaId, schemaFragment[key].$ref).replace('~$locale~', locale)
38
- const fullRefDefaultLocale = ajv.opts.uriResolver.resolve(schemaId, schemaFragment[key].$ref).replace('~$locale~', 'en')
57
+ const ref = schemaFragment[key].$ref.replace('~$locale~', locale)
58
+ const refDefaultLocale = schemaFragment[key].$ref.replace('~$locale~', 'en')
39
59
  let refFragment, refSchemaId
40
60
  try {
41
- [refFragment, refSchemaId] = getJSONRef(schemas, fullRef, ajv)
61
+ [refFragment, refSchemaId] = getJSONRef(schemaId, ref)
62
+ schemaFragment[key].$ref = ref
42
63
  } catch (err) {
43
- [refFragment, refSchemaId] = getJSONRef(schemas, fullRefDefaultLocale, ajv)
64
+ [refFragment, refSchemaId] = getJSONRef(schemaId, refDefaultLocale)
65
+ schemaFragment[key].$ref = refDefaultLocale
44
66
  }
45
- if (typeof refFragment === 'object' && !Array.isArray(refFragment)) {
46
- schemaFragment[key] = { ...refFragment, ...schemaFragment[key] }
47
- delete schemaFragment[key].$ref
48
- } else {
67
+ if (typeof refFragment === 'string') {
49
68
  schemaFragment[key] = refFragment
69
+ } else {
70
+ recurseResolveLocale(refFragment, refSchemaId, getJSONRef, locale, recursed)
50
71
  }
51
- recurse(schemas, schemaFragment[key], refSchemaId, ajv, locale)
52
72
  } else {
53
- recurse(schemas, schemaFragment[key], schemaId, ajv, locale)
73
+ recurseResolveLocale(schemaFragment[key], schemaId, getJSONRef, locale, recursed)
54
74
  }
55
75
  }
56
76
  }
57
- return schemaFragment
58
77
  }
59
78
 
60
79
  /**
80
+ * partially resolve a schema but not recursively, used for layout normalization
61
81
  * @param {import('ajv').SchemaObject} schema
62
- * @param {ajvModule.default} ajv
63
- * @param {string} locale
64
- * @returns {import('ajv').SchemaObject}
82
+ * @param {string} schemaId
83
+ * @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
65
84
  */
66
- export function resolveRefs (schema, ajv, locale = 'en') {
67
- if (!schema.$id) throw new Error('missing schema id')
68
- return recurse({ [schema.$id]: schema }, schema, schema.$id, ajv ?? new Ajv(), locale)
69
- }
85
+ export function partialResolveRefs (schema, schemaId, getJSONRef) {
86
+ let clonedSchema = null
87
+ if (schema.items && schema.items.$ref) {
88
+ const [refFragment] = getJSONRef(schemaId, schema.items.$ref)
89
+ clonedSchema = clonedSchema ?? clone(schema)
90
+ clonedSchema.items = { ...refFragment, ... schema.items }
91
+ }
92
+ if (schema.properties) {
93
+ for (const key in schema.properties) {
94
+ if (schema.properties[key].$ref) {
95
+ const [refFragment] = getJSONRef(schemaId, schema.properties[key].$ref)
96
+ clonedSchema = clonedSchema ?? clone(schema)
97
+ clonedSchema.properties[key] = { ...refFragment, ... schema.properties[key] }
98
+ }
99
+ }
100
+ }
101
+ if (schema.oneOf) {
102
+ for (let i = 0; i < schema.oneOf.length; i++) {
103
+ if (schema.oneOf[i].$ref) {
104
+ const [refFragment] = getJSONRef(schemaId, schema.oneOf[i].$ref)
105
+ clonedSchema = clonedSchema ?? clone(schema)
106
+ clonedSchema.oneOf[i] = { ...refFragment, ... schema.oneOf[i] }
107
+ }
108
+ }
109
+ }
110
+ if (schema.anyOf) {
111
+ for (let i = 0; i < schema.anyOf.length; i++) {
112
+ if (schema.anyOf[i].$ref) {
113
+ const [refFragment] = getJSONRef(schemaId, schema.anyOf[i].$ref)
114
+ clonedSchema = clonedSchema ?? clone(schema)
115
+ clonedSchema.anyOf[i] = { ...refFragment, ... schema.anyOf[i] }
116
+ }
117
+ }
118
+ }
119
+ if (schema.allOf) {
120
+ for (let i = 0; i < schema.allOf.length; i++) {
121
+ if (schema.allOf[i].$ref) {
122
+ const [refFragment] = getJSONRef(schemaId, schema.allOf[i].$ref)
123
+ clonedSchema = clonedSchema ?? clone(schema)
124
+ clonedSchema.allOf[i] = { ...refFragment, ... schema.allOf[i] }
125
+ }
126
+ }
127
+ }
128
+
129
+ return clonedSchema ?? schema
130
+ }
@@ -227,7 +227,7 @@ export class StatefulLayout {
227
227
  this._previousAutofocusTarget = null
228
228
  this._data = data
229
229
  this.initValidationState()
230
- this.activeItems = {}
230
+ this.activatedItems = {}
231
231
  this.updateState()
232
232
  this.handleAutofocus()
233
233
  }
@@ -295,7 +295,7 @@ export class StatefulLayout {
295
295
  createStateTree (rehydrate = false) {
296
296
  /** @type {CreateStateTreeContext} */
297
297
  const createStateTreeContext = {
298
- activeItems: this.activeItems,
298
+ activatedItems: this.activatedItems,
299
299
  autofocusTarget: this._autofocusTarget,
300
300
  initial: !this._lastCreateStateTreeContext,
301
301
  rehydrate,
@@ -322,7 +322,6 @@ export class StatefulLayout {
322
322
  validatedChildren: createStateTreeContext.nodes.filter(n => n.validated).map(n => n.fullKey)
323
323
  }
324
324
  }
325
- this.activeItems = createStateTreeContext.activeItems
326
325
  this.files = shallowProduceArray(this.files, createStateTreeContext.files)
327
326
  }
328
327
 
@@ -416,7 +415,7 @@ export class StatefulLayout {
416
415
  }
417
416
 
418
417
  if (activateKey !== undefined) {
419
- this.activeItems = produce(this.activeItems, draft => { draft[node.fullKey] = activateKey })
418
+ this.activatedItems = produce(this.activatedItems, draft => { draft[node.fullKey] = activateKey })
420
419
  this._autofocusTarget = node.fullKey + '/' + activateKey
421
420
  }
422
421
  if (node.parentFullKey === null) {
@@ -625,14 +624,14 @@ export class StatefulLayout {
625
624
  /**
626
625
  * @type {Record<string, number>}
627
626
  */
628
- activeItems
627
+ activatedItems
629
628
 
630
629
  /**
631
630
  * @param {StateNode} node
632
631
  * @param {number} key
633
632
  */
634
633
  activateItem (node, key) {
635
- this.activeItems = produce(this.activeItems, draft => { draft[node.fullKey] = key })
634
+ this.activatedItems = produce(this.activatedItems, draft => { draft[node.fullKey] = key })
636
635
  this._autofocusTarget = node.fullKey + '/' + key
637
636
  if (node.key === '$oneOf') {
638
637
  this.input(node, undefined)
@@ -646,7 +645,12 @@ export class StatefulLayout {
646
645
  * @param {StateNode} node
647
646
  */
648
647
  deactivateItem (node) {
649
- this.activeItems = produce(this.activeItems, draft => { delete draft[node.fullKey] })
648
+ // also deactivate children oneOf for example
649
+ this.activatedItems = produce(this.activatedItems, draft => {
650
+ for (const key in draft) {
651
+ if (key.startsWith(node.fullKey)) delete draft[key]
652
+ }
653
+ })
650
654
  this.updateState()
651
655
  }
652
656