@syncfusion/ej2-react-form-renderer 0.1.0-beta → 0.1.2-beta
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/README.md +3 -10
- package/package.json +1 -1
- package/src/CheckboxGroup.js +3 -2
- package/src/ConditionalRuleEngine.d.ts +295 -0
- package/src/ConditionalRuleEngine.js +566 -0
- package/src/DataGridComponent.d.ts +0 -1
- package/src/DataGridComponent.js +9 -22
- package/src/FormRenderer.d.ts +28 -7
- package/src/FormRenderer.js +342 -202
- package/src/MinMaxRuleEngine.d.ts +95 -0
- package/src/MinMaxRuleEngine.js +361 -0
- package/src/expressions/DependencyTracker.d.ts +30 -0
- package/src/expressions/DependencyTracker.js +128 -0
- package/src/expressions/ExpressionEngine.d.ts +81 -0
- package/src/expressions/ExpressionEngine.js +503 -0
- package/src/expressions/ExpressionParser.d.ts +39 -0
- package/src/expressions/ExpressionParser.js +270 -0
- package/src/json-converter.d.ts +32 -31
- package/src/json-converter.js +313 -284
- package/src/parser.js +22 -18
- package/src/renderFormField.d.ts +3 -1
- package/src/renderFormField.js +70 -46
- package/src/types/FormSchema.d.ts +36 -2
- package/src/utils.js +348 -0
- package/styles/form-renderer.base.css +49 -23
package/src/FormRenderer.js
CHANGED
|
@@ -6,14 +6,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
7
7
|
import { Form, FormField } from './FormValidator';
|
|
8
8
|
import { enableRipple } from '@syncfusion/ej2-base';
|
|
9
|
-
import {
|
|
9
|
+
import { RenderFormField } from './renderFormField';
|
|
10
10
|
import { CustomTabComponent } from './CustomTabComponent';
|
|
11
|
-
import { mergeSchema
|
|
11
|
+
import { mergeSchema } from './json-converter';
|
|
12
12
|
import { UniversalJsonParser } from './parser';
|
|
13
13
|
import { TooltipComponent } from '@syncfusion/ej2-react-popups';
|
|
14
14
|
import { VALIDATION_REGEX } from './FormValidator';
|
|
15
15
|
import StaticHtmlRenderer from './StaticHtmlRenderer';
|
|
16
|
-
import { splitWidths } from './utils';
|
|
16
|
+
import { formatDateValues, splitWidths } from './utils';
|
|
17
|
+
import { ConditionalRuleEngine } from './ConditionalRuleEngine';
|
|
18
|
+
import { ExpressionEngine } from './expressions/ExpressionEngine';
|
|
19
|
+
import { MinMaxRuleEngine } from './MinMaxRuleEngine';
|
|
17
20
|
enableRipple(true);
|
|
18
21
|
/**
|
|
19
22
|
* Form layout options (only applies when UI schema is NOT provided)
|
|
@@ -24,153 +27,60 @@ export const FormLayout = {
|
|
|
24
27
|
ThreeColumn: 'threecolumn',
|
|
25
28
|
FourColumn: 'fourcolumn'
|
|
26
29
|
};
|
|
27
|
-
/**
|
|
28
|
-
* Helper: Normalize DataSchema input
|
|
29
|
-
* Converts any object to JSON string and parses with UniversalJsonParser
|
|
30
|
-
* Handles FormSchema, DataSchema, or raw data - parser infers the format
|
|
31
|
-
*/
|
|
32
|
-
function normalizeDataSchema(input) {
|
|
33
|
-
try {
|
|
34
|
-
// Convert object to JSON string
|
|
35
|
-
const jsonString = JSON.stringify(input);
|
|
36
|
-
const parser = new UniversalJsonParser();
|
|
37
|
-
const parseResult = parser.parse(jsonString);
|
|
38
|
-
if (parseResult.success && parseResult.schema) {
|
|
39
|
-
const dataSchema = convertComponentsToDataSchema(parseResult.schema.components);
|
|
40
|
-
// console.log(`✅ Parsed object as ${parseResult.metadata.detectedFormat} and converted to DataSchema`);
|
|
41
|
-
return dataSchema;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
console.error('❌ Failed to parse object:', parseResult.errors);
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
console.error('❌ DataSchema normalization error:', error);
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Helper: Parse string input using UniversalJsonParser
|
|
55
|
-
* Returns DataSchema if parseable, otherwise null
|
|
56
|
-
* Validates that UI schema was not accidentally provided
|
|
57
|
-
*/
|
|
58
|
-
function parseStringToDataSchema(input) {
|
|
59
|
-
try {
|
|
60
|
-
// First check if user accidentally pasted UI schema in data schema field
|
|
61
|
-
const testParse = JSON.parse(input);
|
|
62
|
-
if (testParse.layout || (testParse.theme && testParse.properties && testParse.layout)) {
|
|
63
|
-
console.error('The provided schema is a UI Schema in the data schema prop. Please provide a valid Data Schema with field definitions and validation rules.');
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
const parser = new UniversalJsonParser();
|
|
67
|
-
const parseResult = parser.parse(input);
|
|
68
|
-
if (parseResult.success && parseResult.schema) {
|
|
69
|
-
const dataSchema = convertComponentsToDataSchema(parseResult.schema.components);
|
|
70
|
-
return dataSchema;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
console.error('Failed to parse input:', parseResult.errors);
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
console.error('Parser error:', error);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Helper: Parse string input as UISchema
|
|
84
|
-
* Validates JSON structure matches UISchema format
|
|
85
|
-
* Detects if data schema was accidentally provided
|
|
86
|
-
*/
|
|
87
|
-
function parseStringToUISchema(input) {
|
|
88
|
-
try {
|
|
89
|
-
const parsed = JSON.parse(input);
|
|
90
|
-
// Check if data schema was pasted in UI schema field
|
|
91
|
-
if (parsed.properties && !parsed.layout && !parsed.theme) {
|
|
92
|
-
const hasValidationProps = Object.values(parsed.properties).some((prop) => prop.required !== undefined || prop.minLength !== undefined || prop.maxLength !== undefined ||
|
|
93
|
-
prop.minValue !== undefined || prop.maxValue !== undefined || prop.pattern !== undefined);
|
|
94
|
-
if (hasValidationProps) {
|
|
95
|
-
console.error('The provided schema is a Data Schema in the UI schema prop. Please provide a valid UI Schema with theme, properties, and layout definitions.');
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// Validate UISchema structure
|
|
100
|
-
if (!parsed.theme || !parsed.properties || !parsed.layout) {
|
|
101
|
-
console.error('Invalid UISchema: missing theme, properties, or layout');
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
return parsed;
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
console.error('JSON parse error:', error);
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
30
|
// ForwardRef to expose public methods
|
|
112
|
-
export const FormRenderer = React.forwardRef(({
|
|
31
|
+
export const FormRenderer = React.forwardRef(({ schema, locale, onSubmit, onChange, onButtonClick, className = '', layout = FormLayout.SingleColumn, created, ...restProps }, ref) => {
|
|
113
32
|
const processedSchema = React.useMemo(() => {
|
|
114
33
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
:
|
|
126
|
-
: undefined;
|
|
127
|
-
// Scenario 1: Both dataSchema and uiSchema provided - full merge
|
|
128
|
-
if (parsedDataSchema && parsedUISchema) {
|
|
129
|
-
const uiFieldIds = new Set(Object.keys(parsedUISchema.properties));
|
|
130
|
-
const dataFieldIds = Object.keys(parsedDataSchema.properties);
|
|
131
|
-
const missingFields = dataFieldIds.filter(id => !uiFieldIds.has(id));
|
|
132
|
-
if (missingFields.length > 0) {
|
|
133
|
-
console.warn(`⚠️ Found ${missingFields.length} fields in data schema missing from UI schema`);
|
|
34
|
+
if (!schema) {
|
|
35
|
+
return { version: '1.0.0', components: [] };
|
|
36
|
+
}
|
|
37
|
+
let parsedInput = schema;
|
|
38
|
+
if (typeof schema === 'string') {
|
|
39
|
+
try {
|
|
40
|
+
parsedInput = JSON.parse(schema);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error('FormRenderer: Failed to parse schema string', err);
|
|
44
|
+
return { version: '1.0.0', components: [] };
|
|
134
45
|
}
|
|
135
|
-
const merged = mergeSchema({ dataSchema: parsedDataSchema, uiSchema: parsedUISchema });
|
|
136
|
-
return merged;
|
|
137
46
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
layout: generateLayoutFromData(parsedDataSchema)
|
|
143
|
-
};
|
|
144
|
-
const merged = mergeSchema({ dataSchema: parsedDataSchema, uiSchema: autoUISchema });
|
|
145
|
-
return merged;
|
|
47
|
+
const isUnifiedSchema = parsedInput && typeof parsedInput === 'object' &&
|
|
48
|
+
parsedInput.properties && Array.isArray(parsedInput.layout);
|
|
49
|
+
if (isUnifiedSchema) {
|
|
50
|
+
return mergeSchema(parsedInput);
|
|
146
51
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
console.warn(
|
|
52
|
+
else {
|
|
53
|
+
const parser = new UniversalJsonParser();
|
|
54
|
+
const parseResult = parser.parse(typeof schema === 'string' ? schema : JSON.stringify(schema));
|
|
55
|
+
if (parseResult.success && parseResult.schema) {
|
|
56
|
+
return parseResult.schema;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.warn('FormRenderer: Failed to parse schema', parseResult.errors);
|
|
60
|
+
return { version: '1.0.0', components: [] };
|
|
155
61
|
}
|
|
156
|
-
const merged = mergeSchema({ dataSchema: autoDataSchema, uiSchema: parsedUISchema });
|
|
157
|
-
return merged;
|
|
158
62
|
}
|
|
159
|
-
// Scenario 4: Neither provided - return empty form
|
|
160
|
-
console.warn('FormRenderer: No schema provided (dataSchema or uiSchema)');
|
|
161
|
-
return { version: '1.0.0-beta', theme: 'tailwind3', components: [] };
|
|
162
63
|
}
|
|
163
64
|
catch (error) {
|
|
164
65
|
console.error('FormRenderer schema processing error:', error);
|
|
165
|
-
return { version: '1.0.0
|
|
66
|
+
return { version: '1.0.0', components: [] };
|
|
166
67
|
}
|
|
167
|
-
}, [
|
|
68
|
+
}, [schema]);
|
|
168
69
|
const [formState, setFormState] = useState(undefined);
|
|
169
70
|
const isInitializedRef = useRef(false);
|
|
71
|
+
const formLocale = locale || 'en-US';
|
|
170
72
|
// Attach ref to the input element for getComponent
|
|
171
73
|
let setInputRef;
|
|
172
74
|
// Store input component refs by name/id
|
|
173
75
|
const inputRefs = useRef({});
|
|
76
|
+
// Track which fields have active conditional values applied (to prevent re-applying while condition stays true)
|
|
77
|
+
const conditionalValueAppliedRef = useRef({});
|
|
78
|
+
// Expression Engine initialization
|
|
79
|
+
const expressionEngineRef = useRef(null);
|
|
80
|
+
const expressionDependencyGraphRef = useRef({});
|
|
81
|
+
const expressionEvaluationInProgressRef = useRef(false);
|
|
82
|
+
const previousFormValuesRef = useRef({});
|
|
83
|
+
const computedFieldNamesRef = useRef(new Set());
|
|
174
84
|
// Check if UI schema has layout components (table, tabs, or panel)
|
|
175
85
|
const hasLayoutComponents = React.useMemo(() => {
|
|
176
86
|
if (!processedSchema.components)
|
|
@@ -256,7 +166,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
256
166
|
const map = {};
|
|
257
167
|
const traverse = (comps) => {
|
|
258
168
|
comps.forEach(comp => {
|
|
259
|
-
if (comp.label && !['panel', 'table', 'tabs', 'card', 'button', 'message', '
|
|
169
|
+
if (comp.label && !['panel', 'table', 'tabs', 'card', 'button', 'message', 'splitButton'].includes(comp.type)) {
|
|
260
170
|
map[comp.id] = comp.label;
|
|
261
171
|
}
|
|
262
172
|
if (comp.children)
|
|
@@ -282,20 +192,27 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
282
192
|
*/
|
|
283
193
|
const buildValidationRules = useCallback(() => {
|
|
284
194
|
const rules = {};
|
|
195
|
+
const formValues = formState?.values || {};
|
|
285
196
|
const collectRules = (components) => {
|
|
286
197
|
components.forEach((component) => {
|
|
287
198
|
if (component) {
|
|
288
199
|
const componentRules = {};
|
|
289
|
-
|
|
200
|
+
const conditionalRequired = ConditionalRuleEngine.isFieldRequired(component.conditions, formValues);
|
|
201
|
+
const conditionalDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
202
|
+
const conditionalReadOnly = ConditionalRuleEngine.isFieldReadOnly(component.conditions, formValues);
|
|
203
|
+
const finalRequired = component.required || conditionalRequired;
|
|
204
|
+
const finalDisabled = component.disabled || conditionalDisabled;
|
|
205
|
+
const finalReadOnly = component.readOnly || conditionalReadOnly;
|
|
206
|
+
if (finalRequired && !finalDisabled && !finalReadOnly) {
|
|
290
207
|
componentRules.required = [true, component.validationMessage || `${component.label} is required`];
|
|
291
208
|
}
|
|
292
|
-
if (component.minLength && !
|
|
209
|
+
if (component.minLength && !finalDisabled && !finalReadOnly) {
|
|
293
210
|
componentRules.minLength = [component.minLength, `Minimum ${component.minLength} characters required`];
|
|
294
211
|
}
|
|
295
|
-
if (component.maxLength && !
|
|
212
|
+
if (component.maxLength && !finalDisabled && !finalReadOnly) {
|
|
296
213
|
componentRules.maxLength = [component.maxLength, `Maximum ${component.maxLength} characters allowed`];
|
|
297
214
|
}
|
|
298
|
-
if (component?.type === 'textbox' && component?.textboxType && !component?.regex && !componentRules.regex && !
|
|
215
|
+
if (component?.type === 'textbox' && component?.textboxType && !component?.regex && !componentRules.regex && !finalDisabled && !finalReadOnly) {
|
|
299
216
|
if (component.textboxType === 'email') {
|
|
300
217
|
componentRules.regex = [VALIDATION_REGEX['EMAIL'], component?.validationMessage || 'Invalid format'];
|
|
301
218
|
}
|
|
@@ -303,7 +220,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
303
220
|
componentRules.regex = [VALIDATION_REGEX['URL'], component?.validationMessage || 'Invalid format'];
|
|
304
221
|
}
|
|
305
222
|
}
|
|
306
|
-
if (component.regex && !
|
|
223
|
+
if (component.regex && !finalDisabled && !finalReadOnly) {
|
|
307
224
|
componentRules.regex = [component.regex, component.validationMessage || 'Invalid format'];
|
|
308
225
|
}
|
|
309
226
|
if (Object.keys(componentRules).length > 0) {
|
|
@@ -329,7 +246,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
329
246
|
};
|
|
330
247
|
collectRules(processedSchema.components);
|
|
331
248
|
return rules;
|
|
332
|
-
}, [processedSchema]);
|
|
249
|
+
}, [processedSchema, formState?.values]);
|
|
333
250
|
const validationRules = buildValidationRules();
|
|
334
251
|
/**
|
|
335
252
|
* Build initial form values from component defaultValues
|
|
@@ -339,14 +256,23 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
339
256
|
const allComponents = collectAllComponents(processedSchema.components);
|
|
340
257
|
const componentIds = new Set();
|
|
341
258
|
allComponents.forEach(comp => {
|
|
342
|
-
// Do NOT store
|
|
343
|
-
if (comp.type === '
|
|
259
|
+
// Do NOT store dataGrid data inside the form state; grids manage their own internal data.
|
|
260
|
+
if (comp.type === 'dataGrid' || comp.type === 'richTextEditor' || comp.type === 'staticHtml') {
|
|
344
261
|
componentIds.add(comp.id);
|
|
345
262
|
return;
|
|
346
263
|
}
|
|
347
264
|
if (comp.defaultValue !== undefined) {
|
|
348
265
|
values[comp.id] = comp.defaultValue;
|
|
349
266
|
}
|
|
267
|
+
else {
|
|
268
|
+
// Initialize with type-appropriate defaults
|
|
269
|
+
switch (comp.type) {
|
|
270
|
+
case 'checkbox':
|
|
271
|
+
case 'switch':
|
|
272
|
+
values[comp.id] = false;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
350
276
|
componentIds.add(comp.id);
|
|
351
277
|
});
|
|
352
278
|
if (formState) {
|
|
@@ -355,7 +281,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
355
281
|
});
|
|
356
282
|
}
|
|
357
283
|
return values;
|
|
358
|
-
}, [processedSchema, collectAllComponents,
|
|
284
|
+
}, [processedSchema, collectAllComponents, schema]);
|
|
359
285
|
// Update the useEffect that sets default values
|
|
360
286
|
useEffect(() => {
|
|
361
287
|
if (formState && !isInitializedRef.current) {
|
|
@@ -370,21 +296,202 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
370
296
|
});
|
|
371
297
|
}
|
|
372
298
|
}, [formState, processedSchema, initialValues]);
|
|
299
|
+
/**
|
|
300
|
+
* Initialize Expression Engine and build dependency graph
|
|
301
|
+
* Runs when schema changes
|
|
302
|
+
*/
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
if (!processedSchema.components || processedSchema.components.length === 0) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
// Initialize expression engine if not already done
|
|
309
|
+
if (!expressionEngineRef.current) {
|
|
310
|
+
expressionEngineRef.current = new ExpressionEngine();
|
|
311
|
+
}
|
|
312
|
+
const engine = expressionEngineRef.current;
|
|
313
|
+
const allComponents = collectAllComponents(processedSchema.components);
|
|
314
|
+
// Build dependency graph from all expressions
|
|
315
|
+
const graph = engine.buildDependencyGraph(allComponents);
|
|
316
|
+
// Check for circular dependencies
|
|
317
|
+
const cycles = engine.detectCircularDependencies(graph);
|
|
318
|
+
if (cycles.length > 0) {
|
|
319
|
+
//console.warn('⚠️ Circular dependencies detected in expressions:', cycles);
|
|
320
|
+
}
|
|
321
|
+
// Store dependency graph
|
|
322
|
+
expressionDependencyGraphRef.current = graph;
|
|
323
|
+
// Track which fields are computed by expressions
|
|
324
|
+
const computedFields = new Set();
|
|
325
|
+
allComponents.forEach(comp => {
|
|
326
|
+
if (comp.expressionValue) {
|
|
327
|
+
computedFields.add(comp.id);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
computedFieldNamesRef.current = computedFields;
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
console.error('Error initializing expression engine:', error);
|
|
334
|
+
}
|
|
335
|
+
}, [processedSchema, collectAllComponents]);
|
|
336
|
+
/**
|
|
337
|
+
* Evaluate expressions when form values change
|
|
338
|
+
* Runs expressions in topological order to ensure dependencies are satisfied
|
|
339
|
+
*/
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
if (!formState || !expressionEngineRef.current) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Prevent recursive evaluation loops
|
|
345
|
+
if (expressionEvaluationInProgressRef.current) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
expressionEvaluationInProgressRef.current = true;
|
|
350
|
+
const engine = expressionEngineRef.current;
|
|
351
|
+
const dependencyGraph = expressionDependencyGraphRef.current;
|
|
352
|
+
const formValues = formState.values || {};
|
|
353
|
+
const allComponents = collectAllComponents(processedSchema.components);
|
|
354
|
+
const previousValues = previousFormValuesRef.current;
|
|
355
|
+
const computedFieldNames = computedFieldNamesRef.current;
|
|
356
|
+
// Create a mapping of component id -> component name for context building
|
|
357
|
+
const idToNameMap = {};
|
|
358
|
+
allComponents.forEach(comp => {
|
|
359
|
+
if (comp.name) {
|
|
360
|
+
idToNameMap[comp.id] = comp.name;
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
// Convert formValues from id-based keys to name-based keys for expression context
|
|
364
|
+
const contextValues = {};
|
|
365
|
+
Object.entries(formValues).forEach(([componentId, value]) => {
|
|
366
|
+
const fieldName = idToNameMap[componentId];
|
|
367
|
+
if (fieldName) {
|
|
368
|
+
contextValues[fieldName] = value;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
// Capture previous state before checking for changes
|
|
372
|
+
const previousContextValues = { ...previousValues };
|
|
373
|
+
// Update previous values immediately with current context for next cycle
|
|
374
|
+
// This ensures stale values don't affect the next evaluation cycle
|
|
375
|
+
Object.assign(previousValues, contextValues);
|
|
376
|
+
// Find which fields have changed (using captured previous state)
|
|
377
|
+
const changedFields = new Set();
|
|
378
|
+
Object.keys(contextValues).forEach(fieldName => {
|
|
379
|
+
const fieldId = Object.keys(idToNameMap).find(id => idToNameMap[id] === fieldName);
|
|
380
|
+
if (fieldId && contextValues[fieldName] !== previousContextValues[fieldName] && !computedFieldNames.has(fieldId)) {
|
|
381
|
+
changedFields.add(fieldName);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
// If no non-computed fields changed, skip evaluation
|
|
385
|
+
if (changedFields.size === 0) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
// Get topological order for evaluation
|
|
389
|
+
const evaluationOrder = engine.getTopologicalOrder(dependencyGraph);
|
|
390
|
+
evaluationOrder.forEach(fieldName => {
|
|
391
|
+
// Find component with this name
|
|
392
|
+
const component = allComponents.find(c => c.name === fieldName);
|
|
393
|
+
if (component && component.expressionValue) {
|
|
394
|
+
// Mark this field as computed
|
|
395
|
+
computedFieldNames.add(component.id);
|
|
396
|
+
try {
|
|
397
|
+
// Only evaluate if field is visible and not read-only
|
|
398
|
+
const isVisible = component.visible !== false
|
|
399
|
+
? ConditionalRuleEngine.isFieldVisible(component.conditions, formValues)
|
|
400
|
+
: false;
|
|
401
|
+
if (isVisible) {
|
|
402
|
+
const currentValues = {};
|
|
403
|
+
Object.entries(formValues).forEach(([componentId, value]) => {
|
|
404
|
+
const fieldName = idToNameMap[componentId];
|
|
405
|
+
if (fieldName) {
|
|
406
|
+
currentValues[fieldName] = value;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
// Use context values (name-based) for evaluation
|
|
410
|
+
let result = engine.evaluate(component.expressionValue, currentValues);
|
|
411
|
+
result = typeof (result) === 'string' && result.includes('undefined') ? result.replace(/undefined/g, '') : result;
|
|
412
|
+
// Coerce result to appropriate type based on field type
|
|
413
|
+
if (result !== null && result !== undefined && result !== '') {
|
|
414
|
+
switch (component.type) {
|
|
415
|
+
case 'number':
|
|
416
|
+
// Ensure numeric fields return numbers (but preserve empty string)
|
|
417
|
+
const numResult = Number(result);
|
|
418
|
+
result = isNaN(numResult) ? 0 : numResult;
|
|
419
|
+
result = result === 0 ? "" : result;
|
|
420
|
+
break;
|
|
421
|
+
case 'checkbox':
|
|
422
|
+
case 'switch':
|
|
423
|
+
// Ensure boolean fields return booleans
|
|
424
|
+
result = Boolean(result);
|
|
425
|
+
break;
|
|
426
|
+
case 'textbox':
|
|
427
|
+
case 'textarea':
|
|
428
|
+
// Ensure text fields return strings
|
|
429
|
+
result = String(result);
|
|
430
|
+
break;
|
|
431
|
+
case 'dateRange':
|
|
432
|
+
case 'checkboxGroup':
|
|
433
|
+
case "multiselect":
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
// Other types use result as-is
|
|
437
|
+
result = String(result);
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Apply result if different from current value
|
|
442
|
+
if (result !== null && result !== undefined && result !== formValues[component.id]) {
|
|
443
|
+
formState.values[component.id] = result;
|
|
444
|
+
formState.onChange(component.id, { value: result });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
console.error(`Error evaluating expression for field "${fieldName}":`, error);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
// Update previous values for next comparison
|
|
454
|
+
Object.assign(previousValues, contextValues);
|
|
455
|
+
}
|
|
456
|
+
finally {
|
|
457
|
+
expressionEvaluationInProgressRef.current = false;
|
|
458
|
+
}
|
|
459
|
+
}, [formState?.values, processedSchema, collectAllComponents]);
|
|
373
460
|
/**
|
|
374
461
|
* Render a single field with label and error
|
|
375
462
|
*/
|
|
376
463
|
const renderField = (component) => {
|
|
377
|
-
|
|
378
|
-
if (component.visible === false)
|
|
379
|
-
return null;
|
|
464
|
+
const formValues = formState?.values || {};
|
|
380
465
|
// Skip layout and button components (handled separately)
|
|
381
|
-
if (['panel', 'table', 'tabs', 'card', 'message', 'button', '
|
|
466
|
+
if (['panel', 'table', 'tabs', 'card', 'message', 'button', 'splitButton', 'staticHtml'].includes(component.type)) {
|
|
382
467
|
return null;
|
|
383
468
|
}
|
|
469
|
+
const conditionalReadOnly = ConditionalRuleEngine.isFieldReadOnly(component.conditions, formValues);
|
|
470
|
+
const conditionalDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
471
|
+
const conditionalRequired = ConditionalRuleEngine.isFieldRequired(component.conditions, formValues);
|
|
472
|
+
const conditionalValue = ConditionalRuleEngine.getConditionalValue(component.conditions, formValues);
|
|
473
|
+
const computedReadOnly = component.readOnly || conditionalReadOnly;
|
|
474
|
+
const computedDisabled = component.disabled || conditionalDisabled;
|
|
475
|
+
const computedRequired = component.required || conditionalRequired;
|
|
476
|
+
// Calculate min/max constraints from minMaxRange
|
|
477
|
+
let minMaxConstraints = MinMaxRuleEngine.getConstraints(component.minMaxRange, formValues, component.type);
|
|
478
|
+
// For dateTime, also get date and time constraints separately
|
|
479
|
+
if (component.type === 'dateTime') {
|
|
480
|
+
const dateConstraints = MinMaxRuleEngine.getConstraints(component.minMaxRange, formValues, 'date');
|
|
481
|
+
const timeConstraints = MinMaxRuleEngine.getConstraints(component.minMaxTimeRange, formValues, 'time');
|
|
482
|
+
minMaxConstraints = {
|
|
483
|
+
minValue: undefined,
|
|
484
|
+
maxValue: undefined,
|
|
485
|
+
minDate: dateConstraints.minDate,
|
|
486
|
+
maxDate: dateConstraints.maxDate,
|
|
487
|
+
minTime: timeConstraints.minTime,
|
|
488
|
+
maxTime: timeConstraints.maxTime
|
|
489
|
+
};
|
|
490
|
+
}
|
|
384
491
|
const labelPos = component.labelPosition || 'top';
|
|
385
492
|
const labelWidth = component.labelWidth || 'auto';
|
|
386
|
-
const showLabel = !component.hideLabel && component.label && !['checkbox', 'button', '
|
|
387
|
-
if ((component.type === 'radio' || component.type === '
|
|
493
|
+
const showLabel = !component.hideLabel && component.label && !['checkbox', 'button', 'staticHtml'].includes(component.type);
|
|
494
|
+
if ((component.type === 'radio' || component.type === 'checkboxGroup') && Array.isArray(component.options)) {
|
|
388
495
|
// For radio, store each option's ref with unique key
|
|
389
496
|
setInputRef = (el, idx) => {
|
|
390
497
|
if (el && typeof idx === 'number') {
|
|
@@ -399,51 +506,73 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
399
506
|
}
|
|
400
507
|
};
|
|
401
508
|
}
|
|
402
|
-
const
|
|
403
|
-
|
|
509
|
+
const shouldShowError = !!formState?.errors[component.id];
|
|
510
|
+
const isRequiredError = shouldShowError && formState?.errors[component.id].includes('required');
|
|
511
|
+
const hasError = shouldShowError && !(isRequiredError && !computedRequired);
|
|
512
|
+
// Apply conditional value only when condition transitions from false to true (start of new cycle)
|
|
513
|
+
const conditionIsActive = conditionalValue !== undefined;
|
|
514
|
+
const wasConditionActive = conditionalValueAppliedRef.current[component.id] || false;
|
|
515
|
+
if (formState) {
|
|
516
|
+
if (conditionIsActive && !wasConditionActive) {
|
|
517
|
+
formState.onChange(component.id, { value: conditionalValue });
|
|
518
|
+
conditionalValueAppliedRef.current[component.id] = true;
|
|
519
|
+
}
|
|
520
|
+
else if (!conditionIsActive && wasConditionActive) {
|
|
521
|
+
// Condition just became inactive - clear tracking (ready for next cycle)
|
|
522
|
+
conditionalValueAppliedRef.current[component.id] = false;
|
|
523
|
+
}
|
|
524
|
+
// If condition stays active (wasConditionActive && conditionIsActive), do nothing - let user edit
|
|
525
|
+
}
|
|
526
|
+
return (_jsx(FormField, { name: component.id, children: _jsx("div", { className: "form-group", children: _jsxs("div", { className: `field-wrapper label-${labelPos.toLowerCase()}`, children: [showLabel && (_jsxs("label", { className: "field-label", htmlFor: component.id, style: { width: labelPos.toLowerCase() === 'left' ? `${labelWidth}%` : 'auto' }, children: [_jsx("span", { children: component.label }), computedRequired && _jsx("span", { className: "required-asterisk", children: "*" }), component.tooltip && (_jsx(TooltipComponent, { content: component.tooltip, children: _jsx("span", { className: "e-msg-icon", style: {
|
|
404
527
|
cursor: 'pointer',
|
|
405
528
|
display: 'flex',
|
|
406
529
|
alignItems: 'center',
|
|
407
530
|
justifyContent: 'center',
|
|
408
531
|
flexShrink: 0,
|
|
409
|
-
} }) }))] })), _jsxs("div", { className: "field-input", children: [(component.type === 'radio' || component.type === '
|
|
410
|
-
?
|
|
411
|
-
component,
|
|
412
|
-
value: formState?.values[component.id],
|
|
413
|
-
onChange: (value) => {
|
|
532
|
+
} }) }))] })), _jsxs("div", { className: "field-input", children: [(component.type === 'radio' || component.type === 'checkboxGroup') && Array.isArray(component.options)
|
|
533
|
+
? (_jsx(RenderFormField, { component: { ...component, readOnly: computedReadOnly, disabled: computedDisabled, required: computedRequired }, value: formState?.values[component.id], onChange: (value) => {
|
|
414
534
|
formState?.onChange(component.id, { value });
|
|
415
535
|
if (onChange) {
|
|
416
536
|
onChange({ field: component.label || component.id, value });
|
|
417
537
|
}
|
|
418
|
-
},
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
538
|
+
}, locale: formLocale, onBlur: () => formState?.onBlur(component.id), ref: (el, idx) => setInputRef(el, idx), hasError: hasError }))
|
|
539
|
+
: (_jsx(RenderFormField, { component: {
|
|
540
|
+
...component,
|
|
541
|
+
readOnly: computedReadOnly,
|
|
542
|
+
disabled: computedDisabled,
|
|
543
|
+
required: computedRequired,
|
|
544
|
+
...(minMaxConstraints.minValue !== undefined && { minValue: minMaxConstraints.minValue }),
|
|
545
|
+
...(minMaxConstraints.maxValue !== undefined && { maxValue: minMaxConstraints.maxValue }),
|
|
546
|
+
...(minMaxConstraints.minDate !== undefined && { minDate: minMaxConstraints.minDate }),
|
|
547
|
+
...(minMaxConstraints.maxDate !== undefined && { maxDate: minMaxConstraints.maxDate }),
|
|
548
|
+
...(minMaxConstraints.minTime !== undefined && { minTime: minMaxConstraints.minTime }),
|
|
549
|
+
...(minMaxConstraints.maxTime !== undefined && { maxTime: minMaxConstraints.maxTime })
|
|
550
|
+
}, formState: formState, value: formState?.values[component.id], onChange: (value) => {
|
|
551
|
+
if (component.type !== 'dataGrid' && component.type !== 'signature' && component.type !== 'richTextEditor' && component.type !== 'staticHtml') {
|
|
552
|
+
value = formatDateValues(component, value);
|
|
428
553
|
formState?.onChange(component.id, { value });
|
|
429
554
|
if (onChange) {
|
|
430
555
|
onChange({ field: component.label || component.id, value });
|
|
431
556
|
}
|
|
432
557
|
}
|
|
433
|
-
},
|
|
434
|
-
|
|
435
|
-
formState?.onBlur(component.id); },
|
|
436
|
-
ref: setInputRef,
|
|
437
|
-
hasError,
|
|
438
|
-
}), formState?.errors[component.id] && _jsx("div", { className: "e-error", children: formState.errors[component.id] })] })] }) }) }, component.id));
|
|
558
|
+
}, locale: formLocale, onBlur: () => { if (component.type !== 'dataGrid' && component.type !== 'richTextEditor' && component.type !== 'staticHtml')
|
|
559
|
+
formState?.onBlur(component.id); }, ref: setInputRef, hasError: hasError })), hasError && _jsx("div", { className: "e-error", children: formState.errors[component.id] })] }), component.description && _jsx("div", { className: 'field-description', children: component.description })] }) }) }, component.id));
|
|
439
560
|
};
|
|
440
561
|
/**
|
|
441
562
|
* Render component (including layouts)
|
|
442
563
|
*/
|
|
443
564
|
const renderComponent = (component) => {
|
|
444
|
-
|
|
445
|
-
|
|
565
|
+
const formValues = formState?.values || {};
|
|
566
|
+
const isVisible = component.visible === false
|
|
567
|
+
? false
|
|
568
|
+
: ConditionalRuleEngine.isFieldVisible(component.conditions, formValues);
|
|
569
|
+
const isHidden = ConditionalRuleEngine.isFieldHidden(component.conditions, formValues);
|
|
570
|
+
if (!isVisible || isHidden) {
|
|
571
|
+
if (formState && formState.values[component.id] !== undefined && formState.values[component.id] !== null) {
|
|
572
|
+
formState.onChange(component.id, { value: null });
|
|
573
|
+
}
|
|
446
574
|
return null;
|
|
575
|
+
}
|
|
447
576
|
switch (component.type) {
|
|
448
577
|
case 'panel':
|
|
449
578
|
return (_jsxs("fieldset", { className: `form-panel-renderer ${component.cssClass}`, children: [(component.label && !component.hideLabel) && _jsx("legend", { className: `${!component.hideBorders ? 'panel-legend' : ''}`, children: component.label }), _jsx("div", { className: "panel-content", children: component.children?.map(child => renderComponent(child)) })] }, component.id));
|
|
@@ -453,13 +582,15 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
453
582
|
const defaultColSpan = Math.floor(12 / tableCols);
|
|
454
583
|
const columnWidthsProp = component.columnWidths;
|
|
455
584
|
const splittedWidths = columnWidthsProp ? splitWidths(columnWidthsProp.widths) : [];
|
|
456
|
-
return (_jsx("div", { className: `form-table-renderer ${component.hideBorders ? '' : 'table-border'} ${component.cssClass}`, children: Array.from({ length: tableRows }).map((_, rowIndex) => (_jsx("div", { className: "table-row", children: Array.from({ length: tableCols }).map((_, colIndex) => {
|
|
585
|
+
return (_jsx("div", { className: `form-table-renderer ${component.hideBorders ? '' : 'table-border'} ${component.cssClass}`, children: Array.from({ length: tableRows }).map((_, rowIndex) => (_jsx("div", { className: "e-form-table-row", style: component.hideBorders ? { display: 'flex', gap: '1.5rem' } : {}, children: Array.from({ length: tableCols }).map((_, colIndex) => {
|
|
457
586
|
const cellComponents = component.tableCells?.[rowIndex]?.[colIndex] || [];
|
|
458
587
|
const cellSpan = defaultColSpan;
|
|
459
588
|
const colWidthValue = splittedWidths[colIndex];
|
|
460
|
-
const
|
|
589
|
+
const isSingleColumn = tableCols === 1;
|
|
590
|
+
const cellStyle = isSingleColumn ? { width: '100%' } : (colWidthValue ? { width: `${colWidthValue}%` } : {});
|
|
461
591
|
const allNullWidths = !columnWidthsProp || (Array.isArray(columnWidthsProp.widths) && columnWidthsProp.widths.every((w) => w === null));
|
|
462
|
-
|
|
592
|
+
const isFirstRow = rowIndex === 0;
|
|
593
|
+
return (_jsx("div", { className: `e-form-table-cell ${colWidthValue ? '' : allNullWidths ? `col-${cellSpan}` : ''}`, style: component.hideBorders ? { ...cellStyle, padding: isFirstRow ? '0' : '12px 0 0 0' } : cellStyle, children: cellComponents.map(comp => renderComponent(comp)) }, `cell-${rowIndex}-${colIndex}`));
|
|
463
594
|
}) }, `row-${rowIndex}`))) }, component.id));
|
|
464
595
|
case 'tabs':
|
|
465
596
|
return (_jsx("div", { className: `form-tabs-renderer ${component.cssClass || ''}`, children: _jsx(CustomTabComponent, { tabs: component.tabItems?.map(tab => ({
|
|
@@ -468,15 +599,13 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
468
599
|
})) || [], className: component.cssClass }) }, component.id));
|
|
469
600
|
case 'card':
|
|
470
601
|
return (_jsxs("fieldset", { className: `e-card form-card-renderer ${component.cssClass}`, style: { padding: 0 }, children: [_jsx("div", { className: "e-card-header", children: _jsxs("div", { className: "e-card-header-caption", children: [_jsx("div", { className: "e-card-title", children: component.cardTitle || component.label || 'Card Title' }), component.cardSubtitle && (_jsx("div", { className: "e-card-sub-title", children: component.cardSubtitle }))] }) }), _jsx("div", { className: "e-card-content card-content", children: component.children?.map(child => renderComponent(child)) })] }, component.id));
|
|
471
|
-
case '
|
|
602
|
+
case 'staticHtml':
|
|
472
603
|
return (_jsx(StaticHtmlRenderer, { id: component.id, content: component.defaultValue || '<p>HTML content</p>', enableHtmlSanitizer: component.enableHtmlSanitizer }, component.id));
|
|
473
604
|
case 'message':
|
|
474
|
-
return (_jsx("div", { className: "form-group", children:
|
|
475
|
-
component,
|
|
476
|
-
value: undefined,
|
|
477
|
-
onChange: () => { },
|
|
478
|
-
}) }, component.id));
|
|
605
|
+
return (_jsx("div", { className: "form-group", children: _jsx(RenderFormField, { component: component, locale: formLocale, value: undefined, onChange: () => { } }) }, component.id));
|
|
479
606
|
case 'button':
|
|
607
|
+
const buttonlDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
608
|
+
const buttonComputed = component.disabled || buttonlDisabled;
|
|
480
609
|
const handleButtonClick = (event) => {
|
|
481
610
|
if (onButtonClick) {
|
|
482
611
|
onButtonClick({
|
|
@@ -485,21 +614,17 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
485
614
|
});
|
|
486
615
|
}
|
|
487
616
|
};
|
|
488
|
-
return (_jsx("div", { className: "form-group", children:
|
|
489
|
-
component,
|
|
490
|
-
value: undefined,
|
|
491
|
-
onChange: () => { },
|
|
492
|
-
onClick: (e) => { handleButtonClick(e); },
|
|
493
|
-
ref: (el, idx) => {
|
|
617
|
+
return (_jsx("div", { className: "form-group", children: _jsx(RenderFormField, { component: { ...component, disabled: buttonComputed }, value: undefined, onChange: () => { }, locale: formLocale, onClick: (e) => { handleButtonClick(e); }, ref: (el, idx) => {
|
|
494
618
|
if (idx !== undefined) {
|
|
495
619
|
inputRefs.current[`${component.id}_${idx}`] = el;
|
|
496
620
|
}
|
|
497
621
|
else {
|
|
498
622
|
inputRefs.current[component.id] = el;
|
|
499
623
|
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
624
|
+
} }) }, component.id));
|
|
625
|
+
case 'splitButton':
|
|
626
|
+
const splitButtonDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
627
|
+
const computedDisabled = component.disabled || splitButtonDisabled;
|
|
503
628
|
setInputRef = (el) => {
|
|
504
629
|
if (el) {
|
|
505
630
|
inputRefs.current[component.id] = el;
|
|
@@ -514,13 +639,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
514
639
|
});
|
|
515
640
|
}
|
|
516
641
|
};
|
|
517
|
-
return (_jsx("div", { className: "form-group", children:
|
|
518
|
-
component,
|
|
519
|
-
value: undefined,
|
|
520
|
-
onChange: () => { },
|
|
521
|
-
onClick: (e) => { handleSplitButtonEvent(e); },
|
|
522
|
-
ref: setInputRef
|
|
523
|
-
}) }, component.id));
|
|
642
|
+
return (_jsx("div", { className: "form-group", children: _jsx(RenderFormField, { component: { ...component, disabled: computedDisabled }, value: undefined, locale: formLocale, onChange: () => { }, onClick: (e) => { handleSplitButtonEvent(e); }, ref: setInputRef }) }, component.id));
|
|
524
643
|
default:
|
|
525
644
|
return renderField(component);
|
|
526
645
|
}
|
|
@@ -546,15 +665,36 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
546
665
|
},
|
|
547
666
|
}), []);
|
|
548
667
|
return (_jsx("div", { ...restProps, className: `form-renderer-container ${className}`, children: _jsx(Form, { rules: validationRules, initialValues: initialValues, onFormStateChange: (state) => setFormState(state), onSubmit: (data) => {
|
|
549
|
-
const isValid = formState?.errors ? Object.keys(formState.errors).length === 0 : false;
|
|
668
|
+
const isValid = (formState?.errors ? Object.keys(formState.errors).length === 0 : false);
|
|
550
669
|
const transformedData = {};
|
|
670
|
+
const formValues = formState?.values || {};
|
|
551
671
|
// Exclude non-data components (buttons, messages, and layout-only components)
|
|
552
|
-
const excludedTypes = new Set(['button', 'message', 'panel', 'table', 'tabs'
|
|
672
|
+
const excludedTypes = new Set(['button', 'message', 'panel', 'table', 'tabs']);
|
|
553
673
|
const allComps = collectAllComponents(processedSchema.components || []);
|
|
554
674
|
const excludedIds = new Set(allComps.filter(c => excludedTypes.has(c.type)).map(c => c.id));
|
|
675
|
+
const hiddenFieldIds = new Set(allComps.filter(c => {
|
|
676
|
+
if (c.visible === false)
|
|
677
|
+
return true;
|
|
678
|
+
if (c.conditions?.visibleWhen) {
|
|
679
|
+
if (!ConditionalRuleEngine.isFieldVisible(c.conditions, formValues))
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
if (c.conditions?.hideWhen) {
|
|
683
|
+
if (ConditionalRuleEngine.isFieldHidden(c.conditions, formValues))
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
return false;
|
|
687
|
+
}).map(c => c.id));
|
|
688
|
+
const disabledFieldIds = new Set(allComps.filter(c => {
|
|
689
|
+
if (c.disabled)
|
|
690
|
+
return true;
|
|
691
|
+
if (c.conditions?.disabledWhen) {
|
|
692
|
+
return ConditionalRuleEngine.isFieldDisabled(c.conditions, formValues);
|
|
693
|
+
}
|
|
694
|
+
return false;
|
|
695
|
+
}).map(c => c.id));
|
|
555
696
|
Object.entries(data).forEach(([id, val]) => {
|
|
556
|
-
|
|
557
|
-
if (excludedIds.has(id))
|
|
697
|
+
if (excludedIds.has(id) || hiddenFieldIds.has(id) || disabledFieldIds.has(id))
|
|
558
698
|
return;
|
|
559
699
|
const label = idToLabel[id] || id;
|
|
560
700
|
transformedData[label] = val;
|