+ {category.elements
+ .filter((element: Control | Layout) => shouldShowElement(element))
+ .map((element: Control | Layout, index: number) => {
+ if (element.type === "Control") {
+ return
{renderControl(element as Control)}
;
+ } else if (element.type === "HorizontalLayout") {
+ return
{renderLayout(element as Layout)}
;
+ }
+ return null;
+ })}
+
+ );
+ };
+ // Add validation function
+ const validateField = useCallback((path: string, value: any, fieldSchema: any) => {
+ const errors: string[] = [];
+
+ // Required field validation - check if field name is in schema.required array
+ const fieldName = path.split('.').pop() || '';
+ if (schema.required?.includes(fieldName) && (value === undefined || value === null || value === '')) {
+ errors.push('This field is required');
+ }
+
+ // Type-specific validation
+ if (value !== undefined && value !== null) {
+ switch (fieldSchema.type) {
+ case 'string':
+ if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
+ errors.push(`Minimum length is ${fieldSchema.minLength}`);
+ }
+ if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
+ errors.push(`Maximum length is ${fieldSchema.maxLength}`);
+ }
+ if (fieldSchema.pattern && !new RegExp(fieldSchema.pattern).test(value)) {
+ errors.push('Invalid format');
+ }
+ break;
+ case 'number':
+ case 'integer':
+ if (fieldSchema.minimum !== undefined && value < fieldSchema.minimum) {
+ errors.push(`Minimum value is ${fieldSchema.minimum}`);
+ }
+ if (fieldSchema.maximum !== undefined && value > fieldSchema.maximum) {
+ errors.push(`Maximum value is ${fieldSchema.maximum}`);
+ }
+ break;
+ }
+ }
+
+ return errors;
+ }, [])
+ // Helper to get value at a dot-separated path
+ const getValueAtPath = (obj: any, path: string) => {
+ if (!path) return obj;
+ return path.split('.').reduce((acc, part) => (acc ? acc[part] : undefined), obj);
+ };
+ // Update validation state when data changes
+ useEffect(() => {
+ if (isSubmitted) {
+ const newValidationState: ValidationState = {};
+ const validateObject = (obj: any, schema: any, path: string = '') => {
+ if (schema.properties) {
+ Object.entries(schema.properties).forEach(([key, fieldSchema]: [string, any]) => {
+ const fullPath = path ? `${path}.${key}` : key;
+ const value = getValueAtPath(obj, key);
+ newValidationState[fullPath] = {
+ errors: validateField(fullPath, getValueAtPath(obj, key), fieldSchema),
+ touched: true
+ };
+ if (fieldSchema.type === 'object' && fieldSchema.properties) {
+ validateObject(getValueAtPath(obj, key) || {}, fieldSchema, fullPath);
+ }
+ });
+ }
+ };
+ validateObject(data, schema);
+ setValidationState(newValidationState);
+ }
+ }, [data, schema, validateField, isSubmitted]);
+ const handleValueChange = (newValue: any, fieldKey: string, fieldPath?: string) => {
+ const newData = { ...localData };
+ if (fieldPath) {
+ const pathParts = fieldPath.split(".");
+ let current = newData;
+ for (let i = 0; i < pathParts.length; i++) {
+ if (i === pathParts.length - 1) {
+ current[pathParts[i]] = {
+ ...current[pathParts[i]],
+ [fieldKey]: newValue,
+ };
+ } else {
+ current = current[pathParts[i]];
+ }
+ }
+ } else {
+ newData[fieldKey] = newValue;
+ }
+
+ setLocalData(newData);
+ debouncedOnChange(newData);
+ };
+
+ const createInputHandler = (fieldKey: string, fieldPath?: string) => {
+ return (e: React.ChangeEvent