@syncfusion/ej2-react-form-renderer 0.1.2-beta → 34.2.3
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 +88 -105
- package/diConfig.json +85 -0
- package/dist/ej2-react-form-renderer.min.js +10 -0
- package/dist/ej2-react-form-renderer.umd.min.js +11 -0
- package/dist/ej2-react-form-renderer.umd.min.js.map +1 -0
- package/dist/es6/ej2-react-form-renderer.es2015.js +37 -0
- package/dist/es6/ej2-react-form-renderer.es2015.js.map +1 -0
- package/form-renderer.d.ts +4 -0
- package/form-renderer.js +4 -0
- package/index.d.ts +2 -4
- package/index.js +2 -4
- package/license +6 -13
- package/package.json +19 -38
- package/src/form-renderer/formrenderer.component.d.ts +34 -0
- package/src/form-renderer/formrenderer.component.js +49 -0
- package/src/form-renderer/index.d.ts +1 -0
- package/src/form-renderer/index.js +1 -0
- package/src/global.js +1 -0
- package/src/index.d.ts +2 -6
- package/src/index.js +2 -6
- package/styles/bds-lite.css +1 -0
- package/styles/bds-lite.scss +1 -0
- package/styles/bds.css +1 -0
- package/styles/bds.scss +1 -0
- package/styles/bootstrap-dark-lite.css +1 -0
- package/styles/bootstrap-dark-lite.scss +1 -0
- package/styles/bootstrap-dark.css +1 -26
- package/styles/bootstrap-dark.scss +1 -0
- package/styles/bootstrap-lite.css +1 -0
- package/styles/bootstrap-lite.scss +1 -0
- package/styles/bootstrap.css +1 -26
- package/styles/bootstrap.scss +1 -0
- package/styles/bootstrap4-lite.css +1 -0
- package/styles/bootstrap4-lite.scss +1 -0
- package/styles/bootstrap4.css +1 -26
- package/styles/bootstrap4.scss +1 -0
- package/styles/bootstrap5-dark-lite.css +1 -0
- package/styles/bootstrap5-dark-lite.scss +1 -0
- package/styles/bootstrap5-dark.css +1 -26
- package/styles/bootstrap5-dark.scss +1 -0
- package/styles/bootstrap5-lite.css +1 -0
- package/styles/bootstrap5-lite.scss +1 -0
- package/styles/bootstrap5.3-lite.css +1 -0
- package/styles/bootstrap5.3-lite.scss +1 -0
- package/styles/bootstrap5.3.css +1 -26
- package/styles/bootstrap5.3.scss +1 -0
- package/styles/bootstrap5.css +1 -26
- package/styles/bootstrap5.scss +1 -0
- package/styles/fabric-dark-lite.css +1 -0
- package/styles/fabric-dark-lite.scss +1 -0
- package/styles/fabric-dark.css +1 -0
- package/styles/fabric-dark.scss +1 -0
- package/styles/fabric-lite.css +1 -0
- package/styles/fabric-lite.scss +1 -0
- package/styles/fabric.css +1 -0
- package/styles/fabric.scss +1 -0
- package/styles/fluent-dark-lite.css +1 -0
- package/styles/fluent-dark-lite.scss +1 -0
- package/styles/fluent-dark.css +1 -26
- package/styles/fluent-dark.scss +1 -0
- package/styles/fluent-lite.css +1 -0
- package/styles/fluent-lite.scss +1 -0
- package/styles/fluent.css +1 -26
- package/styles/fluent.scss +1 -0
- package/styles/fluent2-lite.css +1 -0
- package/styles/fluent2-lite.scss +1 -0
- package/styles/fluent2.css +1 -26
- package/styles/fluent2.scss +1 -0
- package/styles/form-renderer/bds.css +1 -0
- package/styles/form-renderer/bds.scss +1 -0
- package/styles/form-renderer/bootstrap-dark.css +1 -0
- package/styles/form-renderer/bootstrap-dark.scss +1 -0
- package/styles/form-renderer/bootstrap.css +1 -0
- package/styles/form-renderer/bootstrap.scss +1 -0
- package/styles/form-renderer/bootstrap4.css +1 -0
- package/styles/form-renderer/bootstrap4.scss +1 -0
- package/styles/form-renderer/bootstrap5-dark.css +1 -0
- package/styles/form-renderer/bootstrap5-dark.scss +1 -0
- package/styles/form-renderer/bootstrap5.3.css +1 -0
- package/styles/form-renderer/bootstrap5.3.scss +1 -0
- package/styles/form-renderer/bootstrap5.css +1 -0
- package/styles/form-renderer/bootstrap5.scss +1 -0
- package/styles/form-renderer/fabric-dark.css +1 -0
- package/styles/form-renderer/fabric-dark.scss +1 -0
- package/styles/form-renderer/fabric.css +1 -0
- package/styles/form-renderer/fabric.scss +1 -0
- package/styles/form-renderer/fluent-dark.css +1 -0
- package/styles/form-renderer/fluent-dark.scss +1 -0
- package/styles/form-renderer/fluent.css +1 -0
- package/styles/form-renderer/fluent.scss +1 -0
- package/styles/form-renderer/fluent2.css +1 -0
- package/styles/form-renderer/fluent2.scss +1 -0
- package/styles/form-renderer/highcontrast-light.css +1 -0
- package/styles/form-renderer/highcontrast-light.scss +1 -0
- package/styles/form-renderer/highcontrast.css +1 -0
- package/styles/form-renderer/highcontrast.scss +1 -0
- package/styles/form-renderer/material-dark.css +1 -0
- package/styles/form-renderer/material-dark.scss +1 -0
- package/styles/form-renderer/material.css +1 -0
- package/styles/form-renderer/material.scss +1 -0
- package/styles/form-renderer/material3-dark.css +1 -0
- package/styles/form-renderer/material3-dark.scss +1 -0
- package/styles/form-renderer/material3.css +1 -0
- package/styles/form-renderer/material3.scss +1 -0
- package/styles/form-renderer/tailwind-dark.css +1 -0
- package/styles/form-renderer/tailwind-dark.scss +1 -0
- package/styles/form-renderer/tailwind.css +1 -0
- package/styles/form-renderer/tailwind.scss +1 -0
- package/styles/form-renderer/tailwind3.css +1 -0
- package/styles/form-renderer/tailwind3.scss +1 -0
- package/styles/highcontrast-light-lite.css +1 -0
- package/styles/highcontrast-light-lite.scss +1 -0
- package/styles/highcontrast-light.css +1 -0
- package/styles/highcontrast-light.scss +1 -0
- package/styles/highcontrast-lite.css +1 -0
- package/styles/highcontrast-lite.scss +1 -0
- package/styles/highcontrast.css +1 -0
- package/styles/highcontrast.scss +1 -0
- package/styles/material-dark-lite.css +1 -0
- package/styles/material-dark-lite.scss +1 -0
- package/styles/material-dark.css +1 -26
- package/styles/material-dark.scss +1 -0
- package/styles/material-lite.css +1 -0
- package/styles/material-lite.scss +1 -0
- package/styles/material.css +1 -26
- package/styles/material.scss +1 -0
- package/styles/material3-dark-lite.css +1 -0
- package/styles/material3-dark-lite.scss +1 -0
- package/styles/material3-dark.css +1 -26
- package/styles/material3-dark.scss +1 -0
- package/styles/material3-lite.css +1 -0
- package/styles/material3-lite.scss +1 -0
- package/styles/material3.css +1 -26
- package/styles/material3.scss +1 -0
- package/styles/tailwind-dark-lite.css +1 -0
- package/styles/tailwind-dark-lite.scss +1 -0
- package/styles/tailwind-dark.css +1 -26
- package/styles/tailwind-dark.scss +1 -0
- package/styles/tailwind-lite.css +1 -0
- package/styles/tailwind-lite.scss +1 -0
- package/styles/tailwind.css +1 -26
- package/styles/tailwind.scss +1 -0
- package/styles/tailwind3-lite.css +1 -0
- package/styles/tailwind3-lite.scss +1 -0
- package/styles/tailwind3.css +1 -26
- package/styles/tailwind3.scss +1 -0
- package/src/CheckboxGroup.d.ts +0 -12
- package/src/CheckboxGroup.js +0 -49
- package/src/ConditionalRuleEngine.d.ts +0 -295
- package/src/ConditionalRuleEngine.js +0 -566
- package/src/CustomTabComponent.d.ts +0 -13
- package/src/CustomTabComponent.js +0 -35
- package/src/DataGridComponent.d.ts +0 -28
- package/src/DataGridComponent.js +0 -147
- package/src/FormRenderer.d.ts +0 -84
- package/src/FormRenderer.js +0 -707
- package/src/FormValidator.d.ts +0 -479
- package/src/FormValidator.js +0 -579
- package/src/MinMaxRuleEngine.d.ts +0 -95
- package/src/MinMaxRuleEngine.js +0 -361
- package/src/StaticHtmlRenderer.d.ts +0 -8
- package/src/StaticHtmlRenderer.js +0 -21
- package/src/expressions/DependencyTracker.d.ts +0 -30
- package/src/expressions/DependencyTracker.js +0 -128
- package/src/expressions/ExpressionEngine.d.ts +0 -81
- package/src/expressions/ExpressionEngine.js +0 -503
- package/src/expressions/ExpressionParser.d.ts +0 -39
- package/src/expressions/ExpressionParser.js +0 -270
- package/src/json-converter.d.ts +0 -171
- package/src/json-converter.js +0 -1209
- package/src/parser.d.ts +0 -99
- package/src/parser.js +0 -753
- package/src/renderFormField.d.ts +0 -21
- package/src/renderFormField.js +0 -226
- package/src/types/FormSchema.d.ts +0 -211
- package/src/types/FormSchema.js +0 -1
- package/src/utils.js +0 -408
- package/styles/bootstrap5.3-dark.css +0 -26
- package/styles/fluent2-dark.css +0 -26
- package/styles/form-renderer.base.css +0 -499
- package/styles/tailwind3-dark.css +0 -10
package/src/FormRenderer.js
DELETED
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Form Renderer Component
|
|
4
|
-
* Renders JSON-driven forms with React FormValidator
|
|
5
|
-
*/
|
|
6
|
-
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
7
|
-
import { Form, FormField } from './FormValidator';
|
|
8
|
-
import { enableRipple } from '@syncfusion/ej2-base';
|
|
9
|
-
import { RenderFormField } from './renderFormField';
|
|
10
|
-
import { CustomTabComponent } from './CustomTabComponent';
|
|
11
|
-
import { mergeSchema } from './json-converter';
|
|
12
|
-
import { UniversalJsonParser } from './parser';
|
|
13
|
-
import { TooltipComponent } from '@syncfusion/ej2-react-popups';
|
|
14
|
-
import { VALIDATION_REGEX } from './FormValidator';
|
|
15
|
-
import StaticHtmlRenderer from './StaticHtmlRenderer';
|
|
16
|
-
import { formatDateValues, splitWidths } from './utils';
|
|
17
|
-
import { ConditionalRuleEngine } from './ConditionalRuleEngine';
|
|
18
|
-
import { ExpressionEngine } from './expressions/ExpressionEngine';
|
|
19
|
-
import { MinMaxRuleEngine } from './MinMaxRuleEngine';
|
|
20
|
-
enableRipple(true);
|
|
21
|
-
/**
|
|
22
|
-
* Form layout options (only applies when UI schema is NOT provided)
|
|
23
|
-
*/
|
|
24
|
-
export const FormLayout = {
|
|
25
|
-
SingleColumn: 'single',
|
|
26
|
-
TwoColumn: 'twocolumn',
|
|
27
|
-
ThreeColumn: 'threecolumn',
|
|
28
|
-
FourColumn: 'fourcolumn'
|
|
29
|
-
};
|
|
30
|
-
// ForwardRef to expose public methods
|
|
31
|
-
export const FormRenderer = React.forwardRef(({ schema, locale, onSubmit, onChange, onButtonClick, className = '', layout = FormLayout.SingleColumn, created, ...restProps }, ref) => {
|
|
32
|
-
const processedSchema = React.useMemo(() => {
|
|
33
|
-
try {
|
|
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: [] };
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const isUnifiedSchema = parsedInput && typeof parsedInput === 'object' &&
|
|
48
|
-
parsedInput.properties && Array.isArray(parsedInput.layout);
|
|
49
|
-
if (isUnifiedSchema) {
|
|
50
|
-
return mergeSchema(parsedInput);
|
|
51
|
-
}
|
|
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: [] };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
console.error('FormRenderer schema processing error:', error);
|
|
66
|
-
return { version: '1.0.0', components: [] };
|
|
67
|
-
}
|
|
68
|
-
}, [schema]);
|
|
69
|
-
const [formState, setFormState] = useState(undefined);
|
|
70
|
-
const isInitializedRef = useRef(false);
|
|
71
|
-
const formLocale = locale || 'en-US';
|
|
72
|
-
// Attach ref to the input element for getComponent
|
|
73
|
-
let setInputRef;
|
|
74
|
-
// Store input component refs by name/id
|
|
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());
|
|
84
|
-
// Check if UI schema has layout components (table, tabs, or panel)
|
|
85
|
-
const hasLayoutComponents = React.useMemo(() => {
|
|
86
|
-
if (!processedSchema.components)
|
|
87
|
-
return false;
|
|
88
|
-
const checkForLayoutTypes = (components) => {
|
|
89
|
-
for (const comp of components) {
|
|
90
|
-
if (['table', 'tabs', 'panel', 'card'].includes(comp.type)) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
if (comp.children && checkForLayoutTypes(comp.children)) {
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
if (comp.tableCells) {
|
|
97
|
-
for (const row of comp.tableCells) {
|
|
98
|
-
for (const cell of row) {
|
|
99
|
-
if (checkForLayoutTypes(cell))
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (comp.tabItems) {
|
|
105
|
-
for (const tab of comp.tabItems) {
|
|
106
|
-
if (tab.content && checkForLayoutTypes(tab.content))
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
|
-
};
|
|
113
|
-
return checkForLayoutTypes(processedSchema.components);
|
|
114
|
-
}, [processedSchema]);
|
|
115
|
-
// Determine layout class (only apply if UI schema does NOT have table/tabs/panels)
|
|
116
|
-
const layoutClass = React.useMemo(() => {
|
|
117
|
-
if (hasLayoutComponents)
|
|
118
|
-
return ''; // Ignore layout if UI schema has table/tabs/panels
|
|
119
|
-
// Map enum to CSS class names
|
|
120
|
-
switch (layout) {
|
|
121
|
-
case FormLayout.TwoColumn:
|
|
122
|
-
return 'twolayout';
|
|
123
|
-
case FormLayout.ThreeColumn:
|
|
124
|
-
return 'threelayout';
|
|
125
|
-
case FormLayout.FourColumn:
|
|
126
|
-
return 'fourlayout';
|
|
127
|
-
case FormLayout.SingleColumn:
|
|
128
|
-
default:
|
|
129
|
-
return ''; // Default single column (no class needed)
|
|
130
|
-
}
|
|
131
|
-
}, [layout, hasLayoutComponents]);
|
|
132
|
-
/**
|
|
133
|
-
* Recursively collect all components including nested ones
|
|
134
|
-
*/
|
|
135
|
-
const collectAllComponents = useCallback((components) => {
|
|
136
|
-
const allComponents = [];
|
|
137
|
-
const traverse = (comps) => {
|
|
138
|
-
comps.forEach(comp => {
|
|
139
|
-
allComponents.push(comp);
|
|
140
|
-
// Handle nested components in panels
|
|
141
|
-
if (comp.children && comp.children.length > 0) {
|
|
142
|
-
traverse(comp.children);
|
|
143
|
-
}
|
|
144
|
-
// Handle components in table cells
|
|
145
|
-
if (comp.tableCells) {
|
|
146
|
-
comp.tableCells.forEach(row => {
|
|
147
|
-
row.forEach(cell => {
|
|
148
|
-
traverse(cell);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
// Handle components in tabs
|
|
153
|
-
if (comp.tabItems) {
|
|
154
|
-
comp.tabItems.forEach(tab => {
|
|
155
|
-
if (tab.content) {
|
|
156
|
-
traverse(tab.content);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
traverse(components);
|
|
163
|
-
return allComponents;
|
|
164
|
-
}, []);
|
|
165
|
-
const idToLabel = React.useMemo(() => {
|
|
166
|
-
const map = {};
|
|
167
|
-
const traverse = (comps) => {
|
|
168
|
-
comps.forEach(comp => {
|
|
169
|
-
if (comp.label && !['panel', 'table', 'tabs', 'card', 'button', 'message', 'splitButton'].includes(comp.type)) {
|
|
170
|
-
map[comp.id] = comp.label;
|
|
171
|
-
}
|
|
172
|
-
if (comp.children)
|
|
173
|
-
traverse(comp.children);
|
|
174
|
-
if (comp.tableCells) {
|
|
175
|
-
comp.tableCells.forEach(row => {
|
|
176
|
-
row.forEach(cell => traverse(cell));
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
if (comp.tabItems) {
|
|
180
|
-
comp.tabItems.forEach(tab => {
|
|
181
|
-
if (tab.content)
|
|
182
|
-
traverse(tab.content);
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
traverse(processedSchema.components);
|
|
188
|
-
return map;
|
|
189
|
-
}, [processedSchema]);
|
|
190
|
-
/**
|
|
191
|
-
* Build EJ2 FormValidator rules from schema
|
|
192
|
-
*/
|
|
193
|
-
const buildValidationRules = useCallback(() => {
|
|
194
|
-
const rules = {};
|
|
195
|
-
const formValues = formState?.values || {};
|
|
196
|
-
const collectRules = (components) => {
|
|
197
|
-
components.forEach((component) => {
|
|
198
|
-
if (component) {
|
|
199
|
-
const componentRules = {};
|
|
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) {
|
|
207
|
-
componentRules.required = [true, component.validationMessage || `${component.label} is required`];
|
|
208
|
-
}
|
|
209
|
-
if (component.minLength && !finalDisabled && !finalReadOnly) {
|
|
210
|
-
componentRules.minLength = [component.minLength, `Minimum ${component.minLength} characters required`];
|
|
211
|
-
}
|
|
212
|
-
if (component.maxLength && !finalDisabled && !finalReadOnly) {
|
|
213
|
-
componentRules.maxLength = [component.maxLength, `Maximum ${component.maxLength} characters allowed`];
|
|
214
|
-
}
|
|
215
|
-
if (component?.type === 'textbox' && component?.textboxType && !component?.regex && !componentRules.regex && !finalDisabled && !finalReadOnly) {
|
|
216
|
-
if (component.textboxType === 'email') {
|
|
217
|
-
componentRules.regex = [VALIDATION_REGEX['EMAIL'], component?.validationMessage || 'Invalid format'];
|
|
218
|
-
}
|
|
219
|
-
else if (component.textboxType === 'url') {
|
|
220
|
-
componentRules.regex = [VALIDATION_REGEX['URL'], component?.validationMessage || 'Invalid format'];
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (component.regex && !finalDisabled && !finalReadOnly) {
|
|
224
|
-
componentRules.regex = [component.regex, component.validationMessage || 'Invalid format'];
|
|
225
|
-
}
|
|
226
|
-
if (Object.keys(componentRules).length > 0) {
|
|
227
|
-
rules[component.id] = componentRules;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// Recursively collect rules from nested components
|
|
231
|
-
if (component.children) {
|
|
232
|
-
collectRules(component.children);
|
|
233
|
-
}
|
|
234
|
-
if (component.tableCells) {
|
|
235
|
-
component.tableCells.forEach(row => {
|
|
236
|
-
row.forEach(cell => collectRules(cell));
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
if (component.tabItems) {
|
|
240
|
-
component.tabItems.forEach(tab => {
|
|
241
|
-
if (tab.content)
|
|
242
|
-
collectRules(tab.content);
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
};
|
|
247
|
-
collectRules(processedSchema.components);
|
|
248
|
-
return rules;
|
|
249
|
-
}, [processedSchema, formState?.values]);
|
|
250
|
-
const validationRules = buildValidationRules();
|
|
251
|
-
/**
|
|
252
|
-
* Build initial form values from component defaultValues
|
|
253
|
-
*/
|
|
254
|
-
const initialValues = React.useMemo(() => {
|
|
255
|
-
const values = {};
|
|
256
|
-
const allComponents = collectAllComponents(processedSchema.components);
|
|
257
|
-
const componentIds = new Set();
|
|
258
|
-
allComponents.forEach(comp => {
|
|
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') {
|
|
261
|
-
componentIds.add(comp.id);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (comp.defaultValue !== undefined) {
|
|
265
|
-
values[comp.id] = comp.defaultValue;
|
|
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
|
-
}
|
|
276
|
-
componentIds.add(comp.id);
|
|
277
|
-
});
|
|
278
|
-
if (formState) {
|
|
279
|
-
Object.keys(formState.values).filter(key => !componentIds.has(key)).forEach(key => {
|
|
280
|
-
delete formState.values[key];
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
return values;
|
|
284
|
-
}, [processedSchema, collectAllComponents, schema]);
|
|
285
|
-
// Update the useEffect that sets default values
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
if (formState && !isInitializedRef.current) {
|
|
288
|
-
isInitializedRef.current = true;
|
|
289
|
-
}
|
|
290
|
-
if (formState && initialValues) {
|
|
291
|
-
Object.keys(formState.values).filter(key => !(key in initialValues));
|
|
292
|
-
Object.entries(initialValues).forEach(([id, value]) => {
|
|
293
|
-
if (!formState.values[id]) {
|
|
294
|
-
formState.values[id] = value;
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
}
|
|
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]);
|
|
460
|
-
/**
|
|
461
|
-
* Render a single field with label and error
|
|
462
|
-
*/
|
|
463
|
-
const renderField = (component) => {
|
|
464
|
-
const formValues = formState?.values || {};
|
|
465
|
-
// Skip layout and button components (handled separately)
|
|
466
|
-
if (['panel', 'table', 'tabs', 'card', 'message', 'button', 'splitButton', 'staticHtml'].includes(component.type)) {
|
|
467
|
-
return null;
|
|
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
|
-
}
|
|
491
|
-
const labelPos = component.labelPosition || 'top';
|
|
492
|
-
const labelWidth = component.labelWidth || 'auto';
|
|
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)) {
|
|
495
|
-
// For radio, store each option's ref with unique key
|
|
496
|
-
setInputRef = (el, idx) => {
|
|
497
|
-
if (el && typeof idx === 'number') {
|
|
498
|
-
inputRefs.current[`${component.id}_${idx}`] = el;
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
setInputRef = (el) => {
|
|
504
|
-
if (el) {
|
|
505
|
-
inputRefs.current[component.id] = el;
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
}
|
|
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: {
|
|
527
|
-
cursor: 'pointer',
|
|
528
|
-
display: 'flex',
|
|
529
|
-
alignItems: 'center',
|
|
530
|
-
justifyContent: 'center',
|
|
531
|
-
flexShrink: 0,
|
|
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) => {
|
|
534
|
-
formState?.onChange(component.id, { value });
|
|
535
|
-
if (onChange) {
|
|
536
|
-
onChange({ field: component.label || component.id, value });
|
|
537
|
-
}
|
|
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);
|
|
553
|
-
formState?.onChange(component.id, { value });
|
|
554
|
-
if (onChange) {
|
|
555
|
-
onChange({ field: component.label || component.id, value });
|
|
556
|
-
}
|
|
557
|
-
}
|
|
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));
|
|
560
|
-
};
|
|
561
|
-
/**
|
|
562
|
-
* Render component (including layouts)
|
|
563
|
-
*/
|
|
564
|
-
const renderComponent = (component) => {
|
|
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
|
-
}
|
|
574
|
-
return null;
|
|
575
|
-
}
|
|
576
|
-
switch (component.type) {
|
|
577
|
-
case 'panel':
|
|
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));
|
|
579
|
-
case 'table':
|
|
580
|
-
const tableRows = component.rows || (component.tableCells ? component.tableCells.length : 2);
|
|
581
|
-
const tableCols = component.columns || (component.tableCells && component.tableCells[0] ? component.tableCells[0].length : 2);
|
|
582
|
-
const defaultColSpan = Math.floor(12 / tableCols);
|
|
583
|
-
const columnWidthsProp = component.columnWidths;
|
|
584
|
-
const splittedWidths = columnWidthsProp ? splitWidths(columnWidthsProp.widths) : [];
|
|
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) => {
|
|
586
|
-
const cellComponents = component.tableCells?.[rowIndex]?.[colIndex] || [];
|
|
587
|
-
const cellSpan = defaultColSpan;
|
|
588
|
-
const colWidthValue = splittedWidths[colIndex];
|
|
589
|
-
const isSingleColumn = tableCols === 1;
|
|
590
|
-
const cellStyle = isSingleColumn ? { width: '100%' } : (colWidthValue ? { width: `${colWidthValue}%` } : {});
|
|
591
|
-
const allNullWidths = !columnWidthsProp || (Array.isArray(columnWidthsProp.widths) && columnWidthsProp.widths.every((w) => w === null));
|
|
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}`));
|
|
594
|
-
}) }, `row-${rowIndex}`))) }, component.id));
|
|
595
|
-
case 'tabs':
|
|
596
|
-
return (_jsx("div", { className: `form-tabs-renderer ${component.cssClass || ''}`, children: _jsx(CustomTabComponent, { tabs: component.tabItems?.map(tab => ({
|
|
597
|
-
header: tab.header,
|
|
598
|
-
content: (_jsx("div", { className: "tab-content-renderer", children: tab.content?.map(comp => renderComponent(comp)) }))
|
|
599
|
-
})) || [], className: component.cssClass }) }, component.id));
|
|
600
|
-
case 'card':
|
|
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));
|
|
602
|
-
case 'staticHtml':
|
|
603
|
-
return (_jsx(StaticHtmlRenderer, { id: component.id, content: component.defaultValue || '<p>HTML content</p>', enableHtmlSanitizer: component.enableHtmlSanitizer }, component.id));
|
|
604
|
-
case 'message':
|
|
605
|
-
return (_jsx("div", { className: "form-group", children: _jsx(RenderFormField, { component: component, locale: formLocale, value: undefined, onChange: () => { } }) }, component.id));
|
|
606
|
-
case 'button':
|
|
607
|
-
const buttonlDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
608
|
-
const buttonComputed = component.disabled || buttonlDisabled;
|
|
609
|
-
const handleButtonClick = (event) => {
|
|
610
|
-
if (onButtonClick) {
|
|
611
|
-
onButtonClick({
|
|
612
|
-
buttonId: component.id,
|
|
613
|
-
buttonLabel: component.buttonsGroups ? event.target?.ej2_instances[0].content : component.label || '', event
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
};
|
|
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) => {
|
|
618
|
-
if (idx !== undefined) {
|
|
619
|
-
inputRefs.current[`${component.id}_${idx}`] = el;
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
inputRefs.current[component.id] = el;
|
|
623
|
-
}
|
|
624
|
-
} }) }, component.id));
|
|
625
|
-
case 'splitButton':
|
|
626
|
-
const splitButtonDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
627
|
-
const computedDisabled = component.disabled || splitButtonDisabled;
|
|
628
|
-
setInputRef = (el) => {
|
|
629
|
-
if (el) {
|
|
630
|
-
inputRefs.current[component.id] = el;
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
const handleSplitButtonEvent = (event) => {
|
|
634
|
-
if (onButtonClick) {
|
|
635
|
-
onButtonClick({
|
|
636
|
-
buttonId: component.id,
|
|
637
|
-
buttonLabel: component.label || '',
|
|
638
|
-
event
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
};
|
|
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));
|
|
643
|
-
default:
|
|
644
|
-
return renderField(component);
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
// Notify when all form elements and refs are rendered
|
|
648
|
-
useEffect(() => {
|
|
649
|
-
if (created && formState) {
|
|
650
|
-
setTimeout(() => {
|
|
651
|
-
const hasInputs = Object.keys(inputRefs.current).length > 0;
|
|
652
|
-
if (hasInputs) {
|
|
653
|
-
created();
|
|
654
|
-
}
|
|
655
|
-
}, 0);
|
|
656
|
-
}
|
|
657
|
-
}, [created, formState]);
|
|
658
|
-
// Expose getComponent as a public method
|
|
659
|
-
React.useImperativeHandle(ref, () => ({
|
|
660
|
-
/**
|
|
661
|
-
* Get the instance of the form input by name/id
|
|
662
|
-
*/
|
|
663
|
-
getComponent: (name) => {
|
|
664
|
-
return inputRefs.current[name] || null;
|
|
665
|
-
},
|
|
666
|
-
}), []);
|
|
667
|
-
return (_jsx("div", { ...restProps, className: `form-renderer-container ${className}`, children: _jsx(Form, { rules: validationRules, initialValues: initialValues, onFormStateChange: (state) => setFormState(state), onSubmit: (data) => {
|
|
668
|
-
const isValid = (formState?.errors ? Object.keys(formState.errors).length === 0 : false);
|
|
669
|
-
const transformedData = {};
|
|
670
|
-
const formValues = formState?.values || {};
|
|
671
|
-
// Exclude non-data components (buttons, messages, and layout-only components)
|
|
672
|
-
const excludedTypes = new Set(['button', 'message', 'panel', 'table', 'tabs']);
|
|
673
|
-
const allComps = collectAllComponents(processedSchema.components || []);
|
|
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));
|
|
696
|
-
Object.entries(data).forEach(([id, val]) => {
|
|
697
|
-
if (excludedIds.has(id) || hiddenFieldIds.has(id) || disabledFieldIds.has(id))
|
|
698
|
-
return;
|
|
699
|
-
const label = idToLabel[id] || id;
|
|
700
|
-
transformedData[label] = val;
|
|
701
|
-
});
|
|
702
|
-
if (onSubmit) {
|
|
703
|
-
onSubmit({ data: transformedData, isValid });
|
|
704
|
-
}
|
|
705
|
-
}, validateOnChange: true, className: `form-renderer ${layoutClass}`, children: processedSchema.components.map(component => renderComponent(component)) }) }));
|
|
706
|
-
});
|
|
707
|
-
export default FormRenderer;
|