@jsonforms/core 3.0.0-beta.1 → 3.0.0-beta.4

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 (115) hide show
  1. package/README.md +11 -0
  2. package/docs/assets/js/search.json +1 -1
  3. package/docs/enums/ruleeffect.html +4 -4
  4. package/docs/globals.html +357 -217
  5. package/docs/index.html +29 -4
  6. package/docs/interfaces/addcellrendereraction.html +3 -3
  7. package/docs/interfaces/addrendereraction.html +3 -3
  8. package/docs/interfaces/adduischemaaction.html +3 -3
  9. package/docs/interfaces/andcondition.html +2 -2
  10. package/docs/interfaces/arraycontrolprops.html +21 -21
  11. package/docs/interfaces/arraylayoutprops.html +21 -21
  12. package/docs/interfaces/categorization.html +5 -5
  13. package/docs/interfaces/category.html +5 -5
  14. package/docs/interfaces/cellprops.html +12 -12
  15. package/docs/interfaces/combinatorrendererprops.html +143 -36
  16. package/docs/interfaces/composablecondition.html +2 -2
  17. package/docs/interfaces/condition.html +1 -1
  18. package/docs/interfaces/controlelement.html +22 -4
  19. package/docs/interfaces/controlprops.html +16 -16
  20. package/docs/interfaces/controlstate.html +2 -2
  21. package/docs/interfaces/controlwithdetailprops.html +17 -17
  22. package/docs/interfaces/dispatchcellprops.html +10 -10
  23. package/docs/interfaces/dispatchcellstateprops.html +10 -10
  24. package/docs/interfaces/dispatchpropsofarraycontrol.html +4 -4
  25. package/docs/interfaces/dispatchpropsofcontrol.html +1 -1
  26. package/docs/interfaces/dispatchpropsofmultienumcontrol.html +2 -2
  27. package/docs/interfaces/enumcellprops.html +13 -13
  28. package/docs/interfaces/enumoption.html +2 -2
  29. package/docs/interfaces/grouplayout.html +5 -5
  30. package/docs/interfaces/horizontallayout.html +4 -4
  31. package/docs/interfaces/initactionoptions.html +14 -0
  32. package/docs/interfaces/internationalizable.html +167 -0
  33. package/docs/interfaces/jsonformscore.html +17 -3
  34. package/docs/interfaces/jsonformsprops.html +9 -9
  35. package/docs/interfaces/labeldescription.html +2 -2
  36. package/docs/interfaces/labelelement.html +4 -4
  37. package/docs/interfaces/layout.html +4 -4
  38. package/docs/interfaces/layoutprops.html +10 -10
  39. package/docs/interfaces/leafcondition.html +2 -2
  40. package/docs/interfaces/orcondition.html +2 -2
  41. package/docs/interfaces/ownpropsofcell.html +9 -9
  42. package/docs/interfaces/ownpropsofcontrol.html +9 -12
  43. package/docs/interfaces/ownpropsofenum.html +1 -1
  44. package/docs/interfaces/ownpropsofenumcell.html +10 -10
  45. package/docs/interfaces/ownpropsofjsonformsrenderer.html +8 -8
  46. package/docs/interfaces/ownpropsoflayout.html +9 -9
  47. package/docs/interfaces/ownpropsofmasterlistitem.html +6 -6
  48. package/docs/interfaces/ownpropsofrenderer.html +8 -8
  49. package/docs/interfaces/registerdefaultdataaction.html +3 -3
  50. package/docs/interfaces/removecellrendereraction.html +3 -3
  51. package/docs/interfaces/removerendereraction.html +3 -3
  52. package/docs/interfaces/removeuischemaaction.html +2 -2
  53. package/docs/interfaces/rendererprops.html +9 -9
  54. package/docs/interfaces/rule.html +2 -2
  55. package/docs/interfaces/schemabasedcondition.html +2 -2
  56. package/docs/interfaces/setajvaction.html +2 -2
  57. package/docs/interfaces/setconfigaction.html +2 -2
  58. package/docs/interfaces/setlocaleaction.html +2 -2
  59. package/docs/interfaces/setschemaaction.html +2 -2
  60. package/docs/interfaces/settranslatoraction.html +3 -3
  61. package/docs/interfaces/setuischemaaction.html +2 -2
  62. package/docs/interfaces/setvalidationmodeaction.html +2 -2
  63. package/docs/interfaces/statepropsofarraycontrol.html +17 -17
  64. package/docs/interfaces/statepropsofarraylayout.html +17 -17
  65. package/docs/interfaces/statepropsofcell.html +11 -11
  66. package/docs/interfaces/statepropsofcombinator.html +147 -40
  67. package/docs/interfaces/statepropsofcontrol.html +18 -15
  68. package/docs/interfaces/statepropsofcontrolwithdetail.html +16 -16
  69. package/docs/interfaces/statepropsofenumcell.html +12 -12
  70. package/docs/interfaces/statepropsofjsonformsrenderer.html +9 -9
  71. package/docs/interfaces/statepropsoflayout.html +10 -10
  72. package/docs/interfaces/statepropsofmasteritem.html +7 -7
  73. package/docs/interfaces/statepropsofrenderer.html +9 -9
  74. package/docs/interfaces/statepropsofscopedrenderer.html +12 -12
  75. package/docs/interfaces/uischemaelement.html +3 -3
  76. package/docs/interfaces/unregisterdefaultdataaction.html +2 -2
  77. package/docs/interfaces/updatei18naction.html +4 -4
  78. package/docs/interfaces/verticallayout.html +4 -4
  79. package/docs/interfaces/withclassname.html +1 -1
  80. package/lib/actions/actions.d.ts +1 -0
  81. package/lib/jsonforms-core.cjs.js +201 -179
  82. package/lib/jsonforms-core.cjs.js.map +1 -1
  83. package/lib/jsonforms-core.esm.js +182 -166
  84. package/lib/jsonforms-core.esm.js.map +1 -1
  85. package/lib/models/uischema.d.ts +10 -1
  86. package/lib/reducers/core.d.ts +1 -0
  87. package/lib/reducers/reducers.d.ts +2 -2
  88. package/lib/testers/testers.d.ts +8 -7
  89. package/lib/util/combinators.d.ts +0 -1
  90. package/lib/util/path.d.ts +10 -0
  91. package/lib/util/renderer.d.ts +2 -2
  92. package/lib/util/resolvers.d.ts +1 -1
  93. package/lib/util/util.d.ts +1 -1
  94. package/package.json +2 -2
  95. package/src/actions/actions.ts +1 -0
  96. package/src/generators/uischema.ts +4 -4
  97. package/src/i18n/i18nUtil.ts +10 -7
  98. package/src/models/uischema.ts +14 -1
  99. package/src/reducers/core.ts +30 -3
  100. package/src/reducers/reducers.ts +12 -4
  101. package/src/testers/testers.ts +61 -34
  102. package/src/util/combinators.ts +17 -32
  103. package/src/util/label.ts +2 -2
  104. package/src/util/path.ts +18 -6
  105. package/src/util/renderer.ts +20 -32
  106. package/src/util/resolvers.ts +57 -68
  107. package/src/util/util.ts +1 -1
  108. package/stats.html +1 -1
  109. package/test/generators/uischema.test.ts +18 -0
  110. package/test/i18n/i18nUtil.test.ts +41 -1
  111. package/test/reducers/core.test.ts +203 -1
  112. package/test/testers.test.ts +208 -120
  113. package/test/util/path.test.ts +37 -20
  114. package/test/util/renderer.test.ts +1 -1
  115. package/test/util/resolvers.test.ts +99 -8
