Skip to content

Commit 45682f8

Browse files
committed
Merge branch 'main' into rmt-1768-jsf-performance-ui-lags-when-a-field-contains-large-select
2 parents 1d6a9eb + 01059cf commit 45682f8

File tree

10 files changed

+623
-38
lines changed

10 files changed

+623
-38
lines changed

v0/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#### 0.12.2-beta.0 (2025-10-23)
2+
3+
##### New Features
4+
5+
* **v0:** Support nested fieldset conditionals ([#156](https://github.com/remoteoss/json-schema-form/pull/156)) ([877e82e1](https://github.com/remoteoss/json-schema-form/commit/877e82e148d1ff6964ba634108f8ca821c040565))
6+
17
#### 0.12.1-beta.0 (2025-10-09)
28

39
##### Bug Fixes

v0/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v0/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@remoteoss/json-schema-form",
3-
"version": "0.12.1-beta.0",
3+
"version": "0.12.2-beta.0",
44
"description": "Headless UI form powered by JSON Schemas",
55
"author": "Remote.com <[email protected]> (https://remote.com/)",
66
"license": "MIT",
@@ -43,7 +43,7 @@
4343
},
4444
"lint-staged": {
4545
"*.{js,jsx}": [
46-
"npm run format"
46+
"npm run format --prefix ./v0"
4747
]
4848
},
4949
"dependencies": {

v0/scripts/build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const esbuild = require('esbuild');
66

77
const pkg = require('../package.json');
88

9-
const licenseContent = fs.readFileSync(path.join(__dirname, '../LICENSE'), 'utf8');
9+
const licenseContent = fs.readFileSync(path.join(__dirname, '../../LICENSE'), 'utf8');
1010
const packageJson = require(path.resolve(__dirname, '../package.json'));
1111
const pkgVersion = packageJson.version;
1212

v0/src/calculateConditionalProperties.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,23 @@ function isFieldRequired(node, field) {
2828
/**
2929
* Loops recursively through fieldset fields and returns an copy version of them
3030
* where the required property is updated.
31+
* Since rebuildFieldset is called within a closure, we pass the current fields as parameter
32+
* to restore the computed isVisible property.
3133
*
3234
* @param {Array} fields - list of fields of a fieldset
3335
* @param {Object} property - property that relates with the list of fields
3436
* @returns {Object}
3537
*/
36-
function rebuildFieldset(fields, property) {
38+
function rebuildFieldset(fields, currentFields, property) {
3739
if (property?.properties) {
38-
return fields.map((field) => {
40+
return fields.map((field, index) => {
3941
const propertyConditionals = property.properties[field.name];
42+
const isVisible = currentFields[index].isVisible;
4043
if (!propertyConditionals) {
41-
return field;
44+
return {
45+
...field,
46+
isVisible,
47+
};
4248
}
4349

4450
const newFieldParams = extractParametersFromNode(propertyConditionals);
@@ -47,19 +53,22 @@ function rebuildFieldset(fields, property) {
4753
return {
4854
...field,
4955
...newFieldParams,
50-
fields: rebuildFieldset(field.fields, propertyConditionals),
56+
isVisible,
57+
fields: rebuildFieldset(field.fields, currentFields[index].fields, propertyConditionals),
5158
};
5259
}
5360
return {
5461
...field,
5562
...newFieldParams,
63+
isVisible,
5664
required: isFieldRequired(property, field),
5765
};
5866
});
5967
}
6068

61-
return fields.map((field) => ({
69+
return fields.map((field, index) => ({
6270
...field,
71+
isVisible: currentFields[index].isVisible,
6372
required: isFieldRequired(property, field),
6473
}));
6574
}
@@ -92,7 +101,7 @@ export function calculateConditionalProperties({ fieldParams, customProperties,
92101
*
93102
* @returns {calculateConditionalPropertiesReturn}
94103
*/
95-
return ({ isRequired, conditionBranch, formValues }) => {
104+
return ({ isRequired, conditionBranch, formValues, currentField }) => {
96105
// Check if the current field is conditionally declared in the schema
97106
// console.log('::calc (closure original)', fieldParams.description);
98107
const conditionalProperty = conditionBranch?.properties?.[fieldParams.name];
@@ -110,7 +119,11 @@ export function calculateConditionalProperties({ fieldParams, customProperties,
110119
let fieldSetFields;
111120

112121
if (fieldParams.inputType === supportedTypes.FIELDSET) {
113-
fieldSetFields = rebuildFieldset(fieldParams.fields, conditionalProperty);
122+
fieldSetFields = rebuildFieldset(
123+
fieldParams.fields,
124+
currentField.fields,
125+
conditionalProperty
126+
);
114127
newFieldParams.fields = fieldSetFields;
115128
}
116129

v0/src/helpers.js

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import set from 'lodash/set';
77
import { lazy } from 'yup';
88

99
import { checkIfConditionMatchesProperties } from './internals/checkIfConditionMatches';
10-
import { supportedTypes, getInputType } from './internals/fields';
10+
import { supportedTypes } from './internals/fields';
1111
import { pickXKey } from './internals/helpers';
1212
import { processJSONLogicNode } from './jsonLogic';
1313
import { hasProperty } from './utils';
@@ -161,7 +161,7 @@ function getPrefillSubFieldValues(field, defaultValues, parentFieldKeyPath) {
161161
initialValue[field.name] = subFieldValues;
162162
}
163163
} else {
164-
// getDefaultValues and getPrefillSubFieldValues have a circluar dependency, resulting in one having to be used before defined.
164+
// getDefaultValues and getPrefillSubFieldValues have a circular dependency, resulting in one having to be used before defined.
165165
// As function declarations are hoisted this should not be a problem.
166166
// eslint-disable-next-line no-use-before-define
167167

@@ -295,6 +295,7 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
295295
isRequired: fieldIsRequired,
296296
conditionBranch: node,
297297
formValues,
298+
currentField: field,
298299
});
299300
updateAttributes(newAttributes);
300301
removeConditionalStaleAttributes(field, newAttributes, rootFieldAttrs);
@@ -336,9 +337,22 @@ export function processNode({
336337
const requiredFields = new Set(accRequired);
337338

338339
// Go through the node properties definition and update each field accordingly
339-
Object.keys(node.properties ?? []).forEach((fieldName) => {
340+
Object.entries(node.properties ?? []).forEach(([fieldName, nestedNode]) => {
340341
const field = getField(fieldName, formFields);
341342
updateField(field, requiredFields, node, formValues, logic, { parentID });
343+
344+
// If we're processing a fieldset field node
345+
// update the nested fields going through the node recursively.
346+
const isFieldset = field?.inputType === supportedTypes.FIELDSET;
347+
if (isFieldset) {
348+
processNode({
349+
node: nestedNode,
350+
formValues: formValues[fieldName] || {},
351+
formFields: field.fields,
352+
parentID,
353+
logic,
354+
});
355+
}
342356
});
343357

344358
// Update required fields based on the `required` property and mutate node if needed
@@ -351,7 +365,7 @@ export function processNode({
351365

352366
if (node.if !== undefined) {
353367
const matchesCondition = checkIfConditionMatchesProperties(node, formValues, formFields, logic);
354-
// BUG HERE (unreleated) - what if it matches but doesn't has a then,
368+
// BUG HERE (unrelated) - what if it matches but doesn't has a then,
355369
// it should do nothing, but instead it jumps to node.else when it shouldn't.
356370
if (matchesCondition && node.then) {
357371
const { required: branchRequired } = processNode({
@@ -408,22 +422,6 @@ export function processNode({
408422
});
409423
}
410424

411-
if (node.properties) {
412-
Object.entries(node.properties).forEach(([name, nestedNode]) => {
413-
const inputType = getInputType(nestedNode);
414-
if (inputType === supportedTypes.FIELDSET) {
415-
// It's a fieldset, which might contain scoped conditions
416-
processNode({
417-
node: nestedNode,
418-
formValues: formValues[name] || {},
419-
formFields: getField(name, formFields).fields,
420-
parentID: name,
421-
logic,
422-
});
423-
}
424-
});
425-
}
426-
427425
if (node['x-jsf-logic']) {
428426
const { required: requiredFromLogic } = processJSONLogicNode({
429427
node: node['x-jsf-logic'],
@@ -451,14 +449,14 @@ export function processNode({
451449
function clearValuesIfNotVisible(fields, formValues) {
452450
fields.forEach(({ isVisible = true, name, inputType, fields: nestedFields }) => {
453451
if (!isVisible) {
454-
// TODO I (Sandrina) think this doesn't work. I didn't find any test covering this scenario. Revisit later.
455452
formValues[name] = null;
456453
}
457454
if (inputType === supportedTypes.FIELDSET && nestedFields && formValues[name]) {
458455
clearValuesIfNotVisible(nestedFields, formValues[name]);
459456
}
460457
});
461458
}
459+
462460
/**
463461
* Updates form fields properties based on the current form state and the JSON schema rules
464462
*
@@ -500,7 +498,7 @@ function getFieldOptions(node, presentation) {
500498
}));
501499
}
502500

503-
/** @deprecated - takes precendence in case a JSON Schema still has deprecated options */
501+
/** @deprecated - takes precedence in case a JSON Schema still has deprecated options */
504502
if (presentation.options) {
505503
return presentation.options;
506504
}

0 commit comments

Comments
 (0)