@syncfusion/ej2-react-form-renderer 0.1.1-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.
@@ -6,15 +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 { renderFormField } from './renderFormField';
9
+ import { RenderFormField } from './renderFormField';
10
10
  import { CustomTabComponent } from './CustomTabComponent';
11
- import { mergeSchema, convertComponentsToDataSchema, generateUIPropertiesFromData, generateLayoutFromData, generateDataSchemaFromUI } from './json-converter';
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
16
  import { formatDateValues, splitWidths } from './utils';
17
17
  import { ConditionalRuleEngine } from './ConditionalRuleEngine';
18
+ import { ExpressionEngine } from './expressions/ExpressionEngine';
19
+ import { MinMaxRuleEngine } from './MinMaxRuleEngine';
18
20
  enableRipple(true);
19
21
  /**
20
22
  * Form layout options (only applies when UI schema is NOT provided)
@@ -25,147 +27,45 @@ export const FormLayout = {
25
27
  ThreeColumn: 'threecolumn',
26
28
  FourColumn: 'fourcolumn'
27
29
  };
28
- /**
29
- * Helper: Normalize DataSchema input
30
- * Converts any object to JSON string and parses with UniversalJsonParser
31
- * Handles FormSchema, DataSchema, or raw data - parser infers the format
32
- */
33
- function normalizeDataSchema(input) {
34
- try {
35
- // Convert object to JSON string
36
- const jsonString = JSON.stringify(input);
37
- const parser = new UniversalJsonParser();
38
- const parseResult = parser.parse(jsonString);
39
- if (parseResult.success && parseResult.schema) {
40
- const dataSchema = convertComponentsToDataSchema(parseResult.schema.components);
41
- // console.log(`✅ Parsed object as ${parseResult.metadata.detectedFormat} and converted to DataSchema`);
42
- return dataSchema;
43
- }
44
- else {
45
- console.error('❌ Failed to parse object:', parseResult.errors);
46
- return null;
47
- }
48
- }
49
- catch (error) {
50
- console.error('❌ DataSchema normalization error:', error);
51
- return null;
52
- }
53
- }
54
- /**
55
- * Helper: Parse string input using UniversalJsonParser
56
- * Returns DataSchema if parseable, otherwise null
57
- * Validates that UI schema was not accidentally provided
58
- */
59
- function parseStringToDataSchema(input) {
60
- try {
61
- // First check if user accidentally pasted UI schema in data schema field
62
- const testParse = JSON.parse(input);
63
- if (testParse.layout || (testParse.theme && testParse.properties && testParse.layout)) {
64
- 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.');
65
- return null;
66
- }
67
- const parser = new UniversalJsonParser();
68
- const parseResult = parser.parse(input);
69
- if (parseResult.success && parseResult.schema) {
70
- const dataSchema = convertComponentsToDataSchema(parseResult.schema.components);
71
- return dataSchema;
72
- }
73
- else {
74
- console.error('Failed to parse input:', parseResult.errors);
75
- return null;
76
- }
77
- }
78
- catch (error) {
79
- console.error('Parser error:', error);
80
- return null;
81
- }
82
- }
83
- /**
84
- * Helper: Parse string input as UISchema
85
- * Validates JSON structure matches UISchema format
86
- * Detects if data schema was accidentally provided
87
- */
88
- function parseStringToUISchema(input) {
89
- try {
90
- const parsed = JSON.parse(input);
91
- // Check if data schema was pasted in UI schema field
92
- if (parsed.properties && !parsed.layout && !parsed.theme) {
93
- const hasValidationProps = Object.values(parsed.properties).some((prop) => prop.required !== undefined || prop.minLength !== undefined || prop.maxLength !== undefined ||
94
- prop.minValue !== undefined || prop.maxValue !== undefined || prop.pattern !== undefined);
95
- if (hasValidationProps) {
96
- 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.');
97
- return null;
98
- }
99
- }
100
- // Validate UISchema structure
101
- if (!parsed.theme || !parsed.properties || !parsed.layout) {
102
- console.error('Invalid UISchema: missing theme, properties, or layout');
103
- return null;
104
- }
105
- return parsed;
106
- }
107
- catch (error) {
108
- console.error('JSON parse error:', error);
109
- return null;
110
- }
111
- }
112
30
  // ForwardRef to expose public methods