@@ -47,15 +47,17 @@ import { deriveTypes, hasType, resolveSchema } from '../util';
47
47
  export const NOT_APPLICABLE = -1;
48
48
  /**
49
49
  * A tester is a function that receives an UI schema and a JSON schema and returns a boolean.
50
+ * The rootSchema is handed over as context. Can be used to resolve references.
50
51
  */
51
- export type Tester = (uischema: UISchemaElement, schema: JsonSchema) => boolean;
52
+ export type Tester = (uischema: UISchemaElement, schema: JsonSchema, rootSchema: JsonSchema) => boolean;
52
53
 
53
54
  /**
54
55
  * A ranked tester associates a tester with a number.
55
56
  */
56
57
  export type RankedTester = (
57
58
  uischema: UISchemaElement,
58
- schema: JsonSchema
59
+ schema: JsonSchema,
60
+ rootSchema: JsonSchema
59
61
  ) => number;
60
62
 
61
63
  export const isControl = (uischema: any): uischema is ControlElement =>
@@ -72,8 +74,8 @@ export const isControl = (uischema: any): uischema is ControlElement =>
72
74
  * applied to the resolved sub-schema
73
75
  */
74
76
  export const schemaMatches = (
75
- predicate: (schema: JsonSchema) => boolean
76
- ): Tester => (uischema: UISchemaElement, schema: JsonSchema): boolean => {
77
+ predicate: (schema: JsonSchema, rootSchema: JsonSchema) => boolean
78
+ ): Tester => (uischema: UISchemaElement, schema: JsonSchema, rootSchema: JsonSchema): boolean => {
77
79
  if (isEmpty(uischema) || !isControl(uischema)) {
78
80
  return false;
79
81
  }
@@ -86,26 +88,26 @@ export const schemaMatches = (
86
88
  }
87
89
  let currentDataSchema = schema;
88
90
  if (hasType(schema, 'object')) {
89
- currentDataSchema = resolveSchema(schema, schemaPath);
91
+ currentDataSchema = resolveSchema(schema, schemaPath, rootSchema);
90
92
  }
91
93
  if (currentDataSchema === undefined) {
92
94
  return false;
93
95
  }
94
96
 
95
- return predicate(currentDataSchema);
97
+ return predicate(currentDataSchema, rootSchema);
96
98
  };
