cc-form-engine
Version:
Advanced Angular library for reactive form generation and management with dynamic validation, change tracking, and customizable error messages
1 lines • 82.7 kB
Source Map (JSON)
{"version":3,"file":"cc-form-engine.mjs","sources":["../../../projects/cc-form-engine/src/lib/interfaces/form-engine-config.interface.ts","../../../projects/cc-form-engine/src/lib/services/form-engine-config.service.ts","../../../projects/cc-form-engine/src/lib/parsers/value-parser.ts","../../../projects/cc-form-engine/src/lib/utils/form-utils.ts","../../../projects/cc-form-engine/src/lib/services/form-generator.service.ts","../../../projects/cc-form-engine/src/lib/providers/form-engine.providers.ts","../../../projects/cc-form-engine/src/lib/validators/form-validators.ts","../../../projects/cc-form-engine/src/lib/builders/form-config.builder.ts","../../../projects/cc-form-engine/src/lib/builders/form-factory.ts","../../../projects/cc-form-engine/src/lib/utils/format-utils.ts","../../../projects/cc-form-engine/src/public-api.ts","../../../projects/cc-form-engine/src/cc-form-engine.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface LocaleConfig {\n locale: string;\n currency: string;\n dateFormat?: 'short' | 'long';\n decimalSeparator?: '.' | ',';\n thousandsSeparator?: ',' | '.';\n}\n\nexport interface ErrorMessages {\n required?: string;\n email?: string;\n strictEmail?: string;\n strongPassword?: string;\n minlength?: string;\n maxlength?: string;\n min?: string;\n max?: string;\n pattern?: string;\n whitespace?: string;\n minDate?: string;\n maxDate?: string;\n minMoney?: string;\n maxMoney?: string;\n percentage?: string;\n maxDecimals?: string;\n default?: string;\n}\n\nexport interface FormEngineConfig {\n locale?: LocaleConfig;\n errorMessages?: ErrorMessages;\n trackChanges?: boolean;\n debounceTime?: number;\n}\n\n// Default configurations by country/language\nexport const DEFAULT_LOCALES: Record<string, LocaleConfig> = {\n 'es-CO': {\n locale: 'es-CO',\n currency: 'COP',\n dateFormat: 'short',\n decimalSeparator: ',',\n thousandsSeparator: '.'\n },\n 'en-US': {\n locale: 'en-US',\n currency: 'USD',\n dateFormat: 'short',\n decimalSeparator: '.',\n thousandsSeparator: ','\n },\n 'en-CA': {\n locale: 'en-CA',\n currency: 'CAD',\n dateFormat: 'short',\n decimalSeparator: '.',\n thousandsSeparator: ','\n },\n 'es-MX': {\n locale: 'es-MX',\n currency: 'MXN',\n dateFormat: 'short',\n decimalSeparator: '.',\n thousandsSeparator: ','\n },\n 'pt-BR': {\n locale: 'pt-BR',\n currency: 'BRL',\n dateFormat: 'short',\n decimalSeparator: ',',\n thousandsSeparator: '.'\n }\n};\n\nexport const DEFAULT_ERROR_MESSAGES: Record<string, ErrorMessages> = {\n 'es': {\n required: 'Este campo es requerido',\n email: 'Ingrese un correo electrónico válido',\n strictEmail: 'El formato del correo electrónico no es válido',\n strongPassword: 'La contraseña debe contener mayúsculas, minúsculas, números y caracteres especiales',\n minlength: 'Mínimo {requiredLength} caracteres',\n maxlength: 'Máximo {requiredLength} caracteres',\n min: 'El valor mínimo es {min}',\n max: 'El valor máximo es {max}',\n pattern: 'El formato no es válido',\n whitespace: 'No puede contener solo espacios en blanco',\n minDate: 'La fecha mínima es {min}',\n maxDate: 'La fecha máxima es {max}',\n minMoney: 'El valor mínimo es {min}',\n maxMoney: 'El valor máximo es {max}',\n percentage: 'Porcentaje inválido',\n maxDecimals: 'Máximo {max} decimales',\n default: 'Campo inválido'\n },\n 'en': {\n required: 'This field is required',\n email: 'Please enter a valid email address',\n strictEmail: 'Invalid email format',\n strongPassword: 'Password must contain uppercase, lowercase, numbers and special characters',\n minlength: 'Minimum {requiredLength} characters',\n maxlength: 'Maximum {requiredLength} characters',\n min: 'Minimum value is {min}',\n max: 'Maximum value is {max}',\n pattern: 'Invalid format',\n whitespace: 'Cannot contain only whitespace',\n minDate: 'Minimum date is {min}',\n maxDate: 'Maximum date is {max}',\n minMoney: 'Minimum value is {min}',\n maxMoney: 'Maximum value is {max}',\n percentage: 'Invalid percentage',\n maxDecimals: 'Maximum {max} decimals',\n default: 'Invalid field'\n },\n 'pt': {\n required: 'Este campo é obrigatório',\n email: 'Digite um endereço de email válido',\n strictEmail: 'Formato de email inválido',\n strongPassword: 'A senha deve conter maiúsculas, minúsculas, números e caracteres especiais',\n minlength: 'Mínimo {requiredLength} caracteres',\n maxlength: 'Máximo {requiredLength} caracteres',\n min: 'O valor mínimo é {min}',\n max: 'O valor máximo é {max}',\n pattern: 'Formato inválido',\n whitespace: 'Não pode conter apenas espaços em branco',\n minDate: 'A data mínima é {min}',\n maxDate: 'A data máxima é {max}',\n minMoney: 'O valor mínimo é {min}',\n maxMoney: 'O valor máximo é {max}',\n percentage: 'Porcentagem inválida',\n maxDecimals: 'Máximo {max} decimais',\n default: 'Campo inválido'\n }\n};\n\n// Injection tokens\nexport const FORM_ENGINE_CONFIG = new InjectionToken<FormEngineConfig>('FORM_ENGINE_CONFIG');\nexport const FORM_ENGINE_LOCALE = new InjectionToken<LocaleConfig>('FORM_ENGINE_LOCALE');\nexport const FORM_ENGINE_ERROR_MESSAGES = new InjectionToken<ErrorMessages>('FORM_ENGINE_ERROR_MESSAGES');","import { Injectable, inject } from '@angular/core';\nimport { \n FormEngineConfig, \n LocaleConfig, \n ErrorMessages, \n DEFAULT_LOCALES, \n DEFAULT_ERROR_MESSAGES,\n FORM_ENGINE_CONFIG,\n FORM_ENGINE_LOCALE,\n FORM_ENGINE_ERROR_MESSAGES\n} from '../interfaces/form-engine-config.interface';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class FormEngineConfigService {\n private config: FormEngineConfig;\n private locale: LocaleConfig;\n private errorMessages: ErrorMessages;\n\n constructor() {\n // Try to get configuration from injection tokens\n const tokenConfig = this.getInjectedConfig();\n \n // Merge configurations with priority: token > default\n this.config = this.mergeConfig(tokenConfig);\n this.locale = this.getLocaleConfig(this.config);\n this.errorMessages = this.getErrorMessagesConfig(this.config);\n }\n\n getLocale(): LocaleConfig {\n return this.locale;\n }\n\n getErrorMessages(): ErrorMessages {\n return this.errorMessages;\n }\n\n getConfig(): FormEngineConfig {\n return this.config;\n }\n\n formatErrorMessage(errorKey: string, errorValue?: any): string {\n let template = this.errorMessages[errorKey as keyof ErrorMessages] || this.errorMessages.default || 'Invalid field';\n\n // Replace placeholders in error messages\n if (errorValue && typeof template === 'string') {\n template = template.replace(/\\{(\\w+)\\}/g, (match, key) => {\n return errorValue[key]?.toString() || match;\n });\n }\n\n return template;\n }\n\n updateConfig(newConfig: Partial<FormEngineConfig>): void {\n this.config = { ...this.config, ...newConfig };\n \n if (newConfig.locale) {\n this.locale = newConfig.locale;\n }\n \n if (newConfig.errorMessages) {\n this.errorMessages = { ...this.errorMessages, ...newConfig.errorMessages };\n }\n }\n\n updateLocale(newLocale: Partial<LocaleConfig>): void {\n this.locale = { ...this.locale, ...newLocale };\n }\n\n updateErrorMessages(newMessages: Partial<ErrorMessages>): void {\n this.errorMessages = { ...this.errorMessages, ...newMessages };\n }\n\n // Static methods for creating configurations\n static createConfig(options: Partial<FormEngineConfig> = {}): FormEngineConfig {\n const browserLocale = this.getBrowserLocale();\n const defaultLocale = DEFAULT_LOCALES[browserLocale] || DEFAULT_LOCALES['en-US'];\n const languageCode = browserLocale.split('-')[0];\n const defaultMessages = DEFAULT_ERROR_MESSAGES[languageCode] || DEFAULT_ERROR_MESSAGES['en'];\n\n return {\n locale: defaultLocale,\n errorMessages: defaultMessages,\n trackChanges: true,\n debounceTime: 300,\n ...options\n };\n }\n\n static createLocaleConfig(locale: string): LocaleConfig {\n return DEFAULT_LOCALES[locale] || DEFAULT_LOCALES['en-US'];\n }\n\n static createErrorMessages(language: string): ErrorMessages {\n return DEFAULT_ERROR_MESSAGES[language] || DEFAULT_ERROR_MESSAGES['en'];\n }\n\n private getInjectedConfig(): FormEngineConfig | undefined {\n try {\n const config = inject(FORM_ENGINE_CONFIG, { optional: true });\n const locale = inject(FORM_ENGINE_LOCALE, { optional: true });\n const errorMessages = inject(FORM_ENGINE_ERROR_MESSAGES, { optional: true });\n\n if (!config && !locale && !errorMessages) {\n return undefined;\n }\n\n return {\n ...config,\n locale: locale || config?.locale,\n errorMessages: errorMessages || config?.errorMessages\n };\n } catch {\n return undefined;\n }\n }\n\n private mergeConfig(tokenConfig?: FormEngineConfig): FormEngineConfig {\n const defaultConfig = FormEngineConfigService.createConfig();\n\n return {\n ...defaultConfig,\n ...tokenConfig\n };\n }\n\n private getLocaleConfig(config: FormEngineConfig): LocaleConfig {\n return config.locale || DEFAULT_LOCALES['en-US'];\n }\n\n private getErrorMessagesConfig(config: FormEngineConfig): ErrorMessages {\n return config.errorMessages || DEFAULT_ERROR_MESSAGES['en'];\n }\n\n private static getBrowserLocale(): string {\n if (typeof navigator !== 'undefined') {\n return navigator.language || 'en-US';\n }\n return 'en-US';\n }\n}","import { Injectable, inject } from '@angular/core';\nimport { FieldType } from '../interfaces/form-config.interface';\nimport { FormEngineConfigService } from '../services/form-engine-config.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ValueParser {\n private configService = inject(FormEngineConfigService);\n \n parseValue(value: any, type: FieldType): any {\n if (value === null || value === undefined) {\n return this.getDefaultValue(type);\n }\n\n switch (type) {\n case 'number':\n return this.parseNumber(value);\n case 'money':\n return this.parseMoney(value);\n case 'percentage':\n return this.parsePercentage(value);\n case 'boolean':\n return this.parseBoolean(value);\n case 'date':\n return this.parseDate(value);\n case 'string[]':\n return this.parseStringArray(value);\n case 'array':\n return this.parseArray(value);\n case 'file':\n return value;\n case 'string':\n default:\n return this.parseString(value);\n }\n }\n\n private parseNumber(value: any): number | null {\n if (value === '' || value === null) return null;\n const parsed = Number(value);\n return isNaN(parsed) ? null : parsed;\n }\n\n private parseMoney(value: any): number | null {\n if (value === '' || value === null) return null;\n \n if (typeof value === 'number') return value;\n \n const locale = this.configService.getLocale();\n let cleaned = value.toString().replace(/[^0-9.,-]+/g, '');\n \n // Use locale-specific decimal and thousands separators\n if (locale.decimalSeparator === ',' && locale.thousandsSeparator === '.') {\n // European format: 1.234,56\n cleaned = cleaned.replace(/\\./g, ''); // Remove thousands separators\n cleaned = cleaned.replace(/,/g, '.'); // Convert decimal separator\n } else {\n // US format: 1,234.56 (default)\n cleaned = cleaned.replace(/,/g, ''); // Remove thousands separators\n }\n \n const parsed = parseFloat(cleaned);\n return isNaN(parsed) ? 0 : parsed;\n }\n\n private parsePercentage(value: any): number | null {\n if (value === '' || value === null) return null;\n \n if (typeof value === 'number') return value;\n \n const locale = this.configService.getLocale();\n const stringValue = value.toString().replace(/\\s|%/g, '');\n \n // Use locale-specific decimal separator\n const cleanValue = locale.decimalSeparator === ','\n ? stringValue.replace(',', '.')\n : stringValue;\n \n const parsed = parseFloat(cleanValue);\n \n return isNaN(parsed) ? 0 : parsed;\n }\n\n private parseBoolean(value: any): boolean {\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') {\n return value.toLowerCase() === 'true';\n }\n return Boolean(value);\n }\n\n private parseDate(value: any): string | null {\n if (!value) return null;\n \n if (value instanceof Date && !isNaN(value.getTime())) {\n return value.toISOString().split('T')[0];\n }\n \n if (typeof value === 'string') {\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return date.toISOString().split('T')[0];\n }\n }\n \n return null;\n }\n\n private parseStringArray(value: any): string[] {\n if (Array.isArray(value)) return value;\n if (typeof value === 'string') {\n return value.split(',').map(v => v.trim()).filter(Boolean);\n }\n return [];\n }\n\n private parseArray(value: any): any[] {\n if (Array.isArray(value)) return value;\n \n if (typeof value === 'string') {\n return value\n .split(',')\n .map(v => v.trim())\n .filter(Boolean);\n }\n \n return [];\n }\n\n private parseString(value: any): string {\n if (value === null || value === undefined) return '';\n return String(value);\n }\n\n private getDefaultValue(type: FieldType): any {\n switch (type) {\n case 'number':\n case 'money':\n case 'percentage':\n return null;\n case 'boolean':\n return false;\n case 'date':\n return null;\n case 'string[]':\n case 'array':\n return [];\n case 'file':\n return null;\n case 'string':\n default:\n return '';\n }\n }\n}","export class FormUtils {\n \n static deepEqual(a: any, b: any): boolean {\n if (a == null && b == null) return true;\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object' || a === null || b === null) return a === b;\n \n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!FormUtils.deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n \n if (Array.isArray(a) !== Array.isArray(b)) return false;\n \n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n \n return keysA.every((key) => FormUtils.deepEqual(a[key], b[key]));\n }\n\n static clone<T>(obj: T): T {\n if (obj === null || typeof obj !== 'object') return obj;\n \n if (obj instanceof Date) {\n return new Date(obj.getTime()) as any;\n }\n \n if (Array.isArray(obj)) {\n return obj.map(item => FormUtils.clone(item)) as any;\n }\n \n const clonedObj: any = {};\n for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n clonedObj[key] = FormUtils.clone(obj[key]);\n }\n }\n \n return clonedObj;\n }\n\n static isEmpty(value: any): boolean {\n return (\n value === null ||\n value === undefined ||\n value === '' ||\n (Array.isArray(value) && value.length === 0) ||\n (typeof value === 'object' && Object.keys(value).length === 0)\n );\n }\n\n static isDateString(value: string): boolean {\n if (!value || typeof value !== 'string') return false;\n \n return /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})?)?$/.test(value);\n }\n\n static normalizeDate(value: any): string | null {\n if (!value) return null;\n \n if (value instanceof Date && !isNaN(value.getTime())) {\n return value.toISOString().split('T')[0];\n }\n \n if (typeof value === 'string') {\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return date.toISOString().split('T')[0];\n }\n }\n \n return null;\n }\n}","import { Injectable, signal, untracked, WritableSignal, inject } from '@angular/core';\nimport { FormBuilder, FormGroup, FormControl } from '@angular/forms';\nimport { FormConfig } from '../interfaces/form-config.interface';\nimport { FormTrackedState } from '../models/form-field.model';\nimport { ValueParser } from '../parsers/value-parser';\nimport { FormUtils } from '../utils/form-utils';\nimport { FormEngineConfigService } from './form-engine-config.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class FormGeneratorService {\n private trackedForms = new Map<FormGroup, FormTrackedState>();\n private fb = inject(FormBuilder);\n private valueParser = inject(ValueParser);\n private configService = inject(FormEngineConfigService);\n\n generateFormGroup<T>(formConfig: FormConfig<T>): FormGroup {\n const group: { [key: string]: any } = {};\n \n for (const key in formConfig) {\n if (formConfig.hasOwnProperty(key)) {\n const config = formConfig[key];\n const control = new FormControl(\n { value: config.defaultValue, disabled: config.disabled || false },\n config.validators || []\n );\n group[key] = control;\n }\n }\n\n const form = this.fb.group(group);\n this.initializeTracking(form);\n \n return form;\n }\n\n generateFormGroupFromData<T>(\n formConfig: FormConfig<T>,\n data: Partial<T>\n ): FormGroup {\n const form = this.generateFormGroup(formConfig);\n this.setFormValues(form, data, formConfig);\n return form;\n }\n\n getTypedFormValues<T>(form: FormGroup, config: FormConfig<T>): T {\n const rawValues = form.getRawValue();\n const typedValues = {} as T;\n\n (Object.keys(config) as Array<keyof T>).forEach((key) => {\n const rawValue = rawValues[key as string];\n const fieldType = config[key].type;\n \n const value = this.valueParser.parseValue(rawValue, fieldType);\n \n if (!FormUtils.isEmpty(value)) {\n typedValues[key] = value;\n }\n });\n\n return typedValues;\n }\n\n setFormValues<T>(\n form: FormGroup,\n data: Partial<T>,\n config?: FormConfig<T>\n ): void {\n const tracked = this.ensureFormTracked(form);\n tracked.isInitializing = true;\n\n Object.keys(form.controls).forEach((key) => {\n const control = form.get(key);\n const value = data[key as keyof T];\n\n if (!control) return;\n\n if (value === undefined || value === null) {\n control.setValue(null, { emitEvent: false });\n return;\n }\n\n let parsedValue = value;\n \n if (config && config[key as keyof T]) {\n const fieldType = config[key as keyof T].type;\n parsedValue = this.valueParser.parseValue(value, fieldType);\n } else {\n parsedValue = this.autoParseValue(value);\n }\n\n control.setValue(parsedValue, { emitEvent: false });\n });\n\n tracked.initialValue = this.normalizeFormValues(form.getRawValue());\n untracked(() => tracked.hasChanges.set(false));\n form.updateValueAndValidity({ emitEvent: false });\n tracked.isInitializing = false;\n }\n\n getFormValidationErrors(form: FormGroup): { [key: string]: string };\n getFormValidationErrors<T>(form: FormGroup, config: FormConfig<T>): { [key: string]: string };\n getFormValidationErrors<T>(form: FormGroup, config?: FormConfig<T>): { [key: string]: string } {\n const errors: { [key: string]: string } = {};\n\n Object.keys(form.controls).forEach((key) => {\n const control = form.get(key);\n if (control && control.errors && (control.dirty || control.touched)) {\n const errorKeys = Object.keys(control.errors);\n const firstErrorKey = errorKeys[0];\n \n // Check if config is provided and has custom error messages\n if (config) {\n const fieldConfig = config[key as keyof T];\n if (fieldConfig && fieldConfig.errorMessages && fieldConfig.errorMessages[firstErrorKey]) {\n errors[key] = fieldConfig.errorMessages[firstErrorKey];\n return;\n }\n }\n \n // Fallback to default error message\n errors[key] = this.getErrorMessage(firstErrorKey, control.errors[firstErrorKey]);\n }\n });\n\n return errors;\n }\n\n getHasChanges(form: FormGroup): WritableSignal<boolean> {\n return this.ensureFormTracked(form).hasChanges;\n }\n\n resetFormState(form: FormGroup): void {\n const tracked = this.trackedForms.get(form);\n if (!tracked) return;\n\n tracked.initialValue = this.normalizeFormValues(form.getRawValue());\n untracked(() => tracked.hasChanges.set(false));\n }\n\n markFormAsPristine(form: FormGroup): void {\n form.markAsPristine();\n Object.keys(form.controls).forEach(key => {\n const control = form.get(key);\n if (control) {\n control.markAsPristine();\n control.markAsUntouched();\n }\n });\n this.resetFormState(form);\n }\n\n clearControl(form: FormGroup, controlName: string, emitEvent = true): void {\n const control = form.get(controlName);\n if (!control) return;\n\n control.setValue(null, { emitEvent });\n control.markAsPristine();\n control.markAsUntouched();\n \n if (!emitEvent) {\n const tracked = this.trackedForms.get(form);\n if (tracked) {\n const current = this.normalizeFormValues(form.getRawValue());\n const base = untracked(() => tracked.initialValue);\n const changed = !FormUtils.deepEqual(current, base);\n untracked(() => tracked.hasChanges.set(changed));\n }\n }\n }\n\n disposeForm(form: FormGroup): void {\n this.trackedForms.delete(form);\n }\n\n private initializeTracking(form: FormGroup): void {\n const tracked: FormTrackedState = {\n initialValue: this.normalizeFormValues(form.getRawValue()),\n hasChanges: signal(false),\n isInitializing: false,\n };\n\n this.trackedForms.set(form, tracked);\n\n form.valueChanges.subscribe(() => {\n if (tracked.isInitializing) return;\n \n const current = this.normalizeFormValues(form.getRawValue());\n const base = untracked(() => tracked.initialValue);\n const changed = !FormUtils.deepEqual(current, base);\n untracked(() => tracked.hasChanges.set(changed));\n });\n }\n\n private ensureFormTracked(form: FormGroup): FormTrackedState {\n let tracked = this.trackedForms.get(form);\n \n if (!tracked) {\n this.initializeTracking(form);\n tracked = this.trackedForms.get(form)!;\n }\n \n return tracked;\n }\n\n private normalizeFormValues<T>(raw: T): T {\n const clone = FormUtils.clone(raw);\n\n for (const key in clone) {\n const value = clone[key];\n if (value instanceof Date && !isNaN(value.getTime())) {\n clone[key] = FormUtils.normalizeDate(value) as any;\n }\n }\n\n return clone;\n }\n\n private autoParseValue(value: any): any {\n if (value === null || value === undefined) return value;\n\n if (typeof value === 'string') {\n if (FormUtils.isDateString(value)) {\n return new Date(value);\n }\n \n if (value === 'true' || value === 'false') {\n return value === 'true';\n }\n }\n\n return value;\n }\n\n private getErrorMessage(errorKey: string, errorValue: any): string {\n return this.configService.formatErrorMessage(errorKey, errorValue);\n }\n}","import { Provider } from '@angular/core';\nimport {\n FormEngineConfig,\n LocaleConfig,\n ErrorMessages,\n FORM_ENGINE_CONFIG\n} from '../interfaces/form-engine-config.interface';\nimport { FormEngineConfigService } from '../services/form-engine-config.service';\n\n/**\n * Provides complete FormEngine configuration\n */\nexport function provideFormEngineConfig(config: FormEngineConfig): Provider[] {\n return [\n { provide: FORM_ENGINE_CONFIG, useValue: config },\n FormEngineConfigService\n ];\n}\n\n/**\n * Provides FormEngine with locale-specific configuration\n */\nexport function provideFormEngineWithLocale(\n locale: string,\n customConfig?: Partial<FormEngineConfig>\n): Provider[] {\n const config = FormEngineConfigService.createConfig({\n locale: FormEngineConfigService.createLocaleConfig(locale),\n errorMessages: FormEngineConfigService.createErrorMessages(locale.split('-')[0]),\n ...customConfig\n });\n\n return provideFormEngineConfig(config);\n}\n\n/**\n * Provides FormEngine for Colombian Spanish\n */\nexport function provideFormEngineForColombia(\n customConfig?: Partial<FormEngineConfig>\n): Provider[] {\n return provideFormEngineWithLocale('es-CO', customConfig);\n}\n\n/**\n * Provides FormEngine for US English\n */\nexport function provideFormEngineForUS(\n customConfig?: Partial<FormEngineConfig>\n): Provider[] {\n return provideFormEngineWithLocale('en-US', customConfig);\n}\n\n/**\n * Provides FormEngine for Canadian English\n */\nexport function provideFormEngineForCanada(\n customConfig?: Partial<FormEngineConfig>\n): Provider[] {\n return provideFormEngineWithLocale('en-CA', customConfig);\n}\n\n/**\n * Provides FormEngine for Mexican Spanish\n */\nexport function provideFormEngineForMexico(\n customConfig?: Partial<FormEngineConfig>\n): Provider[] {\n return provideFormEngineWithLocale('es-MX', customConfig);\n}\n\n/**\n * Provides FormEngine for Brazilian Portuguese\n */\nexport function provideFormEngineForBrazil(\n customConfig?: Partial<FormEngineConfig>\n): Provider[] {\n return provideFormEngineWithLocale('pt-BR', customConfig);\n}\n\n/**\n * Provides FormEngine with custom locale and error messages\n */\nexport function provideFormEngineCustom(\n locale: LocaleConfig,\n errorMessages: ErrorMessages,\n customConfig?: Partial<Omit<FormEngineConfig, 'locale' | 'errorMessages'>>\n): Provider[] {\n const config: FormEngineConfig = {\n locale,\n errorMessages,\n trackChanges: true,\n debounceTime: 300,\n ...customConfig\n };\n\n return provideFormEngineConfig(config);\n}\n\n/**\n * Provides FormEngine with auto-detection of browser locale\n */\nexport function provideFormEngineWithAutoLocale(\n fallbackLocale: string = 'en-US',\n customConfig?: Partial<FormEngineConfig>\n): Provider[] {\n const browserLocale = typeof navigator !== 'undefined'\n ? navigator.language\n : fallbackLocale;\n\n return provideFormEngineWithLocale(browserLocale, customConfig);\n}\n","import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';\n\nexport class FormValidators {\n \n static strictEmail(): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const value = control.value;\n if (!value) return null;\n \n const regex = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z]{2,}$/;\n return regex.test(value) ? null : { strictEmail: true };\n };\n }\n\n static strongPassword(): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const value = control.value;\n if (!value) return null;\n \n const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[\\W_]).+$/;\n return regex.test(value) ? null : { strongPassword: true };\n };\n }\n\n static noWhitespace(): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const isOnlyWhitespace = \n typeof control.value === 'string' && \n control.value.trim().length === 0;\n return isOnlyWhitespace ? { whitespace: true } : null;\n };\n }\n\n static alphanumeric(): ValidatorFn {\n return Validators.pattern(/^[a-zA-Z0-9\\s]*$/);\n }\n\n static alphanumericDash(): ValidatorFn {\n return Validators.pattern(/^[a-zA-Z0-9\\- ]*$/);\n }\n\n static alphanumericExtended(): ValidatorFn {\n return Validators.pattern(/^[a-zA-Z0-9áéíóúÁÉÍÓÚüÜñÑ/\\-\\s]*$/);\n }\n\n static numericOnly(): ValidatorFn {\n return Validators.pattern(/^[0-9]*$/);\n }\n\n static minDate(minDate: Date | (() => Date)): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n if (!control.value) return null;\n \n const min = typeof minDate === 'function' ? minDate() : minDate;\n const selectedDate = new Date(control.value);\n \n if (selectedDate < min) {\n return {\n minDate: {\n min: min.toISOString().split('T')[0],\n actual: selectedDate.toISOString().split('T')[0]\n }\n };\n }\n \n return null;\n };\n }\n\n static maxDate(maxDate: Date | (() => Date)): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n if (!control.value) return null;\n \n const max = typeof maxDate === 'function' ? maxDate() : maxDate;\n const selectedDate = new Date(control.value);\n \n if (selectedDate > max) {\n return {\n maxDate: {\n max: max.toISOString().split('T')[0],\n actual: selectedDate.toISOString().split('T')[0]\n }\n };\n }\n \n return null;\n };\n }\n\n static minMoney(minValue: number): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const value = control.value;\n if (value === null || value === undefined || value === '') return null;\n \n const parsed = this.parseMoney(value);\n \n if (isNaN(parsed) || !isFinite(parsed)) {\n return { minMoney: { min: minValue, actual: 'Invalid value' } };\n }\n \n if (parsed < minValue) {\n return { minMoney: { min: minValue, actual: parsed } };\n }\n \n return null;\n };\n }\n\n static maxMoney(maxValue: number): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const value = control.value;\n if (value === null || value === undefined || value === '') return null;\n \n const parsed = this.parseMoney(value);\n \n if (isNaN(parsed) || !isFinite(parsed) || parsed > Number.MAX_SAFE_INTEGER) {\n return { maxMoney: { max: maxValue, actual: 'Value too large' } };\n }\n \n if (parsed > maxValue) {\n return { maxMoney: { max: maxValue, actual: parsed } };\n }\n \n return null;\n };\n }\n\n static maxDecimals(decimals: number): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const value = control.value;\n if (value === null || value === undefined || value === '') return null;\n \n const regex = new RegExp(`^\\\\d+([,.]\\\\d{1,${decimals}})?$`);\n const stringValue = value.toString().replace('.', ',');\n \n if (!regex.test(stringValue)) {\n return { maxDecimals: { max: decimals } };\n }\n \n return null;\n };\n }\n\n static percentage(min = 0, max = 100): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const value = control.value;\n if (value === null || value === undefined || value === '') return null;\n \n const parsed = this.parsePercentage(value);\n \n if (isNaN(parsed)) {\n return { percentage: { message: 'Invalid percentage value' } };\n }\n \n if (parsed < min || parsed > max) {\n return { percentage: { min, max, actual: parsed } };\n }\n \n return null;\n };\n }\n\n private static parseMoney(value: string | number): number {\n if (typeof value === 'number') return value;\n \n const stringValue = value.toString();\n let cleanValue = stringValue.replace(/[$\\s]/g, '');\n cleanValue = cleanValue.replace(/\\./g, '');\n cleanValue = cleanValue.replace(/,/g, '.');\n \n return parseFloat(cleanValue);\n }\n\n private static parsePercentage(value: string | number): number {\n if (typeof value === 'number') return value;\n \n const stringValue = value.toString().replace(/\\s|%/g, '');\n const cleanValue = stringValue.replace(',', '.');\n \n return parseFloat(cleanValue);\n }\n}","import { ValidatorFn } from '@angular/forms';\nimport { FormConfig, FormFieldConfig, FieldType } from '../interfaces/form-config.interface';\n\nexport class FormConfigBuilder<T> {\n private config: Partial<FormConfig<T>> = {};\n\n addField(\n name: keyof T,\n type: FieldType,\n options?: Partial<FormFieldConfig>\n ): FormConfigBuilder<T> {\n const fieldConfig: FormFieldConfig = {\n type,\n defaultValue: options?.defaultValue ?? this.getDefaultValueForType(type),\n validators: options?.validators || [],\n disabled: options?.disabled || false,\n label: options?.label,\n placeholder: options?.placeholder,\n hint: options?.hint,\n errorMessages: options?.errorMessages\n };\n\n this.config[name] = fieldConfig;\n return this;\n }\n\n addTextField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'string', options);\n }\n\n addNumberField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'number', options);\n }\n\n addMoneyField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'money', options);\n }\n\n addPercentageField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'percentage', options);\n }\n\n addBooleanField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'boolean', {\n ...options,\n defaultValue: options?.defaultValue ?? false\n });\n }\n\n addDateField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'date', options);\n }\n\n addFileField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'file', options);\n }\n\n addArrayField(\n name: keyof T,\n options?: Partial<Omit<FormFieldConfig, 'type'>>\n ): FormConfigBuilder<T> {\n return this.addField(name, 'array', {\n ...options,\n defaultValue: options?.defaultValue ?? []\n });\n }\n\n addValidators(name: keyof T, validators: ValidatorFn[]): FormConfigBuilder<T> {\n if (this.config[name]) {\n const existingValidators = this.config[name]!.validators || [];\n this.config[name]!.validators = [...existingValidators, ...validators];\n }\n return this;\n }\n\n setDisabled(name: keyof T, disabled: boolean): FormConfigBuilder<T> {\n if (this.config[name]) {\n this.config[name]!.disabled = disabled;\n }\n return this;\n }\n\n setLabel(name: keyof T, label: string): FormConfigBuilder<T> {\n if (this.config[name]) {\n this.config[name]!.label = label;\n }\n return this;\n }\n\n setPlaceholder(name: keyof T, placeholder: string): FormConfigBuilder<T> {\n if (this.config[name]) {\n this.config[name]!.placeholder = placeholder;\n }\n return this;\n }\n\n setHint(name: keyof T, hint: string): FormConfigBuilder<T> {\n if (this.config[name]) {\n this.config[name]!.hint = hint;\n }\n return this;\n }\n\n setErrorMessages(\n name: keyof T,\n errorMessages: Record<string, string>\n ): FormConfigBuilder<T> {\n if (this.config[name]) {\n this.config[name]!.errorMessages = errorMessages;\n }\n return this;\n }\n\n build(): FormConfig<T> {\n return this.config as FormConfig<T>;\n }\n\n private getDefaultValueForType(type: FieldType): any {\n switch (type) {\n case 'string':\n return '';\n case 'number':\n case 'money':\n case 'percentage':\n return null;\n case 'boolean':\n return false;\n case 'date':\n case 'file':\n return null;\n case 'string[]':\n case 'array':\n return [];\n default:\n return null;\n }\n }\n}","import { Injectable, inject } from '@angular/core';\nimport { FormGroup, Validators, ValidatorFn } from '@angular/forms';\nimport { FormConfig } from '../interfaces/form-config.interface';\nimport { FormGeneratorService } from '../services/form-generator.service';\nimport { FormValidators } from '../validators/form-validators';\nimport { FormConfigBuilder } from './form-config.builder';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class FormFactory {\n private formGenerator = inject(FormGeneratorService);\n\n createForm<T>(config: FormConfig<T>, data?: Partial<T>): FormGroup {\n if (data) {\n return this.formGenerator.generateFormGroupFromData(config, data);\n }\n return this.formGenerator.generateFormGroup(config);\n }\n\n createBuilder<T>(): FormConfigBuilder<T> {\n return new FormConfigBuilder<T>();\n }\n\n createLoginForm(data?: { email?: string; password?: string }): FormGroup {\n const config = new FormConfigBuilder<{ email: string; password: string }>()\n .addTextField('email', {\n validators: [Validators.required, FormValidators.strictEmail()],\n placeholder: 'user@example.com',\n label: 'Email Address',\n errorMessages: {\n required: 'Email is required',\n strictEmail: 'Invalid email format'\n }\n })\n .addTextField('password', {\n validators: [\n Validators.required,\n FormValidators.strongPassword(),\n Validators.minLength(8),\n Validators.maxLength(25)\n ],\n placeholder: 'Password',\n label: 'Password',\n errorMessages: {\n required: 'Password is required',\n strongPassword: 'Must contain uppercase, lowercase, numbers and special characters',\n minlength: 'Minimum 8 characters',\n maxlength: 'Maximum 25 characters'\n }\n })\n .build();\n\n return this.createForm(config, data);\n }\n\n createUserForm(data?: any): FormGroup {\n const config = new FormConfigBuilder<any>()\n .addTextField('firstName', {\n validators: [Validators.required, FormValidators.noWhitespace()],\n label: 'First Name',\n placeholder: 'Enter your first name'\n })\n .addTextField('lastName', {\n validators: [Validators.required, FormValidators.noWhitespace()],\n label: 'Last Name',\n placeholder: 'Enter your last name'\n })\n .addTextField('email', {\n validators: [Validators.required, FormValidators.strictEmail()],\n label: 'Email Address',\n placeholder: 'user@example.com'\n })\n .addDateField('birthDate', {\n validators: [Validators.required],\n label: 'Date of Birth'\n })\n .addBooleanField('active', {\n defaultValue: true,\n label: 'Active User'\n })\n .build();\n\n return this.createForm(config, data);\n }\n\n createProductForm(data?: any): FormGroup {\n const config = new FormConfigBuilder<any>()\n .addTextField('name', {\n validators: [Validators.required, FormValidators.noWhitespace()],\n label: 'Product Name'\n })\n .addTextField('description', {\n label: 'Description'\n })\n .addMoneyField('price', {\n validators: [\n Validators.required,\n FormValidators.minMoney(0),\n FormValidators.maxMoney(999999999)\n ],\n label: 'Price',\n placeholder: '0.00'\n })\n .addNumberField('stock', {\n validators: [Validators.required, Validators.min(0)],\n label: 'Available Stock',\n defaultValue: 0\n })\n .addPercentageField('discount', {\n validators: [FormValidators.percentage(0, 100)],\n label: 'Discount (%)',\n defaultValue: 0\n })\n .addBooleanField('available', {\n defaultValue: true,\n label: 'Available for Sale'\n })\n .build();\n\n return this.createForm(config, data);\n }\n\n createDynamicForm(fields: Array<{\n name: string;\n type: 'string' | 'number' | 'boolean' | 'date' | 'money' | 'percentage';\n label?: string;\n required?: boolean;\n validators?: ValidatorFn[];\n }>): FormGroup {\n const builder = new FormConfigBuilder<any>();\n\n fields.forEach(field => {\n const validators = field.validators || [];\n if (field.required) {\n validators.unshift(Validators.required);\n }\n\n const options = {\n validators,\n label: field.label\n };\n\n switch (field.type) {\n case 'string':\n builder.addTextField(field.name, options);\n break;\n case 'number':\n builder.addNumberField(field.name, options);\n break;\n case 'boolean':\n builder.addBooleanField(field.name, options);\n break;\n case 'date':\n builder.addDateField(field.name, options);\n break;\n case 'money':\n builder.addMoneyField(field.name, options);\n break;\n case 'percentage':\n builder.addPercentageField(field.name, options);\n break;\n }\n });\n\n return this.createForm(builder.build());\n }\n}","import { FormEngineConfigService } from '../services/form-engine-config.service';\nimport { LocaleConfig } from '../interfaces/form-engine-config.interface';\n\nexport class FormatUtils {\n \n static formatMoney(value: number, localeConfig?: LocaleConfig, forceDecimals = false): string {\n if (value == null || isNaN(value)) return '$0';\n \n const config = localeConfig || FormatUtils.getDefaultLocaleConfig();\n const hasDecimals = value % 1 !== 0;\n \n return value.toLocaleString(config.locale, {\n style: 'currency',\n currency: config.currency,\n minimumFractionDigits: forceDecimals || hasDecimals ? 2 : 0,\n maximumFractionDigits: 2,\n });\n }\n\n static formatPercentage(value: number, localeConfig?: LocaleConfig, decimals = 2): string {\n if (value == null || isNaN(value)) return '0%';\n \n const config = localeConfig || FormatUtils.getDefaultLocaleConfig();\n \n const formatted = value.toLocaleString(config.locale, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n });\n \n return `${formatted}%`;\n }\n\n static formatNumber(value: number, localeConfig?: LocaleConfig, decimals?: number): string {\n if (value == null || isNaN(value)) return '0';\n \n const config = localeConfig || FormatUtils.getDefaultLocaleConfig();\n const options: Intl.NumberFormatOptions = {};\n \n if (decimals !== undefined) {\n options.minimumFractionDigits = decimals;\n options.maximumFractionDigits = decimals;\n }\n \n return value.toLocaleString(config.locale, options);\n }\n\n static formatDate(value: Date | string, localeConfig?: LocaleConfig, format?: 'short' | 'long'): string {\n if (!value) return '';\n \n const config = localeConfig || FormatUtils.getDefaultLocaleConfig();\n const dateFormat = format || config.dateFormat || 'short';\n const date = typeof value === 'string' ? new Date(value) : value;\n \n if (isNaN(date.getTime())) return '';\n \n if (dateFormat === 'short') {\n return date.toLocaleDateString(config.locale, {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit'\n });\n }\n \n return date.toLocaleDateString(config.locale, {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n static formatDateTime(value: Date | string, localeConfig?: LocaleConfig): string {\n if (!value) return '';\n \n const config = localeConfig || FormatUtils.getDefaultLocaleConfig();\n const date = typeof value === 'string' ? new Date(value) : value;\n \n if (isNaN(date.getTime())) return '';\n \n return date.toLocaleString(config.locale, {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit'\n });\n }\n\n private static getDefaultLocaleConfig(): LocaleConfig {\n return {\n locale: 'en-US',\n currency: 'USD',\n dateFormat: 'short',\n decimalSeparator: '.',\n thousandsSeparator: ','\n };\n }\n\n // Instance methods for injection-aware formatting\n static createInstance(configService: FormEngineConfigService) {\n return new FormatUtilsInstance(configService);\n }\n}\n\nexport class FormatUtilsInstance {\n constructor(private configService: FormEngineConfigService) {}\n\n formatMoney(value: number, forceDecimals = false): string {\n return FormatUtils.formatMoney(value, this.configService.getLocale(), forceDecimals);\n }\n\n formatPercentage(value: number, decimals = 2): string {\n return FormatUtils.formatPercentage(value, this.configService.getLocale(), decimals);\n }\n\n formatNumber(value: number, decimals?: number): string {\n return FormatUtils.formatNumber(value, this.configService.getLocale(), decimals);\n }\n\n formatDate(value: Date | string, format?: 'short' | 'long'): string {\n return FormatUtils.formatDate(value, this.configService.getLocale(), format);\n }\n\n formatDateTime(value: Date | string): string {\n return FormatUtils.formatDateTime(value, this.configService.getLocale());\n }\n}","/*\n * Public API Surface of cc-form-engine\n */\n\n// Services\nexport * from './lib/services/form-generator.service';\nexport * from './lib/services/form-engine-config.service';\n\n// Interfaces & Types\nexport * from './lib/interfaces/form-config.interface';\nexport * from './lib/interfaces/form-engine-config.interface';\nexport * from './lib/models/form-field.model';\n\n// Providers\nexport * from './lib/providers/form-engine.providers';\n\n// Validators\nexport * from './lib/validators/form-validators';\n\n// Builders & Factories\nexport * from './lib/builders/form-config.builder';\nexport * from './lib/builders/form-factory';\n\n// Parsers\nexport * from './lib/parsers/value-parser';\n\n// Utils\nexport * from './lib/utils/form-utils';\nexport * from './lib/utils/format-utils';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;AAqCA;AACa,MAAA,eAAe,GAAiC;AAC3D,IAAA,OAAO,EAAE;AACP,QAAA,MAAM,EAAE,OAAO;AACf,QAAA,QAAQ,EAAE,KAAK;AACf,QAAA,UAAU,EAAE,OAAO;AACnB,QAAA,gBAAgB,EAAE,GAAG;AACrB,QAAA,kBAAkB,EAAE;AACrB,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,MAAM,EAAE,OAAO;AACf,QAAA,QAAQ,EAAE,KAAK;AACf,QAAA,UAAU,EAAE,OAAO;AACnB,QAAA,gBAAgB,EAAE,GAAG;AACrB,QAAA,kBAAkB,EAAE;AACrB,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,MAAM,EAAE,OAAO;AACf,QAAA,QAAQ,EAAE,KAAK;AACf,QAAA,UAAU,EAAE,OAAO;AACnB,QAAA,gBAAgB,EAAE,GAAG;AACrB,QAAA,kBAAkB,EAAE;AACrB,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,MAAM,EAAE,OAAO;AACf,QAAA,QAAQ,EAAE,KAAK;AACf,QAAA,UAAU,EAAE,OAAO;AACnB,QAAA,gBAAgB,EAAE,GAAG;AACrB,QAAA,kBAAkB,EAAE;AACrB,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,MAAM,EAAE,OAAO;AACf,QAAA,QAAQ,EAAE,KAAK;AACf,QAAA,UAAU,EAAE,OAAO;AACnB,QAAA,gBAAgB,EAAE,GAAG;AACrB,QAAA,kBAAkB,EAAE;AACrB;;AAGU,MAAA,sBAAsB,GAAkC;AACnE,IAAA,IAAI,EAAE;AACJ,QAAA,QAAQ,EAAE,yBAAyB;AACnC,QAAA,KAAK,EAAE,sCAAsC;AAC7C,QAAA,WAAW,EAAE,gDAAgD;AAC7D,QAAA,cAAc,EAAE,qFAAqF;AACrG,QAAA,SAAS,EAAE,oCAAoC;AAC/C,QAAA,SAAS,EAAE,oCAAoC;AAC/C,QAAA,GAAG,EAAE,0BAA0B;AAC/B,QAAA,GAAG,EAAE,0BAA0B;AAC/B,QAAA,OAAO,EAAE,yBAAyB;AAClC,QAAA,UAAU,EAAE,2CAA2C;AACvD,QAAA,OAAO,EAAE,0BAA0B;AACnC,QAAA,OAAO,EAAE,0BAA0B;AACnC,QAAA,QAAQ,EAAE,0BAA0B;AACpC,QAAA,QAAQ,EAAE,0BAA0B;AACpC,QAAA,UAAU,EAAE,qBAAqB;AACjC,QAAA,WAAW,EAAE,wBAAwB;AACrC,QAAA,OAAO,EAAE;AACV,KAAA;AACD,IAAA,IAAI,EAAE;AACJ,QAAA,QAAQ,EAAE,wBAAwB;AAClC,QAAA,KAAK,EAAE,oCAAoC;AAC3C,QAAA,WAAW,EAAE,sBAAsB;AACnC,QAAA,cAAc,EAAE,4EAA4E;AAC5F,QAAA,SAAS,EAAE,qCAAqC;AAChD,QAAA,SAAS,EAAE,qCAAqC;AAChD,QAAA,GAAG,EAAE,wBAAwB;AAC7B,QAAA,GAAG,EAAE,wBAAwB;AAC7B,QAAA,OAAO,EAAE,gBAAgB;AACzB,QAAA,UAAU,EAAE,gCAAgC;AAC5C,QAAA,OAAO,EAAE,uBAAuB;AAChC,QAAA,OAAO,EAAE,uBAAuB;AAChC,QAAA,QAAQ,EAAE,wBAAwB;AAClC,QAAA,QAAQ,EAAE,wBAAwB;AAClC,QAAA,UAAU,EAAE,oBAAoB;AAChC,QAAA,WAAW,EAAE,wBAAwB;AACrC,QAAA,OAAO,EAAE;AACV,KAAA;AACD,IAAA,IAAI,EAAE;AACJ,QAAA,QAAQ,EAAE,0BAA0B;AACpC,QAAA,KAAK,EAAE,oCAAoC;AAC3C,QAAA,WAAW,EAAE,2BAA2B;AACxC,QAAA,cAAc,EAAE,4EAA4E;AAC5F,QAAA,SAAS,EAAE,oCAAoC;AAC/C,QAAA,SAAS,EAAE,oCAAoC;AAC/C,QAAA,GAAG,EAAE,wBAAwB;AAC7B,QAAA,GAAG,EAAE,wBAAwB;AAC7B,QAAA,OAAO,EAAE,kBAAkB;AAC3B,QAAA,UAAU,EAAE,0CAA0C;AACtD,QAAA,OAAO,EAAE,uBAAuB;AAChC,QAAA,OAAO,EAAE,uBAAuB;AAChC,QAAA,QAAQ,EAAE,wBAAwB;AAClC,QAAA,QAAQ,EAAE,wBAAwB;AAClC,QAAA,UAAU,EAAE,sBAAsB;AAClC,QAAA,WAAW,EAAE,uBAAuB;AACpC,QAAA,OAAO,EAAE;AACV;;AAGH;MACa,kBAAkB,GAAG,IAAI,cAAc,CAAmB,oBAAoB;MAC9E,kBAAkB,GAAG,IAAI,cAAc,CAAe,oBAAoB;MAC1E,0BAA0B,GAAG,IAAI,cAAc,CAAgB,4BAA4B;;MC5H3F,uBAAuB,CAAA;AAC1B,IAAA,MAAM;AACN,IAAA,MAAM;AACN,IAAA,aAAa;AAErB,IAAA,WAAA,GAAA;;AAEE,QAAA,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,EAAE;;QAG5C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC;QAC/C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC;;IAG/D,SAAS,GAAA;QACP,OAAO,IAAI,CAAC,MAAM;;IAGpB,gBAAgB,GAAA;QACd,OAAO,IAAI,CAAC,aAAa;;IAG3B,SAAS,GAAA;QACP,OAAO,IAAI,CAAC,MAAM;;IAGpB,kBAAkB,CAAC,QAAgB,EAAE,UAAgB,EAAA;AACnD,QAAA,IAAI,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,QAA+B,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,