113
- export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, onSubmit, onChange, onButtonClick, className = '', layout = FormLayout.SingleColumn, created, ...restProps }, ref) => {
31
+ export const FormRenderer = React.forwardRef(({ schema, locale, onSubmit, onChange, onButtonClick, className = '', layout = FormLayout.SingleColumn, created, ...restProps }, ref) => {
114
32
  const processedSchema = React.useMemo(() => {
115
33
  try {
116
- // Parse and normalize data schema
117
- let parsedDataSchema = dataSchema
118
- ? ((typeof dataSchema === 'string'
119
- ? parseStringToDataSchema(dataSchema)
120
- : normalizeDataSchema(dataSchema)) || undefined)
121
- : undefined;
122
- // Parse and normalize UI schema
123
- let parsedUISchema = uiSchema
124
- ? (typeof uiSchema === 'string'
125
- ? parseStringToUISchema(uiSchema)
126
- : uiSchema) || undefined
127
- : undefined;
128
- // Scenario 1: Both dataSchema and uiSchema provided - full merge
129
- if (parsedDataSchema && parsedUISchema) {
130
- const uiFieldIds = new Set(Object.keys(parsedUISchema.properties));
131
- const dataFieldIds = Object.keys(parsedDataSchema.properties);
132
- const missingFields = dataFieldIds.filter(id => !uiFieldIds.has(id));
133
- if (missingFields.length > 0) {
134
- 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: [] };
135
45
  }
136
- const merged = mergeSchema({ dataSchema: parsedDataSchema, uiSchema: parsedUISchema });
137
- return merged;
138
46
  }
139
- // Scenario 2: Only dataSchema provided - auto-generate UI schema
140
- if (parsedDataSchema && !parsedUISchema) {
141
- const autoUISchema = {
142
- properties: generateUIPropertiesFromData(parsedDataSchema),
143
- layout: generateLayoutFromData(parsedDataSchema)
144
- };
145
- const merged = mergeSchema({ dataSchema: parsedDataSchema, uiSchema: autoUISchema });
146
- return merged;
47
+ const isUnifiedSchema = parsedInput && typeof parsedInput === 'object' &&
48
+ parsedInput.properties && Array.isArray(parsedInput.layout);
49
+ if (isUnifiedSchema) {
50
+ return mergeSchema(parsedInput);
147
51
  }
148
- // Scenario 3: Only uiSchema provided - auto-generate data schema
149
- if (!parsedDataSchema && parsedUISchema) {
150
- const autoDataSchema = generateDataSchemaFromUI(parsedUISchema);
151
- const uiFieldIds = new Set(Object.keys(parsedUISchema.properties));
152
- const dataFieldIds = Object.keys(autoDataSchema.properties);
153
- const missingFields = dataFieldIds.filter(id => !uiFieldIds.has(id));
154
- if (missingFields.length > 0) {
155
- console.warn(`Found ${missingFields.length} fields in auto-generated data schema missing from UI schema`);
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: [] };
156
61
  }
157
- const merged = mergeSchema({ dataSchema: autoDataSchema, uiSchema: parsedUISchema });
158
- return merged;
159
62
  }
160
- // Scenario 4: Neither provided - return empty form
161
- console.warn('FormRenderer: No schema provided (dataSchema or uiSchema)');
162
- return { version: '0.1.0', theme: 'tailwind3', components: [] };
163
63
  }
164
64
  catch (error) {
165
65
  console.error('FormRenderer schema processing error:', error);
166
- return { version: '0.1.0', theme: 'tailwind3', components: [] };
66
+ return { version: '1.0.0', components: [] };
167
67
  }
168
- }, [dataSchema, uiSchema]);
68
+ }, [schema]);
169
69
  const [formState, setFormState] = useState(undefined);
170
70
  const isInitializedRef = useRef(false);
171
71
  const formLocale = locale || 'en-US';
@@ -175,6 +75,12 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
175
75
  const inputRefs = useRef({});
176
76
  // Track which fields have active conditional values applied (to prevent re-applying while condition stays true)
177
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());
178
84
  // Check if UI schema has layout components (table, tabs, or panel)
179
85
  const hasLayoutComponents = React.useMemo(() => {
180
86
  if (!processedSchema.components)
@@ -351,7 +257,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
351
257
  const componentIds = new Set();
352
258
  allComponents.forEach(comp => {
353
259
  // Do NOT store dataGrid data inside the form state; grids manage their own internal data.
354
- if (comp.type === 'dataGrid' || comp.type === 'richTextEditor') {
260
+ if (comp.type === 'dataGrid' || comp.type === 'richTextEditor' || comp.type === 'staticHtml') {
355
261
  componentIds.add(comp.id);
356
262
  return;
357
263
  }
@@ -375,7 +281,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
375
281
  });
376
282
  }
377
283
  return values;
378
- }, [processedSchema, collectAllComponents, dataSchema, uiSchema]);
284
+ }, [processedSchema, collectAllComponents, schema]);
379
285
  // Update the useEffect that sets default values