97
99
 
98
100
  export const schemaSubPathMatches = (
99
101
  subPath: string,
100
- predicate: (schema: JsonSchema) => boolean
101
- ): Tester => (uischema: UISchemaElement, schema: JsonSchema): boolean => {
102
+ predicate: (schema: JsonSchema, rootSchema: JsonSchema) => boolean
103
+ ): Tester => (uischema: UISchemaElement, schema: JsonSchema, rootSchema: JsonSchema): boolean => {
102
104
  if (isEmpty(uischema) || !isControl(uischema)) {
103
105
  return false;
104
106
  }
105
107
  const schemaPath = uischema.scope;
106
108
  let currentDataSchema: JsonSchema = schema;
107
109
  if (hasType(schema, 'object')) {
108
- currentDataSchema = resolveSchema(schema, schemaPath);
110
+ currentDataSchema = resolveSchema(schema, schemaPath, rootSchema);
109
111
  }
110
112
  currentDataSchema = get(currentDataSchema, subPath);
111
113
 
@@ -113,7 +115,7 @@ export const schemaSubPathMatches = (
113
115
  return false;
114
116
  }
115
117
 
116
- return predicate(currentDataSchema);
118
+ return predicate(currentDataSchema, rootSchema);
117
119
  };
118
120
 
119
121
  /**
@@ -142,7 +144,7 @@ export const formatIs = (expectedFormat: string): Tester =>
142
144
  schema =>
143
145
  !isEmpty(schema) &&
144
146
  schema.format === expectedFormat &&
145
- schema.type === 'string'
147
+ hasType(schema, 'string')
146
148
  );
147
149
 
148
150
  /**
@@ -215,8 +217,9 @@ export const scopeEndIs = (expected: string): Tester => (
215
217
  */
216
218
  export const and = (...testers: Tester[]): Tester => (
217
219
  uischema: UISchemaElement,
218
- schema: JsonSchema
219
- ) => testers.reduce((acc, tester) => acc && tester(uischema, schema), true);
220
+ schema: JsonSchema,
221
+ rootSchema: JsonSchema
222
+ ) => testers.reduce((acc, tester) => acc && tester(uischema, schema, rootSchema), true);
220
223
 
221
224
  /**
222
225
  * A tester that allow composing other testers by || them.
@@ -225,8 +228,9 @@ export const and = (...testers: Tester[]): Tester => (
225
228
  */
226
229
  export const or = (...testers: Tester[]): Tester => (
227
230
  uischema: UISchemaElement,
228
- schema: JsonSchema
229
- ) => testers.reduce((acc, tester) => acc || tester(uischema, schema), false);
231
+ schema: JsonSchema,
232
+ rootSchema: JsonSchema
233
+ ) => testers.reduce((acc, tester) => acc || tester(uischema, schema, rootSchema), false);
230
234
  /**
231
235
  * Create a ranked tester that will associate a number with a given tester, if the
232
236
  * latter returns true.
@@ -236,9 +240,10 @@ export const or = (...testers: Tester[]): Tester => (
236
240
  */
