export type FieldType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'textarea' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'date' | 'datetime-local' | 'time' | 'month' | 'week' | 'range' | 'color' | 'location' | 'file' | 'hidden' | 'phone' | 'iso-country' | 'country-dial-code' | 'divider' | 'heading'; export interface SelectOption { label: string; value: any; disabled?: boolean; group?: string; dependsOnValue?: any; } export interface FieldCondition { field: string; operator: 'equals' | 'notEquals' | 'contains' | 'greaterThan' | 'lessThan' | 'exists' | 'notExists'; value?: any; } export interface FieldValidation { required?: boolean; minLength?: number; maxLength?: number; min?: number; max?: number; pattern?: string | RegExp; email?: boolean; custom?: (value: any) => string | null; /** Dynamically required when all (AND) / any (OR) conditions pass — uses field.conditionLogic */ requiredWhen?: FieldCondition | FieldCondition[]; /** This field's numeric value must be <= the value of the named sibling field */ maxField?: string; /** This field's numeric value must be >= the value of the named sibling field */ minField?: string; messages?: { required?: string; minLength?: string; maxLength?: string; min?: string; max?: string; pattern?: string; email?: string; maxField?: string; minField?: string; /** type: 'date' — shown when the value is before field.minDate / after field.maxDate */ minDate?: string; maxDate?: string; }; } /** Auto-populate sibling fields from an API call when this field loses focus */ export interface FieldLookup { /** URL supporting {{fieldKey}} interpolation */ url: string; method?: 'GET' | 'POST'; /** Map: targetSiblingFieldKey -> dotted.path.in.api.response */ populate: Record; /** @default 'blur' */ triggerOn?: 'blur' | 'change'; } /** type: 'select' + remoteSearch — turns an eager `url`-loaded select into a * debounced remote-search typeahead: the field's `url` is called with the * typed text as a query param instead of loading every option upfront. */ export interface RemoteSearchConfig { /** Query param name the typed text is sent under. @default 'q' */ paramName?: string; /** Minimum characters typed before firing a request. @default 3 */ minChars?: number; /** Debounce delay in ms after the user stops typing. @default 300 */ debounceMs?: number; /** Extra static query params sent on every request (e.g. { size: 20 }). */ params?: Record; } /** Async guard executed before advancing to the next stepper step */ export interface StepGuard { url: string; method?: 'GET' | 'POST'; /** Send all current step values as the request body */ withFormValues?: boolean; /** If set, `Object.assign`-patches the next step's FormGroup with the entire API response object */ resultKey?: string; /** Error message shown when the guard API call fails */ errorMessage?: string; } export interface DynamicFormField { key: string; type: FieldType; label?: string; placeholder?: string; hint?: string; defaultValue?: any; disabled?: boolean; readonly?: boolean; hidden?: boolean; options?: SelectOption[] | string; url?: string; /** Label des options API : clé ('codeClient'), chemin pointé ('personnePhysique.nom') * ou modèle de concaténation ('{{codeClient}} - {{telephone}}'). Défaut : 'label'. */ displayName?: string; displayValue?: string; responseDataKey?: string; copyFrom?: string; dependsOn?: string; dependsOnMode?: 'server' | 'client'; dependsOnItemKey?: string; dependsOnOffset?: number; validation?: FieldValidation; /** On blur: call lookup.url and patch sibling fields from the response */ lookup?: FieldLookup; /** type: 'select' only — remote-search typeahead instead of eager-loading `url`. */ remoteSearch?: RemoteSearchConfig; /** Filtre les items reçus de `url` avant leur mapping en {label, value} (ex: ne garder que * status === 'ACTIF'). Même forme que showWhen, mais évalué contre les propriétés BRUTES * de chaque item API (ex: item.status) — pas les valeurs d'un autre champ du formulaire. */ optionsFilter?: FieldCondition | FieldCondition[]; /** Logique entre les conditions de optionsFilter (indépendante de conditionLogic, qui régit showWhen/requiredWhen). @default 'AND' */ optionsFilterLogic?: 'AND' | 'OR'; col?: 1 | 2 | 3 | 4 | 6 | 8 | 9 | 12; cssClass?: string; showWhen?: FieldCondition | FieldCondition[]; conditionLogic?: 'AND' | 'OR'; accept?: string; multiple?: boolean; rows?: number; minDate?: string; maxDate?: string; dateLocale?: string; min?: number; max?: number; step?: number; /** type: 'number' — affiche et saisit le montant avec un séparateur de milliers * (« 20 000 », « 1 250 000,5 ») ; le FormControl garde le nombre (20000). `true` = espace, * ou le séparateur voulu (ex: '.'). Virgule ou point acceptés comme décimale. ← v1.7.21 */ thousandSeparator?: boolean | string; showValue?: boolean; text?: string; level?: 1 | 2 | 3 | 4 | 5 | 6; dividerHeight?: string; dividerWidth?: string; dividerColor?: string; icon?: string; iconSuffix?: string; appearance?: 'outline' | 'fill' | 'simple'; floatLabel?: 'auto' | 'always'; prefix?: string; suffix?: string; mask?: string; fontSize?: string; textAlign?: 'left' | 'center' | 'right'; color?: string; backgroundColor?: string; borderColor?: string; labelColor?: string; fontWeight?: 'normal' | 'bold'; fontStyle?: 'normal' | 'italic'; styles?: Record; uniqueCheck?: { url: string; message?: string; }; defaultCountry?: string; attrs?: Record; } export interface DynamicFormConfig { fields: DynamicFormField[]; submitLabel?: string; resetLabel?: string; showReset?: boolean; showSubmit?: boolean; layout?: 'vertical' | 'horizontal' | 'inline'; cssClass?: string; debug?: boolean; appearance?: 'outline' | 'fill' | 'simple'; floatLabel?: 'auto' | 'always'; /** At least one of these field keys must have a non-empty value for the form to be valid */ atLeastOneOf?: string[]; /** Custom error message when atLeastOneOf constraint is violated */ atLeastOneOfMessage?: string; } export interface FormStep { label: string; icon?: string; description?: string; fields: DynamicFormField[]; /** Async guard: called before advancing to the next step. Blocks advance on API error. */ beforeNext?: StepGuard; /** At least one of these field keys in this step must have a non-empty value */ atLeastOneOf?: string[]; /** Custom error message when atLeastOneOf constraint is violated */ atLeastOneOfMessage?: string; } export interface StepperFormConfig { steps: FormStep[]; submitLabel?: string; showReset?: boolean; resetLabel?: string; linear?: boolean; orientation?: 'horizontal' | 'vertical'; showSummary?: boolean; summaryLabel?: string; cssClass?: string; debug?: boolean; appearance?: 'outline' | 'fill' | 'simple'; floatLabel?: 'auto' | 'always'; } export interface DynamicFormSubmitEvent { value: Record; rawValue: Record; }