380
286
  useEffect(() => {
381
287
  if (formState && !isInitializedRef.current) {
@@ -390,6 +296,167 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
390
296
  });
391
297
  }
392
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]);
393
460
  /**
394
461
  * Render a single field with label and error
395
462
  */
@@ -406,6 +473,21 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
406
473
  const computedReadOnly = component.readOnly || conditionalReadOnly;
407
474
  const computedDisabled = component.disabled || conditionalDisabled;
408
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
+ }
409
491
  const labelPos = component.labelPosition || 'top';
410
492
  const labelWidth = component.labelWidth || 'auto';
411
493
  const showLabel = !component.hideLabel && component.label && !['checkbox', 'button', 'staticHtml'].includes(component.type);
@@ -448,38 +530,33 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
448
530
  justifyContent: 'center',
449
531
  flexShrink: 0,
450
532
  } }) }))] })), _jsxs("div", { className: "field-input", children: [(component.type === 'radio' || component.type === 'checkboxGroup') && Array.isArray(component.options)
451
- ? renderFormField({
452
- component: { ...component, readOnly: computedReadOnly, disabled: computedDisabled, required: computedRequired },
453
- value: formState?.values[component.id],
454
- onChange: (value) => {
533
+ ? (_jsx(RenderFormField, { component: { ...component, readOnly: computedReadOnly, disabled: computedDisabled, required: computedRequired }, value: formState?.values[component.id], onChange: (value) => {
455
534
  formState?.onChange(component.id, { value });
456
535
  if (onChange) {
457
536
  onChange({ field: component.label || component.id, value });
458
537
  }
459
- },
460
- locale: formLocale,
461
- onBlur: () => formState?.onBlur(component.id),
462
- ref: (el, idx) => setInputRef(el, idx),
463
- hasError,
464
- })
465
- : renderFormField({
466
- component: { ...component, readOnly: computedReadOnly, disabled: computedDisabled, required: computedRequired },
467
- value: formState?.values[component.id],
468
- onChange: (value) => {
469
- if (component.type !== 'dataGrid' && component.type !== 'signature' && component.type !== 'richTextEditor') {
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') {
470
552
  value = formatDateValues(component, value);
471
553
  formState?.onChange(component.id, { value });
472
554
  if (onChange) {
473
555
  onChange({ field: component.label || component.id, value });
474
556
  }
475
557
  }
476
- },
477
- locale: formLocale,
478
- onBlur: () => { if (component.type !== 'dataGrid' && component.type !== 'richTextEditor')
479
- formState?.onBlur(component.id); },
480
- ref: setInputRef,
481
- hasError,
482
- }), hasError && _jsx("div", { className: "e-error", children: formState.errors[component.id] })] }), component.description && _jsx("div", { className: 'field-description', children: component.description })] }) }) }, 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));
483
560
  };