237
241
  export const rankWith = (rank: number, tester: Tester) => (
238
242
  uischema: UISchemaElement,
239
- schema: JsonSchema
243
+ schema: JsonSchema,
244
+ rootSchema: JsonSchema
240
245
  ): number => {
241
- if (tester(uischema, schema)) {
246
+ if (tester(uischema, schema, rootSchema)) {
242
247
  return rank;
243
248
  }
244
249
 
@@ -247,9 +252,10 @@ export const rankWith = (rank: number, tester: Tester) => (
247
252
 
248
253
  export const withIncreasedRank = (by: number, rankedTester: RankedTester) => (
249
254
  uischema: UISchemaElement,
250
- schema: JsonSchema
255
+ schema: JsonSchema,
256
+ rootSchema: JsonSchema
251
257
  ): number => {
252
- const rank = rankedTester(uischema, schema);
258
+ const rank = rankedTester(uischema, schema, rootSchema);
253
259
  if (rank === NOT_APPLICABLE) {
254
260
  return NOT_APPLICABLE;
255
261
  }
@@ -380,9 +386,12 @@ export const isDateTimeControl = and(
380
386
  */
381
387
  export const isObjectArray = and(
382
388
  schemaMatches(
383
- schema => hasType(schema, 'array') && !Array.isArray(schema.items) // we don't care about tuples
389
+ (schema, rootSchema) => hasType(schema, 'array') && !Array.isArray(resolveSchema(schema, 'items', rootSchema)) // we don't care about tuples
384
390
  ),
385
- schemaSubPathMatches('items', schema => hasType(schema, 'object'))
391
+ schemaSubPathMatches('items', (schema, rootSchema) => {
392
+ const resolvedSchema = schema.$ref ? resolveSchema(rootSchema, schema.$ref, rootSchema) : schema;
393
+ return hasType(resolvedSchema, 'object')
394
+ })
386
395
  );
387
396
 
388
397
  /**
@@ -394,22 +403,31 @@ export const isObjectArrayControl = and(uiTypeIs('Control'), isObjectArray);
394
403
 
395
404
  const traverse = (
396
405
  any: JsonSchema | JsonSchema[],
397
- pred: (obj: JsonSchema) => boolean
406
+ pred: (obj: JsonSchema) => boolean,
407
+ rootSchema: JsonSchema
398
408
  ): boolean => {
399
409
  if (isArray(any)) {
400
- return reduce(any, (acc, el) => acc || traverse(el, pred), false);
410
+ return reduce(any, (acc, el) => acc || traverse(el, pred, rootSchema), false);
401
411
  }
402
412
 
403
413
  if (pred(any)) {
404
414
  return true;
405
415
  }
416
+
417
+ if (any.$ref) {
418
+ const toTraverse = resolveSchema(rootSchema, any.$ref, rootSchema);
419
+ if (toTraverse && !toTraverse.$ref) {
420
+ return traverse(toTraverse, pred, rootSchema);
421
+ }
422
+ }
423
+
406
424
  if (any.items) {
407
- return traverse(any.items, pred);
425
+ return traverse(any.items, pred, rootSchema);
408
426
  }
409
427
  if (any.properties) {
410
428
  return reduce(
411
429
  toPairs(any.properties),
412
- (acc, [_key, val]) => acc || traverse(val, pred),
430
+ (acc, [_key, val]) => acc || traverse(val, pred, rootSchema),
413
431
  false
414
432
  );
415
433
  }
@@ -419,13 +437,14 @@ const traverse = (
419
437
 
420
438
  export const isObjectArrayWithNesting = (
421
439
  uischema: UISchemaElement,
422
- schema: JsonSchema
440
+ schema: JsonSchema,
441
+ rootSchema: JsonSchema
423
442
  ): boolean => {
424
- if (!uiTypeIs('Control')(uischema, schema)) {
443
+ if (!uiTypeIs('Control')(uischema, schema, rootSchema)) {
425
444
  return false;
426
445
  }
427
446
  const schemaPath = (uischema as ControlElement).scope;
428
- const resolvedSchema = resolveSchema(schema, schemaPath);
447
+ const resolvedSchema = resolveSchema(schema, schemaPath, rootSchema ?? schema);
429
448
  const wantedNestingByType: { [key: string]: number } = {
430
449
  object: 2,
431
450
  array: 1
@@ -437,6 +456,9 @@ export const isObjectArrayWithNesting = (
437
456
  if (val === schema) {
438
457
  return false;
439
458
  }
459
+ if (val.$ref !== undefined) {
460
+ return false;
461
+ }
440
462
  // we don't support multiple types
441
463
  if (typeof val.type !== 'string') {
442
464
  return true;
@@ -447,7 +469,7 @@ export const isObjectArrayWithNesting = (
447
469
  }
448
470
  wantedNestingByType[val.type] = typeCount - 1;
449
471
  return wantedNestingByType[val.type] === 0;
450
- })
472
+ }, rootSchema)
451
473
  ) {
452
474
  return true;
453
475
  }
@@ -479,10 +501,13 @@ export const isArrayObjectControl = isObjectArrayControl;
479
501
  export const isPrimitiveArrayControl = and(
480
502
  uiTypeIs('Control'),
481
503
  schemaMatches(
482
- schema => deriveTypes(schema).length !== 0 && !Array.isArray(schema.items) // we don't care about tuples
504
+ (schema, rootSchema) =>
505
+ deriveTypes(schema).length !== 0 &&
506
+ !Array.isArray(resolveSchema(schema, 'items', rootSchema)) // we don't care about tuples
483
507
  ),
484
- schemaSubPathMatches('items', schema => {
485
- const types = deriveTypes(schema);
508
+ schemaSubPathMatches('items', (schema, rootSchema) => {
509
+ const resolvedSchema = schema.$ref ? resolveSchema(rootSchema, schema.$ref, rootSchema) : schema;
510
+ const types = deriveTypes(resolvedSchema);
486
511
  return (
487
512
  types.length === 1 &&
488
513
  includes(['integer', 'number', 'boolean', 'string'], types[0])
@@ -543,5 +568,7 @@ export const categorizationHasCategory = (uischema: UISchemaElement) =>
543
568
 
544
569
  export const not = (tester: Tester): Tester => (
545
570
  uischema: UISchemaElement,
546
- schema: JsonSchema
547
- ) => !tester(uischema, schema);
571
+ schema: JsonSchema,
572
+ rootSchema: JsonSchema
573
+
574
+ ) => !tester(uischema, schema, rootSchema);
@@ -24,8 +24,8 @@
24
24
  */
25
25
 
26
26
  import { ControlElement, JsonSchema, UISchemaElement } from '../models';
27
- import { resolveSchema } from './resolvers';
28
27
  import { findUISchema, JsonFormsUISchemaRegistryEntry } from '../reducers';
28
+ import { Resolve } from './util';
29
29
 
30
30
  export interface CombinatorSubSchemaRenderInfo {
31
31
  schema: JsonSchema;
@@ -47,24 +47,6 @@ const createLabel = (
47
47
  }
48
48
  };
49
49
 
50
- export const resolveSubSchemas = (
51
- schema: JsonSchema,
52
- rootSchema: JsonSchema,
53
- keyword: CombinatorKeyword
54
- ) => {
55
- // resolve any $refs, otherwise the generated UI schema can't match the schema???
56
- const schemas = schema[keyword] as any[];
57
- if (schemas.findIndex(e => e.$ref !== undefined) !== -1) {
58
- return {
59
- ...schema,
60
- [keyword]: (schema[keyword] as any[]).map(e =>
61
- e.$ref ? resolveSchema(rootSchema, e.$ref) : e
62
- )
63
- };
64
- }
65
- return schema;
66
- };
67
-
68
50
  export const createCombinatorRenderInfos = (
69
51
  combinatorSubSchemas: JsonSchema[],
70
52
  rootSchema: JsonSchema,
@@ -73,16 +55,19 @@ export const createCombinatorRenderInfos = (
73
55
  path: string,
74
56
  uischemas: JsonFormsUISchemaRegistryEntry[]
75
57
  ): CombinatorSubSchemaRenderInfo[] =>
76
- combinatorSubSchemas.map((subSchema, subSchemaIndex) => ({
77
- schema: subSchema,
78
- uischema: findUISchema(
79
- uischemas,
80
- subSchema,
81
- control.scope,
82
- path,
83
- undefined,
84
- control,
85
- rootSchema
86
- ),
87
- label: createLabel(subSchema, subSchemaIndex, keyword)
88
- }));
58
+ combinatorSubSchemas.map((subSchema, subSchemaIndex) => {
59
+ const schema = subSchema.$ref ? Resolve.schema(rootSchema, subSchema.$ref, rootSchema) : subSchema;
60
+ return {
61
+ schema,
62
+ uischema: findUISchema(
63
+ uischemas,
64
+ schema,
65
+ control.scope,
66
+ path,
67
+ undefined,
68
+ control,
69
+ rootSchema
70
+ ),
71
+ label: createLabel(subSchema, subSchemaIndex, keyword)
72
+ }
73
+ });
package/src/util/label.ts CHANGED
@@ -26,6 +26,7 @@
26
26
  import startCase from 'lodash/startCase';
27
27
 
28
28
  import { ControlElement, JsonSchema, LabelDescription } from '../models';
29
+ import { decode } from './path';
29
30
 
30
31
  const deriveLabel = (
31
32
  controlElement: ControlElement,
@@ -36,8 +37,7 @@ const deriveLabel = (
36
37
  }
37
38
  if (typeof controlElement.scope === 'string') {
38
39
  const ref = controlElement.scope;
39
- const label = ref.substr(ref.lastIndexOf('/') + 1);
40
-
40
+ const label = decode(ref.substr(ref.lastIndexOf('/') + 1));
41
41
  return startCase(label);
42
42
  }
43
43
 
package/src/util/path.ts CHANGED
@@ -57,14 +57,15 @@ export { compose as composePaths };
57
57
  */
58
58
  export const toDataPathSegments = (schemaPath: string): string[] => {
59
59
  const s = schemaPath
60
- .replace(/anyOf\/[\d]\//g, '')
61
- .replace(/allOf\/[\d]\//g, '')
62
- .replace(/oneOf\/[\d]\//g, '');
60
+ .replace(/(anyOf|allOf|oneOf)\/[\d]\//g, '')
61
+ .replace(/(then|else)\//g, '');
63
62
  const segments = s.split('/');
64
63
 
65
- const startFromRoot = segments[0] === '#' || segments[0] === '';
64
+ const decodedSegments = segments.map(decode);
65
+
66
+ const startFromRoot = decodedSegments[0] === '#' || decodedSegments[0] === '';
66
67
  const startIndex = startFromRoot ? 2 : 1;
67
- return range(startIndex, segments.length, 2).map(idx => segments[idx]);
68
+ return range(startIndex, decodedSegments.length, 2).map(idx => decodedSegments[idx]);
68
69
  };
69
70
 
70
71
  /**
@@ -77,7 +78,7 @@ export const toDataPathSegments = (schemaPath: string): string[] => {
77
78
  */
78
79
  export const toDataPath = (schemaPath: string): string => {
79
80
  return toDataPathSegments(schemaPath).join('.');
80
- };
81
+ };
81
82
 
82
83
  export const composeWithUi = (scopableUi: Scopable, path: string): string => {
83
84
  const segments = toDataPathSegments(scopableUi.scope);
@@ -88,3 +89,14 @@ export const composeWithUi = (scopableUi: Scopable, path: string): string => {
88
89
 
89
90
  return isEmpty(segments) ? path : compose(path, segments.join('.'));
90
91
  };
92
+
93
+ /**
94
+ * Encodes the given segment to be used as part of a JSON Pointer
95
+ *
96
+ * JSON Pointer has special meaning for "/" and "~", therefore these must be encoded
97
+ */
98
+ export const encode = (segment: string) => segment?.replace(/~/g, '~0').replace(/\//g, '~1');
99
+ /**
100
+ * Decodes a given JSON Pointer segment to its "normal" representation
101
+ */
102
+ export const decode = (pointerSegment: string) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~');
@@ -26,7 +26,8 @@
26
26
  import get from 'lodash/get';
27
27
  import { ControlElement, JsonSchema, UISchemaElement } from '../models';
28
28
  import find from 'lodash/find';
29
- import type {
29
+ import {
30
+ getUISchemas,
30
31
  JsonFormsCellRendererRegistryEntry,
31
32
  JsonFormsRendererRegistryEntry,
32
33
  } from '../reducers';
@@ -48,7 +49,7 @@ import {
48
49
  import { RankedTester } from '../testers';
49
50
  import { hasShowRule, isInherentlyEnabled, isVisible } from './runtime';
50
51
  import { createLabelDescriptionFrom } from './label';
51
- import { CombinatorKeyword, resolveSubSchemas } from './combinators';
52
+ import { CombinatorKeyword } from './combinators';
52
53
  import { moveDown, moveUp } from './array';
53
54
  import { AnyAction, Dispatch } from './type';
54
55
  import { Resolve } from './util';
@@ -65,6 +66,7 @@ const isRequired = (
65
66
  ): boolean => {
66
67
  const pathSegments = schemaPath.split('/');
67
68
  const lastSegment = pathSegments[pathSegments.length - 1];
69
+ // Skip "properties", "items" etc. to resolve the parent
68
70
  const nextHigherSchemaSegments = pathSegments.slice(
69
71
  0,
70
72
  pathSegments.length - 2
@@ -463,8 +465,8 @@ export const mapStateToControlProps = (
463
465
  const schema = resolvedSchema ?? rootSchema;
464
466
  const t = getTranslator()(state);
465
467
  const te = getErrorTranslator()(state);
466
- const i18nLabel = t(getI18nKey(schema, uischema, path, 'label'), label);
467
- const i18nDescription = t(getI18nKey(schema, uischema, path, 'description'), description);
468
+ const i18nLabel = t(getI18nKey(schema, uischema, path, 'label'), label, {schema, uischema, path, errors} );
469
+ const i18nDescription = t(getI18nKey(schema, uischema, path, 'description'), description, {schema, uischema, path, errors});
468
470
  const i18nErrorMessage = getCombinedErrorMessage(errors, te, t, schema, uischema, path);
469
471
 
470
472
  return {
@@ -865,7 +867,8 @@ export const mapStateToLayoutProps = (
865
867
  data,
866
868
  uischema: ownProps.uischema,
867
869
  schema: ownProps.schema,
868
- direction: ownProps.direction ?? getDirection(uischema)
870
+ direction: ownProps.direction ?? getDirection(uischema),
871
+ config
869
872
  };
870
873
  };
871
874
 
@@ -891,7 +894,10 @@ export const mapStateToJsonFormsRendererProps = (
891
894
  state.jsonforms.uischemas,
892
895
  ownProps.schema,
893
896
  undefined,
894
- ownProps.path
897
+ ownProps.path,
898
+ undefined,
899
+ undefined,
900
+ state.jsonforms.core.schema
895
901
  );
896
902
  } else {
897
903
  uischema = getUiSchema(state);
@@ -913,7 +919,7 @@ export const controlDefaultProps = {
913
919
  errors: [] as string[]
914
920
  };
915
921
 
916
- export interface StatePropsOfCombinator extends OwnPropsOfControl {
922
+ export interface StatePropsOfCombinator extends StatePropsOfControl {
917
923
  rootSchema: JsonSchema;
918
924
  path: string;
919
925
  id: string;
@@ -927,25 +933,12 @@ export const mapStateToCombinatorRendererProps = (
927
933
  ownProps: OwnPropsOfControl,
928
934
  keyword: CombinatorKeyword
929
935
  ): StatePropsOfCombinator => {
930
- const { uischema } = ownProps;
931
- const path = composeWithUi(uischema, ownProps.path);
932
- const rootSchema = getSchema(state);
933
- const resolvedSchema = Resolve.schema(
934
- ownProps.schema || rootSchema,
935
- uischema.scope,
936
- rootSchema
936
+ const { data, schema, ...props } = mapStateToControlProps(
937
+ state,
938
+ ownProps
937
939
  );
938
- const visible: boolean =
939
- ownProps.visible === undefined || hasShowRule(uischema)
940
- ? isVisible(uischema, getData(state), ownProps.path, getAjv(state))
941
- : ownProps.visible;
942
- const id = ownProps.id;
943
-
944
- const data = Resolve.data(getData(state), path);
945
940
 
946
941
  const ajv = state.jsonforms.core.ajv;
947
- const schema = resolvedSchema || rootSchema;
948
- const _schema = resolveSubSchemas(schema, rootSchema, keyword);
949
942
  const structuralKeywords = [
950
943
  'required',
951
944
  'additionalProperties',
@@ -964,9 +957,9 @@ export const mapStateToCombinatorRendererProps = (
964
957
  // TODO instead of compiling the combinator subschemas we can compile the original schema
965
958
  // without the combinator alternatives and then revalidate and check the errors for the
966
959
  // element
967
- for (let i = 0; i < _schema[keyword].length; i++) {
960
+ for (let i = 0; i < schema[keyword]?.length; i++) {
968
961
  try {
969
- const valFn = ajv.compile(_schema[keyword][i]);
962
+ const valFn = ajv.compile(schema[keyword][i]);
970
963
  valFn(data);
971
964
  if (dataIsValid(valFn.errors)) {
972
965
  indexOfFittingSchema = i;
@@ -979,14 +972,10 @@ export const mapStateToCombinatorRendererProps = (
979
972
 
980
973
  return {
981
974
  data,
982
- path,
983
975
  schema,
984
- rootSchema,
985
- visible,
986
- id,
976
+ ...props,
987
977
  indexOfFittingSchema,
988
- uischemas: state.jsonforms.uischemas,
989
- uischema
978
+ uischemas: getUISchemas(state)
990
979
  };
991
980
  };
992
981
 
@@ -1044,7 +1033,6 @@ export const mapStateToArrayLayoutProps = (
1044
1033
 
1045
1034
  const resolvedSchema = Resolve.schema(schema, 'items', props.rootSchema);
1046
1035
 
1047
- // TODO Does not consider a specialized '.custom' error message overriding all other error messages
1048
1036
  // TODO Does not consider 'i18n' keys which are specified in the ui schemas of the sub errors
1049
1037
  const childErrors = getCombinedErrorMessage(
1050
1038
  getSubErrorsAt(path, resolvedSchema)(state),
@@ -25,7 +25,8 @@
25
25
 
26
26
  import isEmpty from 'lodash/isEmpty';
27
27
  import get from 'lodash/get';
28
- import { JsonSchema } from '../models';
28
+ import { JsonSchema, JsonSchema7 } from '../models';
29
+ import { decode } from './path';
29
30
 
30
31
  /**
31
32
  * Map for storing refs and the respective schemas they are pointing to.
@@ -110,80 +111,68 @@ const invalidSegment = (pathSegment: string) =>
110
111
  export const resolveSchema = (
111
112
  schema: JsonSchema,
112
113
  schemaPath: string,
113
- rootSchema?: JsonSchema
114
+ rootSchema: JsonSchema
115
+ ): JsonSchema => {
116
+ const segments = schemaPath?.split('/').map(decode);
117
+ return resolveSchemaWithSegments(schema, segments, rootSchema);
118
+ };
119
+
120
+ const resolveSchemaWithSegments = (
121
+ schema: JsonSchema,
122
+ pathSegments: string[],
123
+ rootSchema: JsonSchema
114
124
  ): JsonSchema => {
115
125
  if (isEmpty(schema)) {
116
126
  return undefined;
117
127
  }
118
- const validPathSegments = schemaPath.split('/');
119
- let resultSchema = schema;
120
- for (let i = 0; i < validPathSegments.length; i++) {
121
- let pathSegment = validPathSegments[i];
122
- resultSchema =
123
- resultSchema === undefined || resultSchema.$ref === undefined
124
- ? resultSchema
125
- : resolveSchema(schema, resultSchema.$ref);
126
- if (invalidSegment(pathSegment)) {
127
- // skip invalid segments
128
- continue;
129
- }
130
- let curSchema = get(resultSchema, pathSegment);
131
- if (!curSchema) {
132
- // resolving was not successful, check whether the scope omitted an oneOf, allOf or anyOf and resolve anyway
133
- const schemas = [].concat(
134
- resultSchema?.oneOf ?? [],
135
- resultSchema?.allOf ?? [],
136
- resultSchema?.anyOf ?? []
137
- );
138
- for (let item of schemas) {
139
- curSchema = resolveSchema(item, validPathSegments.slice(i).join('/'));
140
- if (curSchema) {
141
- break;
142
- }
143
- }
144
- if (curSchema) {
145
- // already resolved rest of the path
146
- resultSchema = curSchema;
147
- break;
148
- }
149
- }
150
- resultSchema = curSchema;
128
+
129
+ if (schema.$ref) {
130
+ schema = resolveSchema(rootSchema, schema.$ref, rootSchema);
151
131
  }
152
- // TODO: because schema is already scoped we might end up with refs pointing
153
- // outside of the current schema. It would be better if we'd always could deal
154
- // with absolute paths here, so that we don't need to keep two different
155
- // schemas around
156
- if (resultSchema !== undefined && resultSchema.$ref !== undefined) {
157
- try {
158
- return retrieveResolvableSchema(schema, resultSchema.$ref);
159
- } catch (e) {
160
- return retrieveResolvableSchema(rootSchema, resultSchema.$ref);
161
- }
132
+
133
+ if (!pathSegments || pathSegments.length === 0) {
134
+ return schema;
162
135
  }
163
136
 
164
- return resultSchema;
165
- };
137
+ const [segment, ...remainingSegments] = pathSegments;
166
138
 
167
- /**
168
- * Normalizes the schema and resolves the given ref.
169
- *
170
- * @param {JsonSchema} full the JSON schema to resolved the reference against
171
- * @param {string} reference the reference to be resolved
172
- * @returns {JsonSchema} the resolved sub-schema
173
- */
174
- // disable rule because resolve is mutually recursive
175
- // tslint:disable:only-arrow-functions
176
- function retrieveResolvableSchema(
177
- full: JsonSchema,
178
- reference: string
179
- ): JsonSchema {
180
- // tslint:enable:only-arrow-functions
181
- const child = resolveSchema(full, reference);
182
- const allRefs = findAllRefs(child);
183
- const innerSelfReference = allRefs[reference];
184
- if (innerSelfReference !== undefined) {
185
- innerSelfReference.$ref = '#';
139
+ if (invalidSegment(segment)) {
140
+ return resolveSchemaWithSegments(schema, remainingSegments, rootSchema);
186
141
  }
187
142
 
188
- return child;
189
- }
143
+ const singleSegmentResolveSchema = get(schema, segment);
144
+
145
+ const resolvedSchema = resolveSchemaWithSegments(singleSegmentResolveSchema, remainingSegments, rootSchema);
146
+ if (resolvedSchema) {
147
+ return resolvedSchema;
148
+ }
149
+
150
+ if (segment === 'properties' || segment === 'items') {
151
+ // Let's try to resolve the path, assuming oneOf/allOf/anyOf/then/else was omitted.
152
+ // We only do this when traversing an object or array as we want to avoid
153
+ // following a property which is named oneOf, allOf, anyOf, then or else.
154
+ let alternativeResolveResult = undefined;
155
+
156
+ const subSchemas = [].concat(
157
+ schema.oneOf ?? [],
158
+ schema.allOf ?? [],
159
+ schema.anyOf ?? [],
160
+ (schema as JsonSchema7).then ?? [],
161
+ (schema as JsonSchema7).else ?? []
162
+ );
163
+
164
+ for (const subSchema of subSchemas) {
165
+ alternativeResolveResult = resolveSchemaWithSegments(
166
+ subSchema,
167
+ [segment, ...remainingSegments],
168
+ rootSchema
169
+ );
170
+ if (alternativeResolveResult) {
171
+ break;
172
+ }
173
+ }
174
+ return alternativeResolveResult;
175
+ }
176
+
177
+ return undefined;
178
+ }
package/src/util/util.ts CHANGED
@@ -99,7 +99,7 @@ export const Resolve: {
99
99
  schema(
100
100
  schema: JsonSchema,
101
101
  schemaPath: string,
102
- rootSchema?: JsonSchema
102
+ rootSchema: JsonSchema
103
103
  ): JsonSchema;
104
104
  data(data: any, path: string): any;
105
105
  } = {