@syncfusion/ej2-react-form-renderer 0.1.0-beta → 0.1.1-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/package.json +2 -2
- package/src/CheckboxGroup.js +1 -1
- package/src/ConditionalRuleEngine.d.ts +253 -0
- package/src/ConditionalRuleEngine.js +489 -0
- package/src/DataGridComponent.d.ts +0 -1
- package/src/DataGridComponent.js +3 -19
- package/src/FormRenderer.d.ts +29 -2
- package/src/FormRenderer.js +122 -40
- package/src/json-converter.d.ts +10 -0
- package/src/json-converter.js +181 -32
- package/src/parser.js +18 -17
- package/src/renderFormField.d.ts +2 -1
- package/src/renderFormField.js +44 -43
- package/src/types/FormSchema.d.ts +29 -1
- package/src/utils.js +86 -0
- package/styles/form-renderer.base.css +39 -23
package/src/FormRenderer.js
CHANGED
|
@@ -13,7 +13,8 @@ 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';
|
|
17
18
|
enableRipple(true);
|
|
18
19
|
/**
|
|
19
20
|
* Form layout options (only applies when UI schema is NOT provided)
|
|
@@ -109,7 +110,7 @@ function parseStringToUISchema(input) {
|
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
// ForwardRef to expose public methods
|
|
112
|
-
export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit, onChange, onButtonClick, className = '', layout = FormLayout.SingleColumn, created, ...restProps }, ref) => {
|
|
113
|
+
export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, onSubmit, onChange, onButtonClick, className = '', layout = FormLayout.SingleColumn, created, ...restProps }, ref) => {
|
|
113
114
|
const processedSchema = React.useMemo(() => {
|
|
114
115
|
try {
|
|
115
116
|
// Parse and normalize data schema
|
|
@@ -158,19 +159,22 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
158
159
|
}
|
|
159
160
|
// Scenario 4: Neither provided - return empty form
|
|
160
161
|
console.warn('FormRenderer: No schema provided (dataSchema or uiSchema)');
|
|
161
|
-
return { version: '1.0
|
|
162
|
+
return { version: '0.1.0', theme: 'tailwind3', components: [] };
|
|
162
163
|
}
|
|
163
164
|
catch (error) {
|
|
164
165
|
console.error('FormRenderer schema processing error:', error);
|
|
165
|
-
return { version: '1.0
|
|
166
|
+
return { version: '0.1.0', theme: 'tailwind3', components: [] };
|
|
166
167
|
}
|
|
167
168
|
}, [dataSchema, uiSchema]);
|
|
168
169
|
const [formState, setFormState] = useState(undefined);
|
|
169
170
|
const isInitializedRef = useRef(false);
|
|
171
|
+
const formLocale = locale || 'en-US';
|
|
170
172
|
// Attach ref to the input element for getComponent
|
|
171
173
|
let setInputRef;
|
|
172
174
|
// Store input component refs by name/id
|
|
173
175
|
const inputRefs = useRef({});
|
|
176
|
+
// Track which fields have active conditional values applied (to prevent re-applying while condition stays true)
|
|
177
|
+
const conditionalValueAppliedRef = useRef({});
|
|
174
178
|
// Check if UI schema has layout components (table, tabs, or panel)
|
|
175
179
|
const hasLayoutComponents = React.useMemo(() => {
|
|
176
180
|
if (!processedSchema.components)
|
|
@@ -256,7 +260,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
256
260
|
const map = {};
|
|
257
261
|
const traverse = (comps) => {
|
|
258
262
|
comps.forEach(comp => {
|
|
259
|
-
if (comp.label && !['panel', 'table', 'tabs', 'card', 'button', 'message', '
|
|
263
|
+
if (comp.label && !['panel', 'table', 'tabs', 'card', 'button', 'message', 'splitButton'].includes(comp.type)) {
|
|
260
264
|
map[comp.id] = comp.label;
|
|
261
265
|
}
|
|
262
266
|
if (comp.children)
|
|
@@ -282,20 +286,27 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
282
286
|
*/
|
|
283
287
|
const buildValidationRules = useCallback(() => {
|
|
284
288
|
const rules = {};
|
|
289
|
+
const formValues = formState?.values || {};
|
|
285
290
|
const collectRules = (components) => {
|
|
286
291
|
components.forEach((component) => {
|
|
287
292
|
if (component) {
|
|
288
293
|
const componentRules = {};
|
|
289
|
-
|
|
294
|
+
const conditionalRequired = ConditionalRuleEngine.isFieldRequired(component.conditions, formValues);
|
|
295
|
+
const conditionalDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
296
|
+
const conditionalReadOnly = ConditionalRuleEngine.isFieldReadOnly(component.conditions, formValues);
|
|
297
|
+
const finalRequired = component.required || conditionalRequired;
|
|
298
|
+
const finalDisabled = component.disabled || conditionalDisabled;
|
|
299
|
+
const finalReadOnly = component.readOnly || conditionalReadOnly;
|
|
300
|
+
if (finalRequired && !finalDisabled && !finalReadOnly) {
|
|
290
301
|
componentRules.required = [true, component.validationMessage || `${component.label} is required`];
|
|
291
302
|
}
|
|
292
|
-
if (component.minLength && !
|
|
303
|
+
if (component.minLength && !finalDisabled && !finalReadOnly) {
|
|
293
304
|
componentRules.minLength = [component.minLength, `Minimum ${component.minLength} characters required`];
|
|
294
305
|
}
|
|
295
|
-
if (component.maxLength && !
|
|
306
|
+
if (component.maxLength && !finalDisabled && !finalReadOnly) {
|
|
296
307
|
componentRules.maxLength = [component.maxLength, `Maximum ${component.maxLength} characters allowed`];
|
|
297
308
|
}
|
|
298
|
-
if (component?.type === 'textbox' && component?.textboxType && !component?.regex && !componentRules.regex && !
|
|
309
|
+
if (component?.type === 'textbox' && component?.textboxType && !component?.regex && !componentRules.regex && !finalDisabled && !finalReadOnly) {
|
|
299
310
|
if (component.textboxType === 'email') {
|
|
300
311
|
componentRules.regex = [VALIDATION_REGEX['EMAIL'], component?.validationMessage || 'Invalid format'];
|
|
301
312
|
}
|
|
@@ -303,7 +314,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
303
314
|
componentRules.regex = [VALIDATION_REGEX['URL'], component?.validationMessage || 'Invalid format'];
|
|
304
315
|
}
|
|
305
316
|
}
|
|
306
|
-
if (component.regex && !
|
|
317
|
+
if (component.regex && !finalDisabled && !finalReadOnly) {
|
|
307
318
|
componentRules.regex = [component.regex, component.validationMessage || 'Invalid format'];
|
|
308
319
|
}
|
|
309
320
|
if (Object.keys(componentRules).length > 0) {
|
|
@@ -329,7 +340,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
329
340
|
};
|
|
330
341
|
collectRules(processedSchema.components);
|
|
331
342
|
return rules;
|
|
332
|
-
}, [processedSchema]);
|
|
343
|
+
}, [processedSchema, formState?.values]);
|
|
333
344
|
const validationRules = buildValidationRules();
|
|
334
345
|
/**
|
|
335
346
|
* Build initial form values from component defaultValues
|
|
@@ -339,14 +350,23 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
339
350
|
const allComponents = collectAllComponents(processedSchema.components);
|
|
340
351
|
const componentIds = new Set();
|
|
341
352
|
allComponents.forEach(comp => {
|
|
342
|
-
// Do NOT store
|
|
343
|
-
if (comp.type === '
|
|
353
|
+
// Do NOT store dataGrid data inside the form state; grids manage their own internal data.
|
|
354
|
+
if (comp.type === 'dataGrid' || comp.type === 'richTextEditor') {
|
|
344
355
|
componentIds.add(comp.id);
|
|
345
356
|
return;
|
|
346
357
|
}
|
|
347
358
|
if (comp.defaultValue !== undefined) {
|
|
348
359
|
values[comp.id] = comp.defaultValue;
|
|
349
360
|
}
|
|
361
|
+
else {
|
|
362
|
+
// Initialize with type-appropriate defaults
|
|
363
|
+
switch (comp.type) {
|
|
364
|
+
case 'checkbox':
|
|
365
|
+
case 'switch':
|
|
366
|
+
values[comp.id] = false;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
350
370
|
componentIds.add(comp.id);
|
|
351
371
|
});
|
|
352
372
|
if (formState) {
|
|
@@ -374,17 +394,22 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
374
394
|
* Render a single field with label and error
|
|
375
395
|
*/
|
|
376
396
|
const renderField = (component) => {
|
|
377
|
-
|
|
378
|
-
if (component.visible === false)
|
|
379
|
-
return null;
|
|
397
|
+
const formValues = formState?.values || {};
|
|
380
398
|
// Skip layout and button components (handled separately)
|
|
381
|
-
if (['panel', 'table', 'tabs', 'card', 'message', 'button', '
|
|
399
|
+
if (['panel', 'table', 'tabs', 'card', 'message', 'button', 'splitButton', 'staticHtml'].includes(component.type)) {
|
|
382
400
|
return null;
|
|
383
401
|
}
|
|
402
|
+
const conditionalReadOnly = ConditionalRuleEngine.isFieldReadOnly(component.conditions, formValues);
|
|
403
|
+
const conditionalDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
404
|
+
const conditionalRequired = ConditionalRuleEngine.isFieldRequired(component.conditions, formValues);
|
|
405
|
+
const conditionalValue = ConditionalRuleEngine.getConditionalValue(component.conditions, formValues);
|
|
406
|
+
const computedReadOnly = component.readOnly || conditionalReadOnly;
|
|
407
|
+
const computedDisabled = component.disabled || conditionalDisabled;
|
|
408
|
+
const computedRequired = component.required || conditionalRequired;
|
|
384
409
|
const labelPos = component.labelPosition || 'top';
|
|
385
410
|
const labelWidth = component.labelWidth || 'auto';
|
|
386
|
-
const showLabel = !component.hideLabel && component.label && !['checkbox', 'button', '
|
|
387
|
-
if ((component.type === 'radio' || component.type === '
|
|
411
|
+
const showLabel = !component.hideLabel && component.label && !['checkbox', 'button', 'staticHtml'].includes(component.type);
|
|
412
|
+
if ((component.type === 'radio' || component.type === 'checkboxGroup') && Array.isArray(component.options)) {
|
|
388
413
|
// For radio, store each option's ref with unique key
|
|
389
414
|
setInputRef = (el, idx) => {
|
|
390
415
|
if (el && typeof idx === 'number') {
|
|
@@ -399,16 +424,32 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
399
424
|
}
|
|
400
425
|
};
|
|
401
426
|
}
|
|
402
|
-
const
|
|
403
|
-
|
|
427
|
+
const shouldShowError = !!formState?.errors[component.id];
|
|
428
|
+
const isRequiredError = shouldShowError && formState?.errors[component.id].includes('required');
|
|
429
|
+
const hasError = shouldShowError && !(isRequiredError && !computedRequired);
|
|
430
|
+
// Apply conditional value only when condition transitions from false to true (start of new cycle)
|
|
431
|
+
const conditionIsActive = conditionalValue !== undefined;
|
|
432
|
+
const wasConditionActive = conditionalValueAppliedRef.current[component.id] || false;
|
|
433
|
+
if (formState) {
|
|
434
|
+
if (conditionIsActive && !wasConditionActive) {
|
|
435
|
+
formState.onChange(component.id, { value: conditionalValue });
|
|
436
|
+
conditionalValueAppliedRef.current[component.id] = true;
|
|
437
|
+
}
|
|
438
|
+
else if (!conditionIsActive && wasConditionActive) {
|
|
439
|
+
// Condition just became inactive - clear tracking (ready for next cycle)
|
|
440
|
+
conditionalValueAppliedRef.current[component.id] = false;
|
|
441
|
+
}
|
|
442
|
+
// If condition stays active (wasConditionActive && conditionIsActive), do nothing - let user edit
|
|
443
|
+
}
|
|
444
|
+
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
445
|
cursor: 'pointer',
|
|
405
446
|
display: 'flex',
|
|
406
447
|
alignItems: 'center',
|
|
407
448
|
justifyContent: 'center',
|
|
408
449
|
flexShrink: 0,
|
|
409
|
-
} }) }))] })), _jsxs("div", { className: "field-input", children: [(component.type === 'radio' || component.type === '
|
|
450
|
+
} }) }))] })), _jsxs("div", { className: "field-input", children: [(component.type === 'radio' || component.type === 'checkboxGroup') && Array.isArray(component.options)
|
|
410
451
|
? renderFormField({
|
|
411
|
-
component,
|
|
452
|
+
component: { ...component, readOnly: computedReadOnly, disabled: computedDisabled, required: computedRequired },
|
|
412
453
|
value: formState?.values[component.id],
|
|
413
454
|
onChange: (value) => {
|
|
414
455
|
formState?.onChange(component.id, { value });
|
|
@@ -416,34 +457,45 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
416
457
|
onChange({ field: component.label || component.id, value });
|
|
417
458
|
}
|
|
418
459
|
},
|
|
460
|
+
locale: formLocale,
|
|
419
461
|
onBlur: () => formState?.onBlur(component.id),
|
|
420
462
|
ref: (el, idx) => setInputRef(el, idx),
|
|
421
463
|
hasError,
|
|
422
464
|
})
|
|
423
465
|
: renderFormField({
|
|
424
|
-
component,
|
|
466
|
+
component: { ...component, readOnly: computedReadOnly, disabled: computedDisabled, required: computedRequired },
|
|
425
467
|
value: formState?.values[component.id],
|
|
426
468
|
onChange: (value) => {
|
|
427
|
-
if (component.type !== '
|
|
469
|
+
if (component.type !== 'dataGrid' && component.type !== 'signature' && component.type !== 'richTextEditor') {
|
|
470
|
+
value = formatDateValues(component, value);
|
|
428
471
|
formState?.onChange(component.id, { value });
|
|
429
472
|
if (onChange) {
|
|
430
473
|
onChange({ field: component.label || component.id, value });
|
|
431
474
|
}
|
|
432
475
|
}
|
|
433
476
|
},
|
|
434
|
-
|
|
477
|
+
locale: formLocale,
|
|
478
|
+
onBlur: () => { if (component.type !== 'dataGrid' && component.type !== 'richTextEditor')
|
|
435
479
|
formState?.onBlur(component.id); },
|
|
436
480
|
ref: setInputRef,
|
|
437
481
|
hasError,
|
|
438
|
-
}),
|
|
482
|
+
}), hasError && _jsx("div", { className: "e-error", children: formState.errors[component.id] })] }), component.description && _jsx("div", { className: 'field-description', children: component.description })] }) }) }, component.id));
|
|
439
483
|
};
|
|
440
484
|
/**
|
|
441
485
|
* Render component (including layouts)
|
|
442
486
|
*/
|
|
443
487
|
const renderComponent = (component) => {
|
|
444
|
-
|
|
445
|
-
|
|
488
|
+
const formValues = formState?.values || {};
|
|
489
|
+
const isVisible = component.visible === false
|
|
490
|
+
? false
|
|
491
|
+
: ConditionalRuleEngine.isFieldVisible(component.conditions, formValues);
|
|
492
|
+
const isHidden = ConditionalRuleEngine.isFieldHidden(component.conditions, formValues);
|
|
493
|
+
if (!isVisible || isHidden) {
|
|
494
|
+
if (formState && formState.values[component.id] !== undefined && formState.values[component.id] !== null) {
|
|
495
|
+
formState.onChange(component.id, { value: null });
|
|
496
|
+
}
|
|
446
497
|
return null;
|
|
498
|
+
}
|
|
447
499
|
switch (component.type) {
|
|
448
500
|
case 'panel':
|
|
449
501
|
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 +505,15 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
453
505
|
const defaultColSpan = Math.floor(12 / tableCols);
|
|
454
506
|
const columnWidthsProp = component.columnWidths;
|
|
455
507
|
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) => {
|
|
508
|
+
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
509
|
const cellComponents = component.tableCells?.[rowIndex]?.[colIndex] || [];
|
|
458
510
|
const cellSpan = defaultColSpan;
|
|
459
511
|
const colWidthValue = splittedWidths[colIndex];
|
|
460
|
-
const
|
|
512
|
+
const isSingleColumn = tableCols === 1;
|
|
513
|
+
const cellStyle = isSingleColumn ? { width: '100%' } : (colWidthValue ? { width: `${colWidthValue}%` } : {});
|
|
461
514
|
const allNullWidths = !columnWidthsProp || (Array.isArray(columnWidthsProp.widths) && columnWidthsProp.widths.every((w) => w === null));
|
|
462
|
-
|
|
515
|
+
const isFirstRow = rowIndex === 0;
|
|
516
|
+
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
517
|
}) }, `row-${rowIndex}`))) }, component.id));
|
|
464
518
|
case 'tabs':
|
|
465
519
|
return (_jsx("div", { className: `form-tabs-renderer ${component.cssClass || ''}`, children: _jsx(CustomTabComponent, { tabs: component.tabItems?.map(tab => ({
|
|
@@ -468,15 +522,18 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
468
522
|
})) || [], className: component.cssClass }) }, component.id));
|
|
469
523
|
case 'card':
|
|
470
524
|
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 '
|
|
525
|
+
case 'staticHtml':
|
|
472
526
|
return (_jsx(StaticHtmlRenderer, { id: component.id, content: component.defaultValue || '<p>HTML content</p>', enableHtmlSanitizer: component.enableHtmlSanitizer }, component.id));
|
|
473
527
|
case 'message':
|
|
474
528
|
return (_jsx("div", { className: "form-group", children: renderFormField({
|
|
475
529
|
component,
|
|
530
|
+
locale: formLocale,
|
|
476
531
|
value: undefined,
|
|
477
532
|
onChange: () => { },
|
|
478
533
|
}) }, component.id));
|
|
479
534
|
case 'button':
|
|
535
|
+
const buttonlDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
536
|
+
const buttonComputed = component.disabled || buttonlDisabled;
|
|
480
537
|
const handleButtonClick = (event) => {
|
|
481
538
|
if (onButtonClick) {
|
|
482
539
|
onButtonClick({
|
|
@@ -486,9 +543,10 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
486
543
|
}
|
|
487
544
|
};
|
|
488
545
|
return (_jsx("div", { className: "form-group", children: renderFormField({
|
|
489
|
-
component,
|
|
546
|
+
component: { ...component, disabled: buttonComputed },
|
|
490
547
|
value: undefined,
|
|
491
548
|
onChange: () => { },
|
|
549
|
+
locale: formLocale,
|
|
492
550
|
onClick: (e) => { handleButtonClick(e); },
|
|
493
551
|
ref: (el, idx) => {
|
|
494
552
|
if (idx !== undefined) {
|
|
@@ -499,7 +557,9 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
499
557
|
}
|
|
500
558
|
}
|
|
501
559
|
}) }, component.id));
|
|
502
|
-
case '
|
|
560
|
+
case 'splitButton':
|
|
561
|
+
const splitButtonDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
|
|
562
|
+
const computedDisabled = component.disabled || splitButtonDisabled;
|
|
503
563
|
setInputRef = (el) => {
|
|
504
564
|
if (el) {
|
|
505
565
|
inputRefs.current[component.id] = el;
|
|
@@ -515,8 +575,9 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
515
575
|
}
|
|
516
576
|
};
|
|
517
577
|
return (_jsx("div", { className: "form-group", children: renderFormField({
|
|
518
|
-
component,
|
|
578
|
+
component: { ...component, disabled: computedDisabled },
|
|
519
579
|
value: undefined,
|
|
580
|
+
locale: formLocale,
|
|
520
581
|
onChange: () => { },
|
|
521
582
|
onClick: (e) => { handleSplitButtonEvent(e); },
|
|
522
583
|
ref: setInputRef
|
|
@@ -546,15 +607,36 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, onSubmit,
|
|
|
546
607
|
},
|
|
547
608
|
}), []);
|
|
548
609
|
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;
|
|
610
|
+
const isValid = (formState?.errors ? Object.keys(formState.errors).length === 0 : false);
|
|
550
611
|
const transformedData = {};
|
|
612
|
+
const formValues = formState?.values || {};
|
|
551
613
|
// Exclude non-data components (buttons, messages, and layout-only components)
|
|
552
|
-
const excludedTypes = new Set(['button', 'message', 'panel', 'table', 'tabs'
|
|
614
|
+
const excludedTypes = new Set(['button', 'message', 'panel', 'table', 'tabs']);
|
|
553
615
|
const allComps = collectAllComponents(processedSchema.components || []);
|
|
554
616
|
const excludedIds = new Set(allComps.filter(c => excludedTypes.has(c.type)).map(c => c.id));
|
|
617
|
+
const hiddenFieldIds = new Set(allComps.filter(c => {
|
|
618
|
+
if (c.visible === false)
|
|
619
|
+
return true;
|
|
620
|
+
if (c.conditions?.visibleWhen) {
|
|
621
|
+
if (!ConditionalRuleEngine.isFieldVisible(c.conditions, formValues))
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
if (c.conditions?.hideWhen) {
|
|
625
|
+
if (ConditionalRuleEngine.isFieldHidden(c.conditions, formValues))
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
return false;
|
|
629
|
+
}).map(c => c.id));
|
|
630
|
+
const disabledFieldIds = new Set(allComps.filter(c => {
|
|
631
|
+
if (c.disabled)
|
|
632
|
+
return true;
|
|
633
|
+
if (c.conditions?.disabledWhen) {
|
|
634
|
+
return ConditionalRuleEngine.isFieldDisabled(c.conditions, formValues);
|
|
635
|
+
}
|
|
636
|
+
return false;
|
|
637
|
+
}).map(c => c.id));
|
|
555
638
|
Object.entries(data).forEach(([id, val]) => {
|
|
556
|
-
|
|
557
|
-
if (excludedIds.has(id))
|
|
639
|
+
if (excludedIds.has(id) || hiddenFieldIds.has(id) || disabledFieldIds.has(id))
|
|
558
640
|
return;
|
|
559
641
|
const label = idToLabel[id] || id;
|
|
560
642
|
transformedData[label] = val;
|
package/src/json-converter.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FormSchema, FormComponent } from '../FormBuilder/types/FormSchema';
|
|
2
2
|
export declare const generateId: (type: string) => string;
|
|
3
|
+
export declare const generateName: (type: string) => string;
|
|
3
4
|
export interface SplitSchema {
|
|
4
5
|
dataSchema: DataSchema;
|
|
5
6
|
uiSchema: UISchema;
|
|
@@ -11,6 +12,7 @@ export interface DataSchema {
|
|
|
11
12
|
export interface DataField {
|
|
12
13
|
id: string;
|
|
13
14
|
type: 'string' | 'number' | 'boolean' | 'array' | 'any' | 'button' | 'message' | 'sign' | 'image' | 'file' | 'color' | 'grid';
|
|
15
|
+
name: string;
|
|
14
16
|
label?: string;
|
|
15
17
|
format?: 'date' | 'date-time' | 'time' | 'email' | 'url';
|
|
16
18
|
defaultValue?: any;
|
|
@@ -46,6 +48,7 @@ export interface FieldNode {
|
|
|
46
48
|
export interface TableNode {
|
|
47
49
|
type: 'table';
|
|
48
50
|
id: string;
|
|
51
|
+
name: string;
|
|
49
52
|
label?: string;
|
|
50
53
|
cssClass?: string;
|
|
51
54
|
hideBorders?: boolean;
|
|
@@ -55,6 +58,7 @@ export interface TableNode {
|
|
|
55
58
|
columnWidths?: {
|
|
56
59
|
widths: (number | null)[];
|
|
57
60
|
};
|
|
61
|
+
conditions?: any;
|
|
58
62
|
}
|
|
59
63
|
export interface TableCell {
|
|
60
64
|
row: number;
|
|
@@ -64,19 +68,23 @@ export interface TableCell {
|
|
|
64
68
|
export interface PanelNode {
|
|
65
69
|
type: 'panel';
|
|
66
70
|
id: string;
|
|
71
|
+
name: string;
|
|
67
72
|
label?: string;
|
|
68
73
|
cssClass?: string;
|
|
69
74
|
hideBorders?: boolean;
|
|
70
75
|
hideLabel?: boolean;
|
|
71
76
|
children: LayoutNode[];
|
|
77
|
+
conditions?: any;
|
|
72
78
|
}
|
|
73
79
|
export interface TabsNode {
|
|
74
80
|
type: 'tabs';
|
|
75
81
|
id: string;
|
|
82
|
+
name: string;
|
|
76
83
|
cssClass?: string;
|
|
77
84
|
label?: string;
|
|
78
85
|
tabOptions?: string[];
|
|
79
86
|
tabs: TabItem[];
|
|
87
|
+
conditions?: any;
|
|
80
88
|
}
|
|
81
89
|
export interface TabItem {
|
|
82
90
|
header: string;
|
|
@@ -85,6 +93,7 @@ export interface TabItem {
|
|
|
85
93
|
export interface CardNode {
|
|
86
94
|
type: 'card';
|
|
87
95
|
id: string;
|
|
96
|
+
name: string;
|
|
88
97
|
label?: string;
|
|
89
98
|
cssClass?: string;
|
|
90
99
|
hideLabel?: boolean;
|
|
@@ -94,6 +103,7 @@ export interface CardNode {
|
|
|
94
103
|
actionButtonLabel?: string;
|
|
95
104
|
actionButtonType?: 'button' | 'submit';
|
|
96
105
|
children: LayoutNode[];
|
|
106
|
+
conditions?: any;
|
|
97
107
|
}
|
|
98
108
|
/**
|
|
99
109
|
* Convert label to camelCase identifier
|