484
561
  /**
485
562
  * Render component (including layouts)
@@ -525,12 +602,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
525
602
  case 'staticHtml':
526
603
  return (_jsx(StaticHtmlRenderer, { id: component.id, content: component.defaultValue || '<p>HTML content</p>', enableHtmlSanitizer: component.enableHtmlSanitizer }, component.id));
527
604
  case 'message':
528
- return (_jsx("div", { className: "form-group", children: renderFormField({
529
- component,
530
- locale: formLocale,
531
- value: undefined,
532
- onChange: () => { },
533
- }) }, component.id));
605
+ return (_jsx("div", { className: "form-group", children: _jsx(RenderFormField, { component: component, locale: formLocale, value: undefined, onChange: () => { } }) }, component.id));
534
606
  case 'button':
535
607
  const buttonlDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
536
608
  const buttonComputed = component.disabled || buttonlDisabled;
@@ -542,21 +614,14 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
542
614
  });
543
615
  }
544
616
  };
545
- return (_jsx("div", { className: "form-group", children: renderFormField({
546
- component: { ...component, disabled: buttonComputed },
547
- value: undefined,
548
- onChange: () => { },
549
- locale: formLocale,
550
- onClick: (e) => { handleButtonClick(e); },
551
- 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) => {
552
618
  if (idx !== undefined) {
553
619
  inputRefs.current[`${component.id}_${idx}`] = el;
554
620
  }
555
621
  else {
556
622
  inputRefs.current[component.id] = el;
557
623
  }
558
- }
559
- }) }, component.id));
624
+ } }) }, component.id));
560
625
  case 'splitButton':
561
626
  const splitButtonDisabled = ConditionalRuleEngine.isFieldDisabled(component.conditions, formValues);
562
627
  const computedDisabled = component.disabled || splitButtonDisabled;
@@ -574,14 +639,7 @@ export const FormRenderer = React.forwardRef(({ dataSchema, uiSchema, locale, on
574
639
  });
575
640
  }
576
641
  };
577
- return (_jsx("div", { className: "form-group", children: renderFormField({
578
- component: { ...component, disabled: computedDisabled },
579
- value: undefined,
580
- locale: formLocale,
581
- onChange: () => { },
582
- onClick: (e) => { handleSplitButtonEvent(e); },
583
- ref: setInputRef
584
- }) }, 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));
585
643
  default:
586
644
  return renderField(component);
587
645
  }
@@ -0,0 +1,95 @@
1
+ export interface MinMaxRangeItem {
2
+ enabled: boolean;
3
+ minValue?: number | string | {
4
+ field: string;
5
+ };
6
+ maxValue?: number | string | {
7
+ field: string;
8
+ };
9
+ }
10
+ export interface MinMaxConstraints {
11
+ minValue?: number | string;
12
+ maxValue?: number | string;
13
+ minDate?: string;
14
+ maxDate?: string;
15
+ minTime?: string;
16
+ maxTime?: string;
17
+ }
18
+ export declare class MinMaxRuleEngine {
19
+ /**
20
+ * Extract min/max constraints from minMaxRange array
21
+ * Handles three cases:
22
+ * 1. Direct values (enabled: false)
23
+ * 2. Single field reference (enabled: true, 1 range)
24
+ * 3. Multiple ranges (enabled: true, 2+ ranges) - takes UTMOST
25
+ *
26
+ * @param minMaxRange - Array of range configurations
27
+ * @param formValues - Current form state values (id → value)
28
+ * @param componentType - 'number' | 'date' | 'dateRange'
29
+ * @returns Constraints object with appropriate keys
30
+ */
31
+ static getConstraints(minMaxRange: MinMaxRangeItem[] | undefined, formValues: Record<string, any>, componentType?: string): MinMaxConstraints;
32
+ /**
33
+ * Resolve a single value - either direct or field reference
34
+ *
35
+ * @param value - Direct value or field reference object
36
+ * @param formValues - Form state values for field lookup
37
+ * @returns Resolved value or undefined
38
+ */
39
+ private static resolveValue;
40
+ /**
41
+ * Get the most permissive (widest) minimum value
42
+ * For numbers: MIN of all mins
43
+ * For dates: earliest date (MIN string comparison on ISO format)
44
+ * For times: earliest time (MIN string comparison on 24-hour format)
45
+ *
46
+ * @param ranges - Array of {min, max} values
47
+ * @param componentType - Type of component
48
+ * @returns Most permissive minimum value
49
+ */
50
+ private static getPermissiveMin;
51
+ /**
52
+ * Get the most permissive (widest) maximum value
53
+ * For numbers: MAX of all maxs
54
+ * For dates: latest date (MAX string comparison on ISO format)
55
+ * For times: latest time (MAX string comparison on 24-hour format)
56
+ *
57
+ * @param ranges - Array of {min, max} values
58
+ * @param componentType - Type of component
59
+ * @returns Most permissive maximum value
60
+ */
61
+ private static getPermissiveMax;
62
+ /**
63
+ * Convert MM/DD/YYYY date format to ISO (YYYY-MM-DD) for comparison
64
+ * Handles both formats gracefully
65
+ *
66
+ * @param dateString - Date in MM/DD/YYYY or ISO format
67
+ * @returns ISO date string (YYYY-MM-DD)
68
+ */
69
+ private static convertToISO;
70
+ /**
71
+ * Convert 12-hour time format to 24-hour format for comparison
72
+ * Handles both 12-hour (hh:mm a) and 24-hour (HH:mm) formats
73
+ *
74
+ * @param timeString - Time in 12-hour (hh:mm a/p) or 24-hour (HH:mm) format
75
+ * @returns 24-hour format time string (HH:mm)
76
+ */
77
+ private static convertTimeTo24Hour;
78
+ /**
79
+ * Get empty constraints object based on component type
80
+ */
81
+ private static getEmptyConstraints;
82
+ /**
83
+ * Validate if a value passes all min/max constraints (optional)
84
+ * Can be used for client-side validation before submission
85
+ *
86
+ * @param value - Value to validate
87
+ * @param constraints - Constraints to check against
88
+ * @param componentType - Type of component
89
+ * @returns Validation result with error message if invalid
90
+ */
91
+ static validateValue(value: any, constraints: MinMaxConstraints, componentType: string): {
92
+ isValid: boolean;
93
+ error?: string;
94
+ };
95
+ }