@remoteoss/json-schema-form 0.11.14-dev.20250514100552 â 0.12.0-beta.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/CHANGELOG.md +18 -0
- package/README.md +12 -4
- package/dist/index.cjs +96 -85
- package/dist/index.js +96 -85
- package/dist/standalone.js +96 -85
- package/package.json +1 -1
- package/src/tests/conditions.test.js +0 -68
- package/src/tests/createHeadlessForm.test.js +38 -0
- package/src/tests/helpers.custom.js +1 -1
- package/src/tests/helpers.js +11 -12
- package/src/tests/jsonLogic.fixtures.js +49 -0
- package/src/tests/jsonLogic.test.js +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
#### 0.12.0-beta.0 (2025-06-25)
|
|
2
|
+
|
|
3
|
+
##### New Features
|
|
4
|
+
|
|
5
|
+
* allow select fields to be made from anyOf ([#197](https://github.com/remoteoss/json-schema-form/pull/197)) ([d5da6ae8](https://github.com/remoteoss/json-schema-form/commit/d5da6ae8816a28c8b0011fc699bf351078d6f437))
|
|
6
|
+
|
|
7
|
+
#### 0.11.15-beta.0 (2025-06-06)
|
|
8
|
+
|
|
9
|
+
##### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **v0:** Handle computed presentational attributes correctly ([#195](https://github.com/remoteoss/json-schema-form/pull/195)) ([19c7ff5b](https://github.com/remoteoss/json-schema-form/commit/19c7ff5b4c5cc90bd1025a3ad3032076e4e25b65))
|
|
12
|
+
|
|
13
|
+
#### 0.11.14-beta.0 (2025-05-14)
|
|
14
|
+
|
|
15
|
+
##### Chores
|
|
16
|
+
|
|
17
|
+
* Reminder to publish GitHub release ([#183](https://github.com/remoteoss/json-schema-form/pull/183)) ([adfa8c14](https://github.com/remoteoss/json-schema-form/commit/adfa8c14ca118b1012642f39c8388cf2ba1614c9))
|
|
18
|
+
|
|
1
19
|
#### 0.11.13-beta.0 (2025-05-05)
|
|
2
20
|
|
|
3
21
|
##### Bug Fixes
|
package/README.md
CHANGED
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<code>json-schema-form</code> is a headless UI form library powered by <a href="https://json-schema.org/">JSON Schemas</a>.
|
|
7
7
|
<br/>
|
|
8
|
-
It transforms JSON schemas into Javascript to be consumed by your UI libraries.
|
|
8
|
+
It transforms JSON schemas into Javascript `fields` to be more easily consumed by your UI libraries.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
### Why JSON Schemas for forms?
|
|
14
14
|
|
|
15
|
-
JSON Schemas are the SSoT (Single Source of Truth) that allows you to share form's _structure_ and _validations_ between
|
|
15
|
+
JSON Schemas are the SSoT (Single Source of Truth) that allows you to share form's data _structure_ and _validations_ between the server (backend) and the client (frontend), regardless of the language used.
|
|
16
|
+
|
|
17
|
+
You can use it beyond UI Forms, like lists, tables, and any other UI that needs structured JSON data.
|
|
16
18
|
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
@@ -25,12 +27,18 @@ npm install @remoteoss/json-schema-form
|
|
|
25
27
|
yarn install @remoteoss/json-schema-form
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
##
|
|
30
|
+
## Getting Started
|
|
31
|
+
|
|
32
|
+
Check the đ **[JSF website](https://json-schema-form.vercel.app/)** for documentation.
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
### Playground
|
|
35
|
+
|
|
36
|
+
Check the đšī¸ **[JSF Playground](https://json-schema-form.vercel.app/?path=/docs/playground--docs)** for demos.
|
|
31
37
|
|
|
32
38
|
## Contributing
|
|
33
39
|
|
|
34
40
|
Read [CONTRIBUTING](CONTRIBUTING.md) to get started.
|
|
35
41
|
|
|
42
|
+
We are working on the [next version v1.0](/next). A rewrite in TypeScript with major bugfixes and missing features. It aims to support the latest JSON Schema dialect 2020-12.
|
|
43
|
+
|
|
36
44
|
_Backed by [Remote.com](https://remote.com/)_
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
/*!
|
|
3
3
|
Copyright (c) 2025 Remote Technology, Inc.
|
|
4
|
-
NPM Package: @remoteoss/json-schema-form@0.
|
|
5
|
-
Generated: Wed,
|
|
4
|
+
NPM Package: @remoteoss/json-schema-form@0.12.0-beta.0
|
|
5
|
+
Generated: Wed, 25 Jun 2025 12:45:39 GMT
|
|
6
6
|
|
|
7
7
|
MIT License
|
|
8
8
|
|
|
@@ -97,6 +97,75 @@ function hasProperty(object2, propertyName) {
|
|
|
97
97
|
return Object.prototype.hasOwnProperty.call(object2, propertyName);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// src/internals/checkIfConditionMatches.js
|
|
101
|
+
function checkIfConditionMatchesProperties(node, formValues, formFields, logic) {
|
|
102
|
+
if (typeof node.if === "boolean") {
|
|
103
|
+
return node.if;
|
|
104
|
+
}
|
|
105
|
+
if (node.if.anyOf) {
|
|
106
|
+
return node.if.anyOf.some(
|
|
107
|
+
(property) => checkIfConditionMatchesProperties({ if: property }, formValues, formFields, logic)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return Object.keys(node.if.properties ?? {}).every((name) => {
|
|
111
|
+
const currentProperty = node.if.properties[name];
|
|
112
|
+
const value = formValues[name];
|
|
113
|
+
const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
|
|
114
|
+
value === null;
|
|
115
|
+
const hasIfExplicit = node.if.required?.includes(name);
|
|
116
|
+
if (hasEmptyValue && !hasIfExplicit) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (hasProperty(currentProperty, "const")) {
|
|
120
|
+
return compareFormValueWithSchemaValue(value, currentProperty.const);
|
|
121
|
+
}
|
|
122
|
+
if (currentProperty.contains?.pattern) {
|
|
123
|
+
const formValue = value || [];
|
|
124
|
+
if (Array.isArray(formValue)) {
|
|
125
|
+
const pattern = new RegExp(currentProperty.contains.pattern);
|
|
126
|
+
return (value || []).some((item) => pattern.test(item));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (currentProperty.enum) {
|
|
130
|
+
return currentProperty.enum.includes(value);
|
|
131
|
+
}
|
|
132
|
+
if (currentProperty.properties) {
|
|
133
|
+
return checkIfConditionMatchesProperties(
|
|
134
|
+
{ if: currentProperty },
|
|
135
|
+
formValues[name],
|
|
136
|
+
getField(name, formFields).fields,
|
|
137
|
+
logic
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const field = getField(name, formFields);
|
|
141
|
+
return validateFieldSchema(
|
|
142
|
+
{
|
|
143
|
+
...field,
|
|
144
|
+
...currentProperty,
|
|
145
|
+
required: true
|
|
146
|
+
},
|
|
147
|
+
value
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function checkIfMatchesValidationsAndComputedValues(node, formValues, logic, parentID) {
|
|
152
|
+
const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property]) => {
|
|
153
|
+
const currentValue = logic.getScope(parentID).applyValidationRuleInCondition(name, formValues);
|
|
154
|
+
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
155
|
+
return true;
|
|
156
|
+
return false;
|
|
157
|
+
});
|
|
158
|
+
const computedValuesMatch = Object.entries(node.if.computedValues ?? {}).every(
|
|
159
|
+
([name, property]) => {
|
|
160
|
+
const currentValue = logic.getScope(parentID).applyComputedValueRuleInCondition(name, formValues);
|
|
161
|
+
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
162
|
+
return true;
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
return computedValuesMatch && validationsMatch;
|
|
167
|
+
}
|
|
168
|
+
|
|
100
169
|
// src/internals/helpers.js
|
|
101
170
|
var import_merge = __toESM(require("lodash/fp/merge"));
|
|
102
171
|
var import_get = __toESM(require("lodash/get"));
|
|
@@ -356,76 +425,6 @@ function _composeFieldCustomClosure(defaultComposeFn) {
|
|
|
356
425
|
};
|
|
357
426
|
}
|
|
358
427
|
|
|
359
|
-
// src/internals/checkIfConditionMatches.js
|
|
360
|
-
function checkIfConditionMatchesProperties(node, formValues, formFields, logic) {
|
|
361
|
-
if (typeof node.if === "boolean") {
|
|
362
|
-
return node.if;
|
|
363
|
-
}
|
|
364
|
-
if (node.if.anyOf) {
|
|
365
|
-
return node.if.anyOf.some(
|
|
366
|
-
(property) => checkIfConditionMatchesProperties({ if: property }, formValues, formFields, logic)
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
return Object.keys(node.if.properties ?? {}).every((name) => {
|
|
370
|
-
const currentProperty = node.if.properties[name];
|
|
371
|
-
const field = getField(name, formFields ?? []);
|
|
372
|
-
const isFieldsetField = field?.inputType === supportedTypes.FIELDSET;
|
|
373
|
-
const value = formValues?.[name] ?? (isFieldsetField ? {} : void 0);
|
|
374
|
-
const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
|
|
375
|
-
value === null;
|
|
376
|
-
const hasIfExplicit = node.if.required?.includes(name);
|
|
377
|
-
if (hasEmptyValue && !hasIfExplicit) {
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
if (hasProperty(currentProperty, "const")) {
|
|
381
|
-
return compareFormValueWithSchemaValue(value, currentProperty.const);
|
|
382
|
-
}
|
|
383
|
-
if (currentProperty.contains?.pattern) {
|
|
384
|
-
const formValue = value || [];
|
|
385
|
-
if (Array.isArray(formValue)) {
|
|
386
|
-
const pattern = new RegExp(currentProperty.contains.pattern);
|
|
387
|
-
return (value || []).some((item) => pattern.test(item));
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if (currentProperty.enum) {
|
|
391
|
-
return currentProperty.enum.includes(value);
|
|
392
|
-
}
|
|
393
|
-
if (currentProperty.properties) {
|
|
394
|
-
return checkIfConditionMatchesProperties(
|
|
395
|
-
{ if: currentProperty },
|
|
396
|
-
formValues[name],
|
|
397
|
-
getField(name, formFields).fields,
|
|
398
|
-
logic
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
return validateFieldSchema(
|
|
402
|
-
{
|
|
403
|
-
...field,
|
|
404
|
-
...currentProperty,
|
|
405
|
-
required: true
|
|
406
|
-
},
|
|
407
|
-
value
|
|
408
|
-
);
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
function checkIfMatchesValidationsAndComputedValues(node, formValues, logic, parentID) {
|
|
412
|
-
const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property]) => {
|
|
413
|
-
const currentValue = logic.getScope(parentID).applyValidationRuleInCondition(name, formValues);
|
|
414
|
-
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
415
|
-
return true;
|
|
416
|
-
return false;
|
|
417
|
-
});
|
|
418
|
-
const computedValuesMatch = Object.entries(node.if.computedValues ?? {}).every(
|
|
419
|
-
([name, property]) => {
|
|
420
|
-
const currentValue = logic.getScope(parentID).applyComputedValueRuleInCondition(name, formValues);
|
|
421
|
-
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
422
|
-
return true;
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
);
|
|
426
|
-
return computedValuesMatch && validationsMatch;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
428
|
// src/jsonLogic.js
|
|
430
429
|
var import_json_logic_js = __toESM(require("json-logic-js"));
|
|
431
430
|
|
|
@@ -968,9 +967,16 @@ function replaceHandlebarsTemplates({
|
|
|
968
967
|
function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
|
|
969
968
|
return ({ logic, isRequired, config, formValues }) => {
|
|
970
969
|
const { name, computedAttributes } = fieldParams;
|
|
971
|
-
|
|
970
|
+
let attributes = Object.fromEntries(
|
|
972
971
|
Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
|
|
973
972
|
);
|
|
973
|
+
const { "x-jsf-presentation": presentation, ...rest } = attributes;
|
|
974
|
+
if (presentation) {
|
|
975
|
+
attributes = {
|
|
976
|
+
...rest,
|
|
977
|
+
...presentation
|
|
978
|
+
};
|
|
979
|
+
}
|
|
974
980
|
return {
|
|
975
981
|
...attributes,
|
|
976
982
|
schema: buildYupSchema(
|
|
@@ -994,16 +1000,21 @@ function handleComputedAttribute(logic, formValues, parentID, name) {
|
|
|
994
1000
|
handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
|
|
995
1001
|
];
|
|
996
1002
|
case "x-jsf-presentation": {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
handleNestedObjectForComputedValues(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1003
|
+
const values = {};
|
|
1004
|
+
Object.entries(value).forEach(([presentationKey, presentationValue]) => {
|
|
1005
|
+
if (typeof presentationValue === "object") {
|
|
1006
|
+
values[presentationKey] = handleNestedObjectForComputedValues(
|
|
1007
|
+
presentationValue,
|
|
1008
|
+
formValues,
|
|
1009
|
+
parentID,
|
|
1010
|
+
logic,
|
|
1011
|
+
name
|
|
1012
|
+
);
|
|
1013
|
+
} else {
|
|
1014
|
+
values[presentationKey] = logic.getScope(parentID).applyComputedValueInField(presentationValue, formValues, name);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
return [key, values];
|
|
1007
1018
|
}
|
|
1008
1019
|
case "const":
|
|
1009
1020
|
default: {
|
|
@@ -1445,8 +1456,8 @@ function getFieldOptions(node, presentation) {
|
|
|
1445
1456
|
if (presentation.options) {
|
|
1446
1457
|
return presentation.options;
|
|
1447
1458
|
}
|
|
1448
|
-
if (node.oneOf || presentation.inputType === "radio") {
|
|
1449
|
-
return convertToOptions(node.oneOf || []);
|
|
1459
|
+
if (node.oneOf || node.anyOf || presentation.inputType === "radio") {
|
|
1460
|
+
return convertToOptions(node.oneOf || node.anyOf || []);
|
|
1450
1461
|
}
|
|
1451
1462
|
if (node.items?.anyOf) {
|
|
1452
1463
|
return convertToOptions(node.items.anyOf);
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
/*!
|
|
3
3
|
Copyright (c) 2025 Remote Technology, Inc.
|
|
4
|
-
NPM Package: @remoteoss/json-schema-form@0.
|
|
5
|
-
Generated: Wed,
|
|
4
|
+
NPM Package: @remoteoss/json-schema-form@0.12.0-beta.0
|
|
5
|
+
Generated: Wed, 25 Jun 2025 12:45:39 GMT
|
|
6
6
|
|
|
7
7
|
MIT License
|
|
8
8
|
|
|
@@ -60,6 +60,75 @@ function hasProperty(object2, propertyName) {
|
|
|
60
60
|
return Object.prototype.hasOwnProperty.call(object2, propertyName);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// src/internals/checkIfConditionMatches.js
|
|
64
|
+
function checkIfConditionMatchesProperties(node, formValues, formFields, logic) {
|
|
65
|
+
if (typeof node.if === "boolean") {
|
|
66
|
+
return node.if;
|
|
67
|
+
}
|
|
68
|
+
if (node.if.anyOf) {
|
|
69
|
+
return node.if.anyOf.some(
|
|
70
|
+
(property) => checkIfConditionMatchesProperties({ if: property }, formValues, formFields, logic)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return Object.keys(node.if.properties ?? {}).every((name) => {
|
|
74
|
+
const currentProperty = node.if.properties[name];
|
|
75
|
+
const value = formValues[name];
|
|
76
|
+
const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
|
|
77
|
+
value === null;
|
|
78
|
+
const hasIfExplicit = node.if.required?.includes(name);
|
|
79
|
+
if (hasEmptyValue && !hasIfExplicit) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (hasProperty(currentProperty, "const")) {
|
|
83
|
+
return compareFormValueWithSchemaValue(value, currentProperty.const);
|
|
84
|
+
}
|
|
85
|
+
if (currentProperty.contains?.pattern) {
|
|
86
|
+
const formValue = value || [];
|
|
87
|
+
if (Array.isArray(formValue)) {
|
|
88
|
+
const pattern = new RegExp(currentProperty.contains.pattern);
|
|
89
|
+
return (value || []).some((item) => pattern.test(item));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (currentProperty.enum) {
|
|
93
|
+
return currentProperty.enum.includes(value);
|
|
94
|
+
}
|
|
95
|
+
if (currentProperty.properties) {
|
|
96
|
+
return checkIfConditionMatchesProperties(
|
|
97
|
+
{ if: currentProperty },
|
|
98
|
+
formValues[name],
|
|
99
|
+
getField(name, formFields).fields,
|
|
100
|
+
logic
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const field = getField(name, formFields);
|
|
104
|
+
return validateFieldSchema(
|
|
105
|
+
{
|
|
106
|
+
...field,
|
|
107
|
+
...currentProperty,
|
|
108
|
+
required: true
|
|
109
|
+
},
|
|
110
|
+
value
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function checkIfMatchesValidationsAndComputedValues(node, formValues, logic, parentID) {
|
|
115
|
+
const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property]) => {
|
|
116
|
+
const currentValue = logic.getScope(parentID).applyValidationRuleInCondition(name, formValues);
|
|
117
|
+
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
118
|
+
return true;
|
|
119
|
+
return false;
|
|
120
|
+
});
|
|
121
|
+
const computedValuesMatch = Object.entries(node.if.computedValues ?? {}).every(
|
|
122
|
+
([name, property]) => {
|
|
123
|
+
const currentValue = logic.getScope(parentID).applyComputedValueRuleInCondition(name, formValues);
|
|
124
|
+
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
125
|
+
return true;
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
return computedValuesMatch && validationsMatch;
|
|
130
|
+
}
|
|
131
|
+
|
|
63
132
|
// src/internals/helpers.js
|
|
64
133
|
import merge from "lodash/fp/merge";
|
|
65
134
|
import get from "lodash/get";
|
|
@@ -319,76 +388,6 @@ function _composeFieldCustomClosure(defaultComposeFn) {
|
|
|
319
388
|
};
|
|
320
389
|
}
|
|
321
390
|
|
|
322
|
-
// src/internals/checkIfConditionMatches.js
|
|
323
|
-
function checkIfConditionMatchesProperties(node, formValues, formFields, logic) {
|
|
324
|
-
if (typeof node.if === "boolean") {
|
|
325
|
-
return node.if;
|
|
326
|
-
}
|
|
327
|
-
if (node.if.anyOf) {
|
|
328
|
-
return node.if.anyOf.some(
|
|
329
|
-
(property) => checkIfConditionMatchesProperties({ if: property }, formValues, formFields, logic)
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
return Object.keys(node.if.properties ?? {}).every((name) => {
|
|
333
|
-
const currentProperty = node.if.properties[name];
|
|
334
|
-
const field = getField(name, formFields ?? []);
|
|
335
|
-
const isFieldsetField = field?.inputType === supportedTypes.FIELDSET;
|
|
336
|
-
const value = formValues?.[name] ?? (isFieldsetField ? {} : void 0);
|
|
337
|
-
const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
|
|
338
|
-
value === null;
|
|
339
|
-
const hasIfExplicit = node.if.required?.includes(name);
|
|
340
|
-
if (hasEmptyValue && !hasIfExplicit) {
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
if (hasProperty(currentProperty, "const")) {
|
|
344
|
-
return compareFormValueWithSchemaValue(value, currentProperty.const);
|
|
345
|
-
}
|
|
346
|
-
if (currentProperty.contains?.pattern) {
|
|
347
|
-
const formValue = value || [];
|
|
348
|
-
if (Array.isArray(formValue)) {
|
|
349
|
-
const pattern = new RegExp(currentProperty.contains.pattern);
|
|
350
|
-
return (value || []).some((item) => pattern.test(item));
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (currentProperty.enum) {
|
|
354
|
-
return currentProperty.enum.includes(value);
|
|
355
|
-
}
|
|
356
|
-
if (currentProperty.properties) {
|
|
357
|
-
return checkIfConditionMatchesProperties(
|
|
358
|
-
{ if: currentProperty },
|
|
359
|
-
formValues[name],
|
|
360
|
-
getField(name, formFields).fields,
|
|
361
|
-
logic
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
return validateFieldSchema(
|
|
365
|
-
{
|
|
366
|
-
...field,
|
|
367
|
-
...currentProperty,
|
|
368
|
-
required: true
|
|
369
|
-
},
|
|
370
|
-
value
|
|
371
|
-
);
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
function checkIfMatchesValidationsAndComputedValues(node, formValues, logic, parentID) {
|
|
375
|
-
const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property]) => {
|
|
376
|
-
const currentValue = logic.getScope(parentID).applyValidationRuleInCondition(name, formValues);
|
|
377
|
-
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
378
|
-
return true;
|
|
379
|
-
return false;
|
|
380
|
-
});
|
|
381
|
-
const computedValuesMatch = Object.entries(node.if.computedValues ?? {}).every(
|
|
382
|
-
([name, property]) => {
|
|
383
|
-
const currentValue = logic.getScope(parentID).applyComputedValueRuleInCondition(name, formValues);
|
|
384
|
-
if (Object.hasOwn(property, "const") && currentValue === property.const)
|
|
385
|
-
return true;
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
);
|
|
389
|
-
return computedValuesMatch && validationsMatch;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
391
|
// src/jsonLogic.js
|
|
393
392
|
import jsonLogic from "json-logic-js";
|
|
394
393
|
|
|
@@ -931,9 +930,16 @@ function replaceHandlebarsTemplates({
|
|
|
931
930
|
function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
|
|
932
931
|
return ({ logic, isRequired, config, formValues }) => {
|
|
933
932
|
const { name, computedAttributes } = fieldParams;
|
|
934
|
-
|
|
933
|
+
let attributes = Object.fromEntries(
|
|
935
934
|
Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
|
|
936
935
|
);
|
|
936
|
+
const { "x-jsf-presentation": presentation, ...rest } = attributes;
|
|
937
|
+
if (presentation) {
|
|
938
|
+
attributes = {
|
|
939
|
+
...rest,
|
|
940
|
+
...presentation
|
|
941
|
+
};
|
|
942
|
+
}
|
|
937
943
|
return {
|
|
938
944
|
...attributes,
|
|
939
945
|
schema: buildYupSchema(
|
|
@@ -957,16 +963,21 @@ function handleComputedAttribute(logic, formValues, parentID, name) {
|
|
|
957
963
|
handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
|
|
958
964
|
];
|
|
959
965
|
case "x-jsf-presentation": {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
handleNestedObjectForComputedValues(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
966
|
+
const values = {};
|
|
967
|
+
Object.entries(value).forEach(([presentationKey, presentationValue]) => {
|
|
968
|
+
if (typeof presentationValue === "object") {
|
|
969
|
+
values[presentationKey] = handleNestedObjectForComputedValues(
|
|
970
|
+
presentationValue,
|
|
971
|
+
formValues,
|
|
972
|
+
parentID,
|
|
973
|
+
logic,
|
|
974
|
+
name
|
|
975
|
+
);
|
|
976
|
+
} else {
|
|
977
|
+
values[presentationKey] = logic.getScope(parentID).applyComputedValueInField(presentationValue, formValues, name);
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
return [key, values];
|
|
970
981
|
}
|
|
971
982
|
case "const":
|
|
972
983
|
default: {
|
|
@@ -1408,8 +1419,8 @@ function getFieldOptions(node, presentation) {
|
|
|
1408
1419
|
if (presentation.options) {
|
|
1409
1420
|
return presentation.options;
|
|
1410
1421
|
}
|
|
1411
|
-
if (node.oneOf || presentation.inputType === "radio") {
|
|
1412
|
-
return convertToOptions(node.oneOf || []);
|
|
1422
|
+
if (node.oneOf || node.anyOf || presentation.inputType === "radio") {
|
|
1423
|
+
return convertToOptions(node.oneOf || node.anyOf || []);
|
|
1413
1424
|
}
|
|
1414
1425
|
if (node.items?.anyOf) {
|
|
1415
1426
|
return convertToOptions(node.items.anyOf);
|
package/dist/standalone.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
/*!
|
|
3
3
|
Copyright (c) 2025 Remote Technology, Inc.
|
|
4
|
-
NPM Package: @remoteoss/json-schema-form@0.
|
|
5
|
-
Generated: Wed,
|
|
4
|
+
NPM Package: @remoteoss/json-schema-form@0.12.0-beta.0
|
|
5
|
+
Generated: Wed, 25 Jun 2025 12:45:39 GMT
|
|
6
6
|
|
|
7
7
|
MIT License
|
|
8
8
|
|
|
@@ -11577,6 +11577,75 @@ function hasProperty(object2, propertyName) {
|
|
|
11577
11577
|
return Object.prototype.hasOwnProperty.call(object2, propertyName);
|
|
11578
11578
|
}
|
|
11579
11579
|
|
|
11580
|
+
// src/internals/checkIfConditionMatches.js
|
|
11581
|
+
function checkIfConditionMatchesProperties(node, formValues, formFields, logic) {
|
|
11582
|
+
if (typeof node.if === "boolean") {
|
|
11583
|
+
return node.if;
|
|
11584
|
+
}
|
|
11585
|
+
if (node.if.anyOf) {
|
|
11586
|
+
return node.if.anyOf.some(
|
|
11587
|
+
(property2) => checkIfConditionMatchesProperties({ if: property2 }, formValues, formFields, logic)
|
|
11588
|
+
);
|
|
11589
|
+
}
|
|
11590
|
+
return Object.keys(node.if.properties ?? {}).every((name) => {
|
|
11591
|
+
const currentProperty = node.if.properties[name];
|
|
11592
|
+
const value = formValues[name];
|
|
11593
|
+
const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
|
|
11594
|
+
value === null;
|
|
11595
|
+
const hasIfExplicit = node.if.required?.includes(name);
|
|
11596
|
+
if (hasEmptyValue && !hasIfExplicit) {
|
|
11597
|
+
return true;
|
|
11598
|
+
}
|
|
11599
|
+
if (hasProperty(currentProperty, "const")) {
|
|
11600
|
+
return compareFormValueWithSchemaValue(value, currentProperty.const);
|
|
11601
|
+
}
|
|
11602
|
+
if (currentProperty.contains?.pattern) {
|
|
11603
|
+
const formValue = value || [];
|
|
11604
|
+
if (Array.isArray(formValue)) {
|
|
11605
|
+
const pattern = new RegExp(currentProperty.contains.pattern);
|
|
11606
|
+
return (value || []).some((item) => pattern.test(item));
|
|
11607
|
+
}
|
|
11608
|
+
}
|
|
11609
|
+
if (currentProperty.enum) {
|
|
11610
|
+
return currentProperty.enum.includes(value);
|
|
11611
|
+
}
|
|
11612
|
+
if (currentProperty.properties) {
|
|
11613
|
+
return checkIfConditionMatchesProperties(
|
|
11614
|
+
{ if: currentProperty },
|
|
11615
|
+
formValues[name],
|
|
11616
|
+
getField(name, formFields).fields,
|
|
11617
|
+
logic
|
|
11618
|
+
);
|
|
11619
|
+
}
|
|
11620
|
+
const field = getField(name, formFields);
|
|
11621
|
+
return validateFieldSchema(
|
|
11622
|
+
{
|
|
11623
|
+
...field,
|
|
11624
|
+
...currentProperty,
|
|
11625
|
+
required: true
|
|
11626
|
+
},
|
|
11627
|
+
value
|
|
11628
|
+
);
|
|
11629
|
+
});
|
|
11630
|
+
}
|
|
11631
|
+
function checkIfMatchesValidationsAndComputedValues(node, formValues, logic, parentID) {
|
|
11632
|
+
const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property2]) => {
|
|
11633
|
+
const currentValue = logic.getScope(parentID).applyValidationRuleInCondition(name, formValues);
|
|
11634
|
+
if (Object.hasOwn(property2, "const") && currentValue === property2.const)
|
|
11635
|
+
return true;
|
|
11636
|
+
return false;
|
|
11637
|
+
});
|
|
11638
|
+
const computedValuesMatch = Object.entries(node.if.computedValues ?? {}).every(
|
|
11639
|
+
([name, property2]) => {
|
|
11640
|
+
const currentValue = logic.getScope(parentID).applyComputedValueRuleInCondition(name, formValues);
|
|
11641
|
+
if (Object.hasOwn(property2, "const") && currentValue === property2.const)
|
|
11642
|
+
return true;
|
|
11643
|
+
return false;
|
|
11644
|
+
}
|
|
11645
|
+
);
|
|
11646
|
+
return computedValuesMatch && validationsMatch;
|
|
11647
|
+
}
|
|
11648
|
+
|
|
11580
11649
|
// src/internals/helpers.js
|
|
11581
11650
|
var import_merge = __toESM(require_merge2());
|
|
11582
11651
|
var import_get2 = __toESM(require_get());
|
|
@@ -11836,76 +11905,6 @@ function _composeFieldCustomClosure(defaultComposeFn) {
|
|
|
11836
11905
|
};
|
|
11837
11906
|
}
|
|
11838
11907
|
|
|
11839
|
-
// src/internals/checkIfConditionMatches.js
|
|
11840
|
-
function checkIfConditionMatchesProperties(node, formValues, formFields, logic) {
|
|
11841
|
-
if (typeof node.if === "boolean") {
|
|
11842
|
-
return node.if;
|
|
11843
|
-
}
|
|
11844
|
-
if (node.if.anyOf) {
|
|
11845
|
-
return node.if.anyOf.some(
|
|
11846
|
-
(property2) => checkIfConditionMatchesProperties({ if: property2 }, formValues, formFields, logic)
|
|
11847
|
-
);
|
|
11848
|
-
}
|
|
11849
|
-
return Object.keys(node.if.properties ?? {}).every((name) => {
|
|
11850
|
-
const currentProperty = node.if.properties[name];
|
|
11851
|
-
const field = getField(name, formFields ?? []);
|
|
11852
|
-
const isFieldsetField = field?.inputType === supportedTypes.FIELDSET;
|
|
11853
|
-
const value = formValues?.[name] ?? (isFieldsetField ? {} : void 0);
|
|
11854
|
-
const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
|
|
11855
|
-
value === null;
|
|
11856
|
-
const hasIfExplicit = node.if.required?.includes(name);
|
|
11857
|
-
if (hasEmptyValue && !hasIfExplicit) {
|
|
11858
|
-
return true;
|
|
11859
|
-
}
|
|
11860
|
-
if (hasProperty(currentProperty, "const")) {
|
|
11861
|
-
return compareFormValueWithSchemaValue(value, currentProperty.const);
|
|
11862
|
-
}
|
|
11863
|
-
if (currentProperty.contains?.pattern) {
|
|
11864
|
-
const formValue = value || [];
|
|
11865
|
-
if (Array.isArray(formValue)) {
|
|
11866
|
-
const pattern = new RegExp(currentProperty.contains.pattern);
|
|
11867
|
-
return (value || []).some((item) => pattern.test(item));
|
|
11868
|
-
}
|
|
11869
|
-
}
|
|
11870
|
-
if (currentProperty.enum) {
|
|
11871
|
-
return currentProperty.enum.includes(value);
|
|
11872
|
-
}
|
|
11873
|
-
if (currentProperty.properties) {
|
|
11874
|
-
return checkIfConditionMatchesProperties(
|
|
11875
|
-
{ if: currentProperty },
|
|
11876
|
-
formValues[name],
|
|
11877
|
-
getField(name, formFields).fields,
|
|
11878
|
-
logic
|
|
11879
|
-
);
|
|
11880
|
-
}
|
|
11881
|
-
return validateFieldSchema(
|
|
11882
|
-
{
|
|
11883
|
-
...field,
|
|
11884
|
-
...currentProperty,
|
|
11885
|
-
required: true
|
|
11886
|
-
},
|
|
11887
|
-
value
|
|
11888
|
-
);
|
|
11889
|
-
});
|
|
11890
|
-
}
|
|
11891
|
-
function checkIfMatchesValidationsAndComputedValues(node, formValues, logic, parentID) {
|
|
11892
|
-
const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property2]) => {
|
|
11893
|
-
const currentValue = logic.getScope(parentID).applyValidationRuleInCondition(name, formValues);
|
|
11894
|
-
if (Object.hasOwn(property2, "const") && currentValue === property2.const)
|
|
11895
|
-
return true;
|
|
11896
|
-
return false;
|
|
11897
|
-
});
|
|
11898
|
-
const computedValuesMatch = Object.entries(node.if.computedValues ?? {}).every(
|
|
11899
|
-
([name, property2]) => {
|
|
11900
|
-
const currentValue = logic.getScope(parentID).applyComputedValueRuleInCondition(name, formValues);
|
|
11901
|
-
if (Object.hasOwn(property2, "const") && currentValue === property2.const)
|
|
11902
|
-
return true;
|
|
11903
|
-
return false;
|
|
11904
|
-
}
|
|
11905
|
-
);
|
|
11906
|
-
return computedValuesMatch && validationsMatch;
|
|
11907
|
-
}
|
|
11908
|
-
|
|
11909
11908
|
// src/jsonLogic.js
|
|
11910
11909
|
var import_json_logic_js = __toESM(require_logic());
|
|
11911
11910
|
|
|
@@ -12447,9 +12446,16 @@ function replaceHandlebarsTemplates({
|
|
|
12447
12446
|
function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
|
|
12448
12447
|
return ({ logic, isRequired, config, formValues }) => {
|
|
12449
12448
|
const { name, computedAttributes } = fieldParams;
|
|
12450
|
-
|
|
12449
|
+
let attributes = Object.fromEntries(
|
|
12451
12450
|
Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
|
|
12452
12451
|
);
|
|
12452
|
+
const { "x-jsf-presentation": presentation, ...rest } = attributes;
|
|
12453
|
+
if (presentation) {
|
|
12454
|
+
attributes = {
|
|
12455
|
+
...rest,
|
|
12456
|
+
...presentation
|
|
12457
|
+
};
|
|
12458
|
+
}
|
|
12453
12459
|
return {
|
|
12454
12460
|
...attributes,
|
|
12455
12461
|
schema: buildYupSchema(
|
|
@@ -12473,16 +12479,21 @@ function handleComputedAttribute(logic, formValues, parentID, name) {
|
|
|
12473
12479
|
handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
|
|
12474
12480
|
];
|
|
12475
12481
|
case "x-jsf-presentation": {
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
handleNestedObjectForComputedValues(
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12482
|
+
const values2 = {};
|
|
12483
|
+
Object.entries(value).forEach(([presentationKey, presentationValue]) => {
|
|
12484
|
+
if (typeof presentationValue === "object") {
|
|
12485
|
+
values2[presentationKey] = handleNestedObjectForComputedValues(
|
|
12486
|
+
presentationValue,
|
|
12487
|
+
formValues,
|
|
12488
|
+
parentID,
|
|
12489
|
+
logic,
|
|
12490
|
+
name
|
|
12491
|
+
);
|
|
12492
|
+
} else {
|
|
12493
|
+
values2[presentationKey] = logic.getScope(parentID).applyComputedValueInField(presentationValue, formValues, name);
|
|
12494
|
+
}
|
|
12495
|
+
});
|
|
12496
|
+
return [key, values2];
|
|
12486
12497
|
}
|
|
12487
12498
|
case "const":
|
|
12488
12499
|
default: {
|
|
@@ -12924,8 +12935,8 @@ function getFieldOptions(node, presentation) {
|
|
|
12924
12935
|
if (presentation.options) {
|
|
12925
12936
|
return presentation.options;
|
|
12926
12937
|
}
|
|
12927
|
-
if (node.oneOf || presentation.inputType === "radio") {
|
|
12928
|
-
return convertToOptions(node.oneOf || []);
|
|
12938
|
+
if (node.oneOf || node.anyOf || presentation.inputType === "radio") {
|
|
12939
|
+
return convertToOptions(node.oneOf || node.anyOf || []);
|
|
12929
12940
|
}
|
|
12930
12941
|
if (node.items?.anyOf) {
|
|
12931
12942
|
return convertToOptions(node.items.anyOf);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remoteoss/json-schema-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-beta.0",
|
|
4
4
|
"description": "Headless UI form powered by JSON Schemas",
|
|
5
5
|
"author": "Remote.com <engineering@remote.com> (https://remote.com/)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -596,74 +596,6 @@ describe('Conditional with anyOf', () => {
|
|
|
596
596
|
});
|
|
597
597
|
});
|
|
598
598
|
|
|
599
|
-
describe('Conditional with fieldset', () => {
|
|
600
|
-
const schema = {
|
|
601
|
-
additionalProperties: false,
|
|
602
|
-
type: 'object',
|
|
603
|
-
properties: {
|
|
604
|
-
field_a: {
|
|
605
|
-
type: 'object',
|
|
606
|
-
properties: {
|
|
607
|
-
min: { type: 'number' },
|
|
608
|
-
max: { type: 'number' },
|
|
609
|
-
},
|
|
610
|
-
},
|
|
611
|
-
field_b: { type: 'string' },
|
|
612
|
-
},
|
|
613
|
-
allOf: [
|
|
614
|
-
{
|
|
615
|
-
if: {
|
|
616
|
-
properties: {
|
|
617
|
-
field_a: {
|
|
618
|
-
properties: {
|
|
619
|
-
min: {
|
|
620
|
-
minimum: 10,
|
|
621
|
-
},
|
|
622
|
-
},
|
|
623
|
-
required: ['min'],
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
required: ['field_a'],
|
|
627
|
-
},
|
|
628
|
-
then: {
|
|
629
|
-
required: ['field_b'],
|
|
630
|
-
},
|
|
631
|
-
else: {
|
|
632
|
-
properties: {
|
|
633
|
-
field_b: false,
|
|
634
|
-
},
|
|
635
|
-
},
|
|
636
|
-
},
|
|
637
|
-
],
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
it('handles true case', () => {
|
|
641
|
-
const { fields, handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
642
|
-
|
|
643
|
-
expect(fields[1].isVisible).toBe(false);
|
|
644
|
-
expect(handleValidation({ field_a: { min: 100 } }).formErrors).toEqual({
|
|
645
|
-
field_b: 'Required field',
|
|
646
|
-
});
|
|
647
|
-
expect(fields[1].isVisible).toBe(true);
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
it('handles false case', () => {
|
|
651
|
-
const { fields, handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
652
|
-
|
|
653
|
-
expect(fields[1].isVisible).toBe(false);
|
|
654
|
-
expect(handleValidation({ field_a: { min: 1 } }).formErrors).toBeUndefined();
|
|
655
|
-
expect(fields[1].isVisible).toBe(false);
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
it('handles undefined fieldset case', () => {
|
|
659
|
-
const { fields, handleValidation } = createHeadlessForm(schema, { strictInputType: false });
|
|
660
|
-
|
|
661
|
-
expect(fields[1].isVisible).toBe(false);
|
|
662
|
-
expect(handleValidation({}).formErrors).toBeUndefined();
|
|
663
|
-
expect(fields[1].isVisible).toBe(false);
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
|
|
667
599
|
describe('Conditionals - bugs and code-smells', () => {
|
|
668
600
|
// Why do we have these bugs?
|
|
669
601
|
// To be honest we never realized it much later later.
|
|
@@ -571,6 +571,44 @@ describe('createHeadlessForm', () => {
|
|
|
571
571
|
});
|
|
572
572
|
});
|
|
573
573
|
|
|
574
|
+
it('support "select" field type from anyOf', () => {
|
|
575
|
+
const schema = {
|
|
576
|
+
type: 'object',
|
|
577
|
+
properties: {
|
|
578
|
+
browser: {
|
|
579
|
+
type: 'string',
|
|
580
|
+
anyOf: [
|
|
581
|
+
{ title: 'Chrome', const: 'chr' },
|
|
582
|
+
{ title: 'Firefox', const: 'ff' },
|
|
583
|
+
{ title: 'Add new option', pattern: '.*' },
|
|
584
|
+
],
|
|
585
|
+
'x-jsf-presentation': {
|
|
586
|
+
inputType: 'select',
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const { fields } = createHeadlessForm(schema);
|
|
593
|
+
const fieldSelect = fields[0];
|
|
594
|
+
expect(fieldSelect).toMatchObject({
|
|
595
|
+
options: [
|
|
596
|
+
{
|
|
597
|
+
value: 'chr',
|
|
598
|
+
label: 'Chrome',
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
value: 'ff',
|
|
602
|
+
label: 'Firefox',
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
label: 'Add new option',
|
|
606
|
+
pattern: '.*',
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
574
612
|
it('supports "select" field type with multiple options @deprecated', () => {
|
|
575
613
|
const result = createHeadlessForm(schemaInputTypeSelectMultipleDeprecated);
|
|
576
614
|
expect(result).toMatchObject({
|
|
@@ -205,7 +205,7 @@ export const schemaInputTypeHidden = {
|
|
|
205
205
|
a_hidden_select_multiple: {
|
|
206
206
|
...schemaInputTypeCountriesMultiple.properties.nationality,
|
|
207
207
|
title: 'Select multi hidden',
|
|
208
|
-
default: ['Albania, Algeria'],
|
|
208
|
+
default: ['Albania', 'Algeria'],
|
|
209
209
|
'x-jsf-presentation': { inputType: 'hidden' },
|
|
210
210
|
type: 'array',
|
|
211
211
|
},
|
package/src/tests/helpers.js
CHANGED
|
@@ -448,20 +448,19 @@ export const mockGroupArrayInput = {
|
|
|
448
448
|
sex: {
|
|
449
449
|
description:
|
|
450
450
|
'We know sex is non-binary but for insurance and payroll purposes, we need to collect this information.',
|
|
451
|
-
enum: ['female', 'male'],
|
|
452
451
|
'x-jsf-presentation': {
|
|
453
452
|
inputType: 'radio',
|
|
454
|
-
options: [
|
|
455
|
-
{
|
|
456
|
-
label: 'Male',
|
|
457
|
-
value: 'male',
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
label: 'Female',
|
|
461
|
-
value: 'female',
|
|
462
|
-
},
|
|
463
|
-
],
|
|
464
453
|
},
|
|
454
|
+
oneOf: [
|
|
455
|
+
{
|
|
456
|
+
const: 'male',
|
|
457
|
+
title: 'Male',
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
const: 'female',
|
|
461
|
+
title: 'Female',
|
|
462
|
+
},
|
|
463
|
+
],
|
|
465
464
|
title: 'Child Sex',
|
|
466
465
|
type: 'string',
|
|
467
466
|
},
|
|
@@ -1469,7 +1468,6 @@ export const schemaDynamicValidationConst = {
|
|
|
1469
1468
|
required: ['a_fieldset', 'validate_tabs', 'mandatory_group_array'],
|
|
1470
1469
|
'x-jsf-order': ['validate_tabs', 'a_fieldset', 'mandatory_group_array', 'a_group_array'],
|
|
1471
1470
|
};
|
|
1472
|
-
|
|
1473
1471
|
export const schemaDynamicValidationMinimumMaximum = JSONSchemaBuilder()
|
|
1474
1472
|
.addInput({
|
|
1475
1473
|
a_number: mockNumberInput,
|
|
@@ -1543,6 +1541,7 @@ export const schemaDynamicValidationContains = JSONSchemaBuilder()
|
|
|
1543
1541
|
},
|
|
1544
1542
|
'x-jsf-presentation': {
|
|
1545
1543
|
inputType: 'select',
|
|
1544
|
+
multiple: true,
|
|
1546
1545
|
options: [
|
|
1547
1546
|
{
|
|
1548
1547
|
label: 'All',
|
|
@@ -364,6 +364,55 @@ export const schemaWithComputedAttributesAndErrorMessages = {
|
|
|
364
364
|
},
|
|
365
365
|
};
|
|
366
366
|
|
|
367
|
+
export const schemaWithComputedPresentationAttributes = {
|
|
368
|
+
properties: {
|
|
369
|
+
amount: {
|
|
370
|
+
description: 'Total amount to be reimbursed, including taxes.',
|
|
371
|
+
minimum: 0,
|
|
372
|
+
title: 'Total amount',
|
|
373
|
+
type: 'number',
|
|
374
|
+
'x-jsf-logic-computedAttrs': {
|
|
375
|
+
'x-jsf-presentation': {
|
|
376
|
+
currency: 'currency_selected',
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
'x-jsf-presentation': {
|
|
380
|
+
currency: '---',
|
|
381
|
+
inputType: 'money',
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
currency: {
|
|
385
|
+
default: 'USD',
|
|
386
|
+
description: 'Currency in which the expense was paid.',
|
|
387
|
+
oneOf: [
|
|
388
|
+
{
|
|
389
|
+
const: 'UGX',
|
|
390
|
+
title: 'UGX - Ugandan Shilling',
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
const: 'USD',
|
|
394
|
+
title: 'USD - United States Dollar',
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
title: 'Currency',
|
|
398
|
+
type: 'string',
|
|
399
|
+
'x-jsf-presentation': {
|
|
400
|
+
inputType: 'select',
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
type: 'object',
|
|
405
|
+
'x-jsf-logic': {
|
|
406
|
+
computedValues: {
|
|
407
|
+
currency_selected: {
|
|
408
|
+
rule: {
|
|
409
|
+
var: 'currency',
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
|
|
367
416
|
export const schemaWithDeepVarThatDoesNotExist = {
|
|
368
417
|
properties: {
|
|
369
418
|
field_a: {
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
schemaWithValidationThatDoesNotExistOnProperty,
|
|
36
36
|
badSchemaThatWillNotSetAForcedValue,
|
|
37
37
|
schemaWithReduceAccumulator,
|
|
38
|
+
schemaWithComputedPresentationAttributes,
|
|
38
39
|
} from './jsonLogic.fixtures';
|
|
39
40
|
import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils';
|
|
40
41
|
import { createHeadlessForm } from '@/createHeadlessForm';
|
|
@@ -385,6 +386,17 @@ describe('jsonLogic: cross-values validations', () => {
|
|
|
385
386
|
expect(fieldB.statement).toEqual({ description: 'Must be bigger than 4 and smaller than 8' });
|
|
386
387
|
});
|
|
387
388
|
|
|
389
|
+
it('presentation attributes work', () => {
|
|
390
|
+
const { fields, handleValidation } = createHeadlessForm(
|
|
391
|
+
schemaWithComputedPresentationAttributes,
|
|
392
|
+
{ strictInputType: false }
|
|
393
|
+
);
|
|
394
|
+
const fieldB = fields.find((i) => i.name === 'amount');
|
|
395
|
+
expect(handleValidation({ currency: 'UGX' }).formErrors).toEqual(undefined);
|
|
396
|
+
expect(fieldB.inputType).toEqual('money');
|
|
397
|
+
expect(fieldB.currency).toEqual('UGX');
|
|
398
|
+
});
|
|
399
|
+
|
|
388
400
|
it('Use a inline-rule in a schema for a title attribute', () => {
|
|
389
401
|
const { fields, handleValidation } = createHeadlessForm(
|
|
390
402
|
schemaWithInlineRuleForComputedAttributeWithCopy,
|