UNPKG

signal-template-forms

Version:

A powerful, type-safe Angular forms library built with signals, providing reactive form management with excellent developer experience and performance.

4,625 lines 331 kB
import * as i0 from '@angular/core';
import { Injectable, inject, Injector, signal, effect, input, computed, ElementRef, Renderer2, HostListener, HostBinding, Directive, output, viewChildren, ChangeDetectionStrategy, Component, ApplicationRef, InjectionToken, Inject, Optional, viewChild, DestroyRef, ViewEncapsulation, model } from '@angular/core';
import { timer, firstValueFrom, from, fromEvent, tap as tap$1, isObservable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import * as i1 from 'lucide-angular';
import { CircleCheck, CircleX, LucideAngularModule, PlusCircleIcon, MinusCircleIcon, ChevronDownCircleIcon, ChevronUpCircleIcon, SquareCheck } from 'lucide-angular';
import * as i2 from '@angular/common';
import { DOCUMENT, NgClass, CommonModule, NgComponentOutlet, NgTemplateOutlet } from '@angular/common';
import { trigger, state, transition, style, animate } from '@angular/animations';

var FormFieldType;
(function (FormFieldType) {
    FormFieldType["AUTOCOMPLETE"] = "autocomplete";
    FormFieldType["CHECKBOX"] = "checkbox";
    FormFieldType["CHECKBOX_GROUP"] = "checkbox-group";
    FormFieldType["CHIPLIST"] = "chiplist";
    FormFieldType["COLOR"] = "color";
    FormFieldType["DATETIME"] = "datetime";
    FormFieldType["FILE"] = "file";
    FormFieldType["MULTISELECT"] = "multiselect";
    FormFieldType["NUMBER"] = "number";
    FormFieldType["PASSWORD"] = "password";
    FormFieldType["RADIO"] = "radio";
    FormFieldType["RATING"] = "rating";
    FormFieldType["REPEATABLE_GROUP"] = "repeatable-group";
    FormFieldType["SELECT"] = "select";
    FormFieldType["SLIDER"] = "slider";
    FormFieldType["SWITCH"] = "switch";
    FormFieldType["TEXT"] = "text";
    FormFieldType["TEXTAREA"] = "textarea";
})(FormFieldType || (FormFieldType = {}));

class ValueHelper {
    static isNull(value) {
        return value === null || value === undefined;
    }
    static isString(value) {
        return typeof value === 'string';
    }
    static isNumber(value) {
        return typeof value === 'number' && !isNaN(value);
    }
    static isBoolean(value) {
        return typeof value === 'boolean';
    }
    static isDate(value) {
        return value instanceof Date && !isNaN(value.getTime());
    }
    static isObject(value) {
        return typeof value === 'object' && value !== null;
    }
    static hasValueProperty(value) {
        return ValueHelper.isObject(value) && 'value' in value;
    }
    static isCheckboxInput(element) {
        return element instanceof HTMLInputElement && element.type === 'checkbox';
    }
    static isDateInput(element) {
        return element instanceof HTMLInputElement && element.type === 'date';
    }
    static isNumberInput(element) {
        return element instanceof HTMLInputElement && element.type === 'number';
    }
    static isComboboxDiv(element) {
        return (element instanceof HTMLDivElement &&
            element.getAttribute('role') === 'combobox');
    }
    static isFormElement(element) {
        return (element instanceof HTMLInputElement ||
            element instanceof HTMLSelectElement ||
            element instanceof HTMLTextAreaElement);
    }
    static extractValueString(value) {
        if (ValueHelper.isNull(value)) {
            return '';
        }
        if (ValueHelper.isString(value)) {
            return value;
        }
        if (ValueHelper.isNumber(value) || ValueHelper.isBoolean(value))
            return String(value);
        if (ValueHelper.hasValueProperty(value)) {
            return String(value.label);
        }
        return String(value);
    }
}

class FieldRoleAttributesService {
    getAttributesForField(field) {
        const baseAttributes = this.getBaseAttributes(field);
        const roleAttributes = this.getRoleSpecificAttributes(field);
        const typeAttributes = this.getTypeSpecificAttributes(field);
        return {
            role: roleAttributes.role,
            ariaAttributes: {
                ...baseAttributes.ariaAttributes,
                ...roleAttributes.ariaAttributes,
                ...typeAttributes.ariaAttributes,
            },
            inputAttributes: {
                ...baseAttributes.inputAttributes,
                ...roleAttributes.inputAttributes,
                ...typeAttributes.inputAttributes,
            },
        };
    }
    getBaseAttributes(field) {
        const name = String(field.name);
        return {
            ariaAttributes: {
                'aria-invalid': field.error() ? 'true' : 'false',
                'aria-describedby': field.error() ? `${name}-error` : null,
            },
            inputAttributes: {
                disabled: field.isDisabled() ? true : null,
                placeholder: this.getPlaceholder(field),
            },
        };
    }
    /**
     * Generates intelligent default placeholders based on field type and name
     */
    getPlaceholder(field) {
        // If user provided a custom placeholder, use it
        if (field.config?.placeholder) {
            return field.config.placeholder;
        }
        // Generate smart defaults based on field type
        const fieldName = String(field.name);
        const label = field.label || fieldName;
        switch (field.type) {
            case FormFieldType.TEXT:
            case FormFieldType.PASSWORD:
            case FormFieldType.TEXTAREA:
            case FormFieldType.NUMBER:
                return `Type ${label.toLowerCase()} here`;
            case FormFieldType.SELECT:
            case FormFieldType.AUTOCOMPLETE:
                return `Select a ${label.toLowerCase()}`;
            case FormFieldType.MULTISELECT:
            case FormFieldType.CHIPLIST:
                return `Select ${label.toLowerCase()}`;
            case FormFieldType.DATETIME:
                return `Select ${label.toLowerCase()}`;
            case FormFieldType.FILE:
                return `Choose ${label.toLowerCase()} file`;
            case FormFieldType.COLOR:
                return `Pick a color for ${label.toLowerCase()}`;
            // These field types don't typically use placeholders
            case FormFieldType.CHECKBOX:
            case FormFieldType.CHECKBOX_GROUP:
            case FormFieldType.RADIO:
            case FormFieldType.SWITCH:
            case FormFieldType.SLIDER:
            case FormFieldType.RATING:
                return null;
            default:
                return `Enter ${label.toLowerCase()}`;
        }
    }
    getRoleSpecificAttributes(field) {
        const name = String(field.name);
        switch (field.type) {
            case FormFieldType.SELECT:
            case FormFieldType.AUTOCOMPLETE:
                return {
                    role: 'combobox',
                    ariaAttributes: {
                        'aria-owns': `${name}-listbox`,
                        'aria-controls': `${name}-listbox`,
                        'aria-haspopup': 'listbox',
                    },
                    inputAttributes: {},
                };
            case FormFieldType.CHECKBOX_GROUP:
                return {
                    role: 'group',
                    ariaAttributes: {
                        'aria-labelledby': `${name}-legend`,
                    },
                    inputAttributes: {},
                };
            case FormFieldType.RADIO:
                return {
                    role: 'radiogroup',
                    ariaAttributes: {
                        'aria-labelledby': `${name}-legend`,
                    },
                    inputAttributes: {},
                };
            case FormFieldType.SWITCH:
                const fieldValue = field.value();
                let ariaChecked = null;
                if (fieldValue === null || fieldValue === undefined) {
                    ariaChecked = null;
                }
                else if (typeof fieldValue === 'boolean') {
                    ariaChecked = fieldValue ? 'true' : 'false';
                }
                else if (fieldValue) {
                    ariaChecked = String(fieldValue);
                }
                else {
                    ariaChecked = String(fieldValue);
                }
                return {
                    role: 'switch',
                    ariaAttributes: {
                        'aria-checked': ariaChecked,
                    },
                    inputAttributes: {
                        checked: Boolean(fieldValue),
                    },
                };
            case FormFieldType.SLIDER:
                const min = String(field.config?.min ?? 0);
                const max = String(field.config?.max ?? 100);
                const step = String(field.config?.step ?? 1);
                const value = String(field.value() ?? 0);
                return {
                    role: 'slider',
                    ariaAttributes: {
                        'aria-valuemin': min,
                        'aria-valuemax': max,
                        'aria-valuenow': value,
                    },
                    inputAttributes: {
                        min,
                        max,
                        step,
                    },
                };
            case FormFieldType.RATING:
                return {
                    role: 'radiogroup',
                    ariaAttributes: {
                        'aria-valuemin': '0',
                        'aria-valuemax': field.config?.max?.toString() ?? '5',
                        'aria-valuenow': field.value()?.toString() ?? '0',
                        'aria-label': field.label,
                        'aria-invalid': field.error() ? 'true' : 'false',
                        'aria-describedby': field.error()
                            ? `error-${String(field.name)}`
                            : `hint-${String(field.name)}`,
                    },
                    inputAttributes: {},
                };
            case FormFieldType.FILE:
                const accept = Array.isArray(field.config?.accept)
                    ? field.config.accept.join(',')
                    : null;
                return {
                    role: 'button',
                    ariaAttributes: {
                        'aria-label': field.config?.uploadText ?? 'Click or drag a file to upload',
                    },
                    inputAttributes: {
                        accept,
                    },
                };
            case FormFieldType.MULTISELECT:
                return {
                    role: 'listbox',
                    ariaAttributes: {
                        'aria-label': field.label,
                        'aria-multiselectable': 'true',
                    },
                    inputAttributes: {},
                };
            default:
                return {
                    ariaAttributes: {},
                    inputAttributes: {},
                };
        }
    }
    getTypeSpecificAttributes(field) {
        switch (field.type) {
            case FormFieldType.PASSWORD:
                return {
                    ariaAttributes: {
                        'aria-label': `Password field for ${field.label}`,
                    },
                    inputAttributes: {
                        autocomplete: 'current-password',
                    },
                };
            case FormFieldType.COLOR:
                return {
                    ariaAttributes: {
                        'aria-label': `Color picker for ${field.label}`,
                    },
                    inputAttributes: {},
                };
            case FormFieldType.DATETIME:
                return {
                    ariaAttributes: {},
                    inputAttributes: {},
                };
            default:
                return {
                    ariaAttributes: {},
                    inputAttributes: {},
                };
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FieldRoleAttributesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FieldRoleAttributesService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FieldRoleAttributesService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

class ValidationService {
    injector = inject(Injector);
    validatedFields = new Map();
    // Blur refresh signal to track blur events
    blurRefresh = signal(null);
    constructor() {
        this.setupSyncValidationEffect();
        this.setupBlurValidationEffect();
    }
    /**
     * Trigger blur validation for a specific field
     * This creates a refresh signal that triggers validation
     */
    triggerBlurValidation(fieldPath) {
        this.blurRefresh.set(fieldPath);
        // Reset after a short delay to allow for multiple triggers
        setTimeout(() => {
            if (this.blurRefresh() === fieldPath) {
                this.blurRefresh.set(null);
            }
        }, 50);
    }
    setupSyncValidationEffect() {
        effect(() => {
            this.validatedFields.forEach(({ field, form, config }) => {
                const value = field.value();
                if (config.trigger === 'change') {
                    this.runSyncValidation(field, value, form);
                }
                if (config.trigger === 'change' && field.asyncValidators?.length) {
                    timer(config.debounceMs || 300).subscribe(() => {
                        this.runAsyncValidation(field, value, form);
                    });
                }
            });
        }, { injector: this.injector });
    }
    setupBlurValidationEffect() {
        effect(() => {
            const blurredFieldPath = this.blurRefresh();
            if (!blurredFieldPath) {
                return;
            }
            const fieldData = this.validatedFields.get(blurredFieldPath);
            if (!fieldData) {
                return;
            }
            const { field, form, config } = fieldData;
            if (config.trigger === 'blur') {
                // Mark as touched since blur occurred
                field.touched.set(true);
                // Run validation
                this.runSyncValidation(field, field.value(), form);
                if (field.asyncValidators?.length) {
                    this.runAsyncValidation(field, field.value(), form);
                }
            }
        }, { injector: this.injector });
    }
    /**
     * Register a field for validation
     */
    setupFieldValidation(field, form) {
        const fieldPath = field.path;
        const config = this.getValidationConfig(field);
        if (this.validatedFields.has(fieldPath)) {
            return;
        }
        this.validatedFields.set(fieldPath, { field, form, config });
        if (config.validateAsyncOnInit && field.asyncValidators?.length) {
            timer(0).subscribe(() => {
                this.runAsyncValidation(field, field.value(), form);
            });
        }
    }
    /**
     * Get the effective validation configuration for a field
     */
    getValidationConfig(field) {
        const fieldConfig = field.validationConfig;
        const configFromFieldConfig = field.config?.validation;
        return {
            trigger: fieldConfig?.trigger || configFromFieldConfig?.trigger || 'change',
            debounceMs: fieldConfig?.debounceMs || configFromFieldConfig?.debounceMs || 300,
            validateAsyncOnInit: fieldConfig?.validateAsyncOnInit ||
                configFromFieldConfig?.validateAsyncOnInit ||
                false,
        };
    }
    /**
     * Run synchronous validation
     */
    runSyncValidation(field, value, form) {
        const validators = field.validators ?? [];
        for (const validator of validators) {
            const error = validator(value, form);
            if (error) {
                field.error.set(error);
                return;
            }
        }
        field.error.set(null);
    }
    /**
     * Run asynchronous validation
     */
    runAsyncValidation(field, value, form) {
        const asyncValidators = field.asyncValidators ?? [];
        if (asyncValidators.length === 0) {
            return;
        }
        field.validating.set(true);
        field.asyncError.set(null);
        // Run all async validators in parallel
        const validationPromises = asyncValidators.map((validator) => {
            const result = validator(value, form);
            return result instanceof Promise ? result : firstValueFrom(result);
        });
        from(Promise.all(validationPromises))
            .pipe(tap((results) => {
            // Find the first error
            const firstError = results.find((result) => result !== null);
            field.asyncError.set(firstError || null);
            field.validating.set(false);
        }))
            .subscribe({
            error: (error) => {
                console.error('Async validation error:', error);
                field.asyncError.set('Validation failed');
                field.validating.set(false);
            },
        });
    }
    /**
     * Manually trigger validation for a field
     */
    triggerValidation(field, trigger = 'submit') {
        const value = field.value();
        const fieldData = this.validatedFields.get(field.path);
        if (fieldData) {
            if (trigger === 'submit' || fieldData.config.trigger === 'change') {
                this.runSyncValidation(field, value, fieldData.form);
            }
            if (trigger === 'submit' && field.asyncValidators?.length) {
                this.runAsyncValidation(field, value, fieldData.form);
            }
        }
    }
    /**
     * Get the combined error for a field (sync + async)
     */
    getCombinedError(field) {
        const hasAsyncValidation = 'asyncError' in field && 'validating' in field;
        if (hasAsyncValidation && 'error' in field) {
            const syncError = field.error();
            const asyncError = field.asyncError();
            return syncError || asyncError;
        }
        if ('error' in field) {
            const error = field.error();
            if (typeof error === 'boolean') {
                return error ? 'Field has errors' : null;
            }
            return error;
        }
        return null;
    }
    /**
     * Check if a field is currently validating or has any errors
     */
    isFieldInvalid(field) {
        // Check if field has async validation properties
        const hasAsyncValidation = 'asyncError' in field && 'validating' in field;
        if (hasAsyncValidation && 'error' in field) {
            const error = field.error();
            const asyncError = field.asyncError();
            const validating = field.validating();
            return !!(error || asyncError || validating);
        }
        // For fields without async validation, just check sync error
        if ('error' in field) {
            const error = field.error();
            // Handle both string | null and boolean error types
            if (typeof error === 'boolean') {
                return error;
            }
            return !!error;
        }
        return false;
    }
    /**
     * Validate all fields for submit trigger and return if form is valid
     */
    validateFormForSubmit(fields, form) {
        let valid = true;
        for (const field of fields) {
            field.touched.set(true);
            if (this.isFieldWithForm(field)) {
                const nestedValid = field.form.validateForm();
                valid = valid && nestedValid;
                continue;
            }
            if (this.isRepeatableField(field)) {
                const nestedForms = field.repeatableForms();
                const allValid = nestedForms.every((form) => form.validateForm());
                valid = valid && allValid;
                continue;
            }
            const fieldData = this.validatedFields.get(field.path);
            if (fieldData) {
                this.runSyncValidation(field, field.value(), form);
                if (field.error()) {
                    valid = false;
                }
            }
            else {
                const validators = field.validators ?? [];
                for (const validator of validators) {
                    const error = validator(field.value(), form);
                    if (error) {
                        field.error.set(error);
                        valid = false;
                        break;
                    }
                    else {
                        field.error.set(null);
                    }
                }
            }
        }
        return valid;
    }
    isFieldWithForm(field) {
        return 'form' in field && field.form !== undefined;
    }
    isRepeatableField(field) {
        return 'repeatableForms' in field && field.repeatableForms !== undefined;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ValidationService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ValidationService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: () => [] });

/**
 * Signal-based form field directive that handles two-way data binding, validation,
 * and accessibility attributes for form elements
 *
 * @template TModel - The form model type
 * @template K - The field key type
 * @template TFieldType - The form field type enum
 */
class SignalModelDirective {
    signalModel = input.required();
    initialValue = signal(null);
    hasCapturedInitial = signal(false);
    roleAttributes = computed(() => this.roleService.getAttributesForField(this.signalModel()));
    elementRef = inject((ElementRef));
    renderer = inject(Renderer2);
    injector = inject(Injector);
    roleService = inject(FieldRoleAttributesService);
    validationService = inject(ValidationService);
    form = computed(() => this.signalModel().getForm());
    /**
     * Binds the field name as the element's name attribute
     * @returns The field name as a string
     */
    get name() {
        return String(this.signalModel().name);
    }
    /**
     * Binds the field name as the element's id attribute
     * @returns The field name as a string
     */
    get id() {
        const baseName = String(this.signalModel().name);
        // For radio buttons, we need unique IDs within the group
        if (this.elementRef.nativeElement.type === 'radio') {
            return this.generateUniqueRadioId(baseName);
        }
        return baseName;
    }
    /**
     * Generates a unique ID for radio buttons by checking existing IDs in the DOM
     */
    generateUniqueRadioId(baseName) {
        // Check if any other radio with the same name already exists
        const existingRadios = document.querySelectorAll(`input[name="${baseName}"]`);
        const existingIds = Array.from(existingRadios).map((radio) => radio.id);
        // Generate ID with index
        let index = 0;
        let candidateId = `${baseName}_${index}`;
        while (existingIds.includes(candidateId)) {
            index++;
            candidateId = `${baseName}_${index}`;
        }
        return candidateId;
    }
    /**
     * Initializes the directive by setting up all reactive effects
     */
    constructor() {
        this.setupInitialValueEffect();
        this.setupFocusEffect();
        this.setupValueSyncEffect();
        this.setupAttributesEffect();
    }
    /**
     * Angular lifecycle hook that sets the initial element value
     */
    ngOnInit() {
        this.setInitialValue();
    }
    /**
     * Sets up effect to capture the initial field value for dirty state tracking
     */
    setupInitialValueEffect() {
        effect(() => {
            if (!this.hasCapturedInitial() && this.signalModel().value()) {
                this.initialValue.set(this.signalModel().value());
                this.hasCapturedInitial.set(true);
            }
        }, { injector: this.injector });
    }
    /**
     * Sets up effect to handle focus highlighting behavior and auto-focus
     */
    setupFocusEffect() {
        effect(() => {
            if (!this.signalModel().focus())
                return;
            const native = this.elementRef.nativeElement;
            native.focus?.();
            this.renderer.addClass(native, 'form-error-highlight');
            setTimeout(() => {
                this.renderer.removeClass(native, 'form-error-highlight');
                this.signalModel().focus.set(false);
            }, 800);
        }, { injector: this.injector });
    }
    /**
     * Sets up effect to sync element value with model value changes
     */
    setupValueSyncEffect() {
        effect(() => {
            const value = this.signalModel().value();
            this.setElementValue(this.elementRef.nativeElement, value);
        }, { injector: this.injector });
    }
    /**
     * Sets up effect to dynamically apply all field attributes (role, aria, input)
     */
    setupAttributesEffect() {
        effect(() => {
            const attrs = this.roleAttributes();
            const el = this.elementRef.nativeElement;
            if (attrs.role) {
                this.renderer.setAttribute(el, 'role', attrs.role);
            }
            Object.entries(attrs.ariaAttributes).forEach(([key, value]) => {
                if (value !== null && value !== undefined) {
                    this.renderer.setAttribute(el, key, String(value));
                    return;
                }
                this.renderer.removeAttribute(el, key);
            });
            Object.entries(attrs.inputAttributes).forEach(([key, value]) => {
                if (value !== null && value !== undefined) {
                    this.renderer.setAttribute(el, key, String(value));
                    return;
                }
                this.renderer.removeAttribute(el, key);
            });
            const fieldValue = this.getFieldValue();
            if (fieldValue !== null) {
                this.renderer.setAttribute(el, 'value', fieldValue);
            }
        }, { injector: this.injector });
    }
    /**
     * Sets the initial element value from the field model
     */
    setInitialValue() {
        this.setElementValue(this.elementRef.nativeElement, this.signalModel().value());
    }
    /**
     * Extracts the field value as a display string
     * @returns The field value formatted as a string, or null if no value
     */
    getFieldValue() {
        const value = this.signalModel().value();
        return ValueHelper.extractValueString(value);
    }
    /**
     * Handles blur events by marking the field as touched and triggering blur validation
     */
    onBlur() {
        const field = this.signalModel();
        field.touched.set(true);
        // Trigger blur validation through the validation service
        this.validationService.triggerBlurValidation(field.path);
    }
    /**
     * Handles focus events by updating the field focus state
     */
    onFocus() {
        this.signalModel().focus.set(true);
    }
    /**
     * Handles input events and updates the model value with parsed data
     */
    onInput(event) {
        const target = event.target;
        const raw = this.extractValue(target);
        const parsed = this.signalModel().parser?.(raw) ?? raw;
        this.signalModel().value.set(parsed);
        const dirty = JSON.stringify(parsed) !== JSON.stringify(this.initialValue());
        this.signalModel().dirty.set(dirty);
        this.signalModel().touched.set(true);
    }
    /**
     * Extracts the raw value from different types of form elements
     * @param target - The form element to extract value from
     * @returns The extracted value based on element type
     */
    extractValue(target) {
        if (target instanceof HTMLInputElement) {
            const raw = target.value;
            switch (target.type) {
                case 'checkbox':
                    return target.checked;
                case 'number':
                case 'range':
                    const parsed = parseFloat(raw);
                    return isNaN(parsed) ? 0 : parsed;
                case 'date':
                    return target.valueAsDate;
                case 'radio':
                    if (this.signalModel().type === FormFieldType.RADIO) {
                        const config = this.signalModel().config;
                        if (config?.valueType === 'number') {
                            return parseFloat(raw);
                        }
                        if (config?.valueType === 'boolean') {
                            return raw === 'true';
                        }
                    }
                    return raw;
                default:
                    return raw;
            }
        }
        if (target instanceof HTMLSelectElement ||
            this.signalModel().type === FormFieldType.AUTOCOMPLETE) {
            const raw = target.value;
            const field = this.signalModel();
            if (field.type === FormFieldType.SELECT) {
                if ('options' in field) {
                    const foundOption = field.options().find((opt) => String(opt.label) === raw);
                    if (foundOption) {
                        return foundOption.label;
                    }
                }
                const config = field.config;
                if (config?.valueType === 'number') {
                    return parseFloat(raw);
                }
                if (config?.valueType === 'boolean') {
                    return raw === 'true';
                }
            }
            return raw;
        }
        if (target instanceof HTMLTextAreaElement) {
            return target.value;
        }
        return null;
    }
    /**
     * Sets the value of a form element based on its type
     * @param element - The form element to update
     * @param value - The value to set
     */
    setElementValue(element, value) {
        if (ValueHelper.isCheckboxInput(element)) {
            this.setCheckboxValue(element, Boolean(value));
            return;
        }
        if (ValueHelper.isDateInput(element) && ValueHelper.isDate(value)) {
            this.setDateValue(element, value);
            return;
        }
        if (ValueHelper.isNumberInput(element) && ValueHelper.isNumber(value)) {
            this.setNumberValue(element, value);
            return;
        }
        if (ValueHelper.isFormElement(element)) {
            this.setStandardValue(element, value);
            return;
        }
        if (ValueHelper.isComboboxDiv(element)) {
            this.setComboboxValue(element, value);
            return;
        }
    }
    /**
     * Sets the checked state of a checkbox input
     * @param element - The checkbox input element
     * @param value - The boolean value to set
     */
    setCheckboxValue(element, value) {
        element.checked = value;
    }
    /**
     * Sets the date value of a date input
     * @param element - The date input element
     * @param value - The Date object to set
     */
    setDateValue(element, value) {
        element.valueAsDate = value;
    }
    /**
     * Sets the numeric value of a number input
     * @param element - The number input element
     * @param value - The number value to set
     */
    setNumberValue(element, value) {
        element.valueAsNumber = value;
    }
    /**
     * Sets the string value of standard form elements
     * @param element - The form element (input, select, textarea)
     * @param value - The value to convert to string and set
     */
    setStandardValue(element, value) {
        element.value = ValueHelper.extractValueString(value);
    }
    /**
     * Sets accessibility attributes for custom combobox elements
     * @param element - The div element with combobox role
     * @param value - The value to set as aria-valuenow
     */
    setComboboxValue(element, value) {
        const valueString = ValueHelper.extractValueString(value);
        if (valueString) {
            this.renderer.setAttribute(element, 'aria-valuenow', valueString);
        }
        else {
            this.renderer.removeAttribute(element, 'aria-valuenow');
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalModelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: SignalModelDirective, isStandalone: true, selector: "[signalModel]", inputs: { signalModel: { classPropertyName: "signalModel", publicName: "signalModel", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "blur": "onBlur()", "focus": "onFocus()", "input": "onInput($event)" }, properties: { "attr.name": "this.name", "attr.id": "this.id" } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalModelDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[signalModel]',
                    standalone: true,
                }]
        }], ctorParameters: () => [], propDecorators: { name: [{
                type: HostBinding,
                args: ['attr.name']
            }], id: [{
                type: HostBinding,
                args: ['attr.id']
            }], onBlur: [{
                type: HostListener,
                args: ['blur']
            }], onFocus: [{
                type: HostListener,
                args: ['focus']
            }], onInput: [{
                type: HostListener,
                args: ['input', ['$event']]
            }] } });

var FormStatus;
(function (FormStatus) {
    FormStatus["Error"] = "Error";
    FormStatus["Idle"] = "Idle";
    FormStatus["Submitting"] = "Submitting";
    FormStatus["Success"] = "Success";
})(FormStatus || (FormStatus = {}));

var NumberInputType;
(function (NumberInputType) {
    NumberInputType["STANDARD"] = "standard";
    NumberInputType["CURRENCY"] = "currency";
    NumberInputType["PERCENTAGE"] = "percentage";
    NumberInputType["DECIMAL"] = "decimal";
    NumberInputType["INTEGER"] = "integer";
    NumberInputType["UNIT_CONVERSION"] = "unitConversion";
})(NumberInputType || (NumberInputType = {}));

class FormEngine {
    static validateForm(fields, form, validationService) {
        return () => {
            if (validationService &&
                typeof validationService.validateFormForSubmit === 'function') {
                let valid = true;
                for (const field of fields) {
                    if (this.isFieldWithForm(field)) {
                        const nestedValid = field.form.validateForm();
                        valid = valid && nestedValid;
                        continue;
                    }
                    if (this.isRepeatableField(field)) {
                        const nestedForms = field.repeatableForms();
                        const allValid = nestedForms.every((form) => form.validateForm());
                        valid = valid && allValid;
                        continue;
                    }
                }
                const fieldsValid = validationService.validateFormForSubmit(fields, form);
                return valid && fieldsValid;
            }
            let valid = true;
            for (const field of fields) {
                if (this.isFieldWithForm(field)) {
                    const nestedValid = field.form.validateForm();
                    valid = valid && nestedValid;
                    continue;
                }
                if (this.isRepeatableField(field)) {
                    const nestedForms = field.repeatableForms();
                    const allValid = nestedForms.every((form) => form.validateForm());
                    valid = valid && allValid;
                    continue;
                }
                field.touched.set(true);
                const validators = field.validators ?? [];
                for (const validator of validators) {
                    const error = validator(field.value(), form);
                    if (error) {
                        field.error.set(error);
                        valid = false;
                        break;
                    }
                    else {
                        field.error.set(null);
                    }
                }
            }
            return valid;
        };
    }
    static resetForm(fields, initialModel) {
        return () => {
            for (const field of fields) {
                if (this.isFieldWithForm(field)) {
                    field.form.reset();
                    continue;
                }
                const initialValue = initialModel[field.name];
                field.value.set(initialValue);
                field.touched.set(false);
                field.dirty.set(false);
                field.error.set(null);
                field.focus.set(false);
            }
        };
    }
    static patchForm(fields) {
        return (patch) => {
            for (const field of fields) {
                const fieldName = field.name;
                const newValue = patch[fieldName];
                if (newValue === undefined)
                    continue;
                if (this.isFieldWithForm(field) && typeof newValue === 'object') {
                    field.form.patchValue(newValue);
                }
                else {
                    field.value.set(newValue);
                    field.dirty.set(true);
                }
            }
        };
    }
    static setFormValue(fields) {
        return (value) => {
            for (const field of fields) {
                const fieldName = field.name;
                const newValue = value[fieldName];
                if (this.isFieldWithForm(field) && typeof newValue === 'object') {
                    field.form.setValue(newValue);
                }
                else {
                    field.value.set(newValue);
                    field.dirty.set(true);
                }
            }
        };
    }
    static getErrors(fields) {
        return () => {
            const errors = [];
            for (const field of fields) {
                if (this.isFieldWithForm(field)) {
                    const nestedErrors = field.form.getErrors();
                    const updatedNestedErrors = nestedErrors.map((err) => ({
                        ...err,
                        path: `${field.path}.${err.path}`,
                    }));
                    errors.push(...updatedNestedErrors);
                    continue;
                }
                if (this.isRepeatableField(field)) {
                    const nestedForms = field.repeatableForms();
                    nestedForms.forEach((form) => {
                        const nestedErrors = form.getErrors();
                        const updatedNestedErrors = nestedErrors.map((err) => ({
                            name: err.name,
                            message: err.message,
                            path: err.path,
                            field: err.field,
                            focusField: err.focusField,
                            trigger: err.trigger,
                        }));
                        errors.push(...updatedNestedErrors);
                    });
                    continue;
                }
                if (field.error()) {
                    const trigger = field.validationConfig?.trigger ||
                        field.config?.validation?.trigger ||
                        'change';
                    errors.push({
                        name: field.name,
                        message: field.error() ?? '',
                        path: field.path,
                        field: field,
                        trigger,
                        focusField: () => {
                            if (field.focus) {
                                field.focus.set(true);
                            }
                        },
                    });
                }
            }
            return errors;
        };
    }
    static getValueFromFields(fields, form) {
        return fields.reduce((acc, field) => {
            const name = field.name;
            const disabled = typeof field.disabled === 'function'
                ? field.disabled(form)
                : field.disabled;
            if (!disabled) {
                acc[name] = this.getFieldOutputValue(field);
            }
            return acc;
        }, {});
    }
    static getFieldOutputValue(field) {
        if (this.isRepeatableField(field)) {
            return field.repeatableForms().map((form) => form.getValue());
        }
        if (this.isCheckboxGroupField(field)) {
            const val = field.value();
            const valueType = field.valueType ?? 'array';
            if (valueType === 'map') {
                return val;
            }
            if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
                return Object.entries(val)
                    .filter(([, checked]) => checked)
                    .map(([key]) => key);
            }
            return val;
        }
        if (this.isFieldWithForm(field)) {
            return field.form.getValue();
        }
        return field.value();
    }
    static getValue(fields) {
        return () => fields.reduce((acc, field) => {
            acc[field.name] = field.value();
            return acc;
        }, {});
    }
    static getField(fields) {
        return (key) => {
            const field = fields.find((f) => f.name === key);
            if (!field) {
                throw new Error(`Field ${String(key)} not found`);
            }
            // Handle repeatable fields
            if (this.isRepeatableField(field)) {
                return field;
            }
            // Handle nested form fields
            if (this.isFieldWithForm(field)) {
                return field;
            }
            // Handle regular fields
            return field;
        };
    }
    static getRawValue(fields) {
        return computed(() => {
            return fields.reduce((acc, field) => {
                acc[field.name] = field.value();
                return acc;
            }, {});
        });
    }
    static runSaveHandler(fields, status, form, onSave) {
        return () => {
            const isValid = this.validateForm(fields, form);
            if (!isValid()) {
                status.set(FormStatus.Error);
                return;
            }
            status.set(FormStatus.Submitting);
            try {
                const getValue = this.getValue(fields);
                onSave?.(getValue());
                status.set(FormStatus.Success);
                for (const field of fields) {
                    if (this.isFieldWithForm(field)) {
                        field.form.save();
                    }
                    else {
                        field.touched.set(false);
                        field.dirty.set(false);
                        if (form.config?.disableUponComplete) {
                            field.disabled = () => true;
                        }
                    }
                }
                if (!form.config?.disableUponComplete) {
                    setTimeout(() => status.set(FormStatus.Idle), 2000);
                }
            }
            catch {
                status.set(FormStatus.Error);
            }
        };
    }
    static isFieldWithForm(field) {
        return 'form' in field && field.form !== undefined;
    }
    static isRepeatableField(field) {
        return 'repeatableForms' in field && field.repeatableForms !== undefined;
    }
    static isCheckboxGroupField(field) {
        return field.type === FormFieldType.CHECKBOX_GROUP;
    }
}

class FieldUtils {
    /**
     * Creates default form configuration with theme settings
     * @param config - Optional user configuration to merge with defaults
     * @returns Complete configuration with defaults applied
     */
    static createDefaultConfig(config) {
        const defaults = {
            layout: 'flex',
            theme: 'light',
            allowDarkMode: false,
        };
        if (!config) {
            return defaults;
        }
        // Apply theme defaults based on allowDarkMode setting
        const themeDefaults = {
            theme: config.allowDarkMode ? 'auto' : 'light',
            allowDarkMode: config.allowDarkMode ?? defaults.allowDarkMode,
        };
        return {
            ...defaults,
            ...config,
            ...themeDefaults,
        };
    }
    static anyTouched(fields) {
        return computed(() => {
            return fields.some((field) => {
                if (this.isFieldWithForm(field)) {
                    return field.form.anyTouched();
                }
                if (this.isRepeatableField(field)) {
                    if (field.touched()) {
                        return true;
                    }
                    return field.repeatableForms().some((form) => form.anyTouched());
                }
                return field.touched();
            });
        });
    }
    static hasSaved(form) {
        return computed(() => {
            return (!form.anyTouched() &&
                !form.anyDirty() &&
                form.status() === FormStatus.Success);
        });
    }
    static anyDirty(fields) {
        return computed(() => {
            return fields.some((field) => {
                if (this.isFieldWithForm(field)) {
                    return field.form.anyDirty();
                }
                if (this.isRepeatableField(field)) {
                    if (field.dirty()) {
                        return true;
                    }
                    return field.repeatableForms().some((form) => form.anyDirty());
                }
                return field.dirty();
            });
        });
    }
    static isFieldWithForm(field) {
        return 'form' in field && field.form !== undefined;
    }
    static isRepeatableField(field) {
        return 'repeatableForms' in field && field.repeatableForms !== undefined;
    }
}

// field-factory.ts
/**
 * FieldFactory - Factory class for building Signal Form fields
 *
 * Responsible for converting field builder configurations into fully
 * initialized SignalFormField instances with reactive state management.
 * Handles different field types including nested forms, repeatable groups,
 * and fields with static or computed options.
 *
 * Features:
 * - Type-safe field construction with TypeScript generics
 * - Automatic reactive state binding (value, error, validation, etc.)
 * - Support for nested form groups and repeatable field arrays
 * - Dynamic and computed options for select-type fields
 * - Path tracking for nested field references
 *
 * @example
 * ```typescript
 * const textField = FieldFactory.build(
 *   { name: 'email', type: FormFieldType.TEXT, label: 'Email' },
 *   { email: '' },
 *   formContainer,
 *   'user'
 * );
 * ```
 */
class FieldFactory {
    /**
     * Builds a complete SignalFormField from a field configuration
     *
     * Converts field builder input into a reactive field with full state management.
     * Automatically detects field type (normal, nested group, or repeatable group)
     * and creates appropriate field structure with computed properties and signals.
     *
     * @template TModel - The TypeScript type of the form data model
     * @param field - Field configuration from form builder
     * @param model - Current form data model containing initial values
     * @param formRef - Reference to the parent form container
     * @param parentPath - Path prefix for nested fields (e.g., "user.address")
     * @returns Fully initialized SignalFormField with reactive state
     *
     * @example
     * ```typescript
     * // Build a simple text field
     * const nameField = FieldFactory.build(
     *   { name: 'firstName', type: FormFieldType.TEXT, label: 'First Name' },
     *   { firstName: 'John' },
     *   formContainer
     * );
     *
     * // Build a select field with options
     * const countryField = FieldFactory.build(
     *   {
     *     name: 'country',
     *     type: FormFieldType.SELECT,
     *     label: 'Country',
     *     options: [{ value: 'US', label: 'United States' }]
     *   },
     *   { country: 'US' },
     *   formContainer
     * );
     * ```
     */
    static build(field, model, formRef, parentPath = '') {
        const rawValue = model[field.name];
        const referencePath = parentPath
            ? `${parentPath}.${String(field.name)}`
            : `${String(field.name)}`;
        // Create base reactive state that all fields share
        const baseFieldState = {
            path: referencePath,
            error: signal(null),
            asyncError: signal(null),
            validating: signal(false),
            touched: signal(false),
            dirty: signal(false),
            focus: signal(false),
            value: signal(rawValue),
            getForm: () => formRef,
            isDisabled: computed(() => typeof field.disabled === 'function'
                ? field.disabled(formRef)
                : (field.disabled ?? false)),
            isHidden: computed(() => typeof field.hidden === 'function'
                ? field.hidden(formRef)
                : (field.hidden ?? false)),
        };
        // Handle Repeatable Group fields (arrays of forms)
        if (this.isRepeatableGroupField(field)) {
            return this.buildRepeatableGroup({ ...field, path: referencePath }, rawValue, formRef, referencePath, baseFieldState);
        }
        // Handle Nested Form Group fields (nested forms)
        if (this.isNestedGroupField(field)) {
            return this.buildNestedForm({ ...field, path: referencePath }, rawValue, formRef, referencePath, baseFieldState);
        }
        // Handle normal fields (text, select, etc.)
        let baseField = {
            ...field,
            ...baseFieldState,
            path: referencePath,
        };
        // Handle options for fields that support them (select, radio, etc.)
        if (!this.hasOptions(field)) {
            return baseField;
        }
        if (this.hasComputedOptions(field)) {
            // Create computed options that react to form state changes
            const fieldWithComputedOptions = field;
            const computedOptionsSignal = computed(() => {
                const sourceValue = fieldWithComputedOptions.computedOptions.source(formRef);
                return fieldWithComputedOptions.computedOptions.filterFn(sourceValue, fieldWithComputedOptions.options, baseField.value());
            });
            return {
                ...baseField,
                options: computedOptionsSignal,
            };
        }
        // Use static options wrapped in a signal
        return {
            ...baseField,
            options: signal(field.options),
        };
    }
    /**
     * Type guard to check if a field is a repeatable group field
     * Repeatable groups contain arrays of form items that can be added/removed
     *
     * @template TModel - The form model type
     * @param field - Field configuration to check
     * @returns True if field is a repeatable group, false otherwise
     * @private
     */
    static isRepeatableGroupField(field) {
        return 'type' in field && field.type === FormFieldType.REPEATABLE_GROUP;
    }
    /**
     * Type guard to check if a field is a nested group field
     * Nested groups contain sub-forms with their own field collections
     *
     * @template TModel - The form model type
     * @param field - Field configuration to check
     * @returns True if field is a nested group, false otherwise
     * @private
     */
    static isNestedGroupField(field) {
        return (!('type' in field) && 'fields' in field && Array.isArray(field.fields));
    }
    /**
     * Type guard to check if a field supports options (select, radio, etc.)
     * Uses Extract to get only field types that have options property
     *
     * @template TModel - The form model type
     * @param field - Field configuration to check
     * @returns True if field supports options, false otherwise
     * @private
     */
    static hasOptions(field) {
        return ('type' in field &&
            'options' in field &&
            Array.isArray(field.options) &&
            [
                FormFieldType.SELECT,
                FormFieldType.RADIO,
                FormFieldType.CHECKBOX,
                FormFieldType.CHECKBOX_GROUP,
                FormFieldType.MULTISELECT,
                FormFieldType.CHIPLIST,
            ].includes(field.type));
    }
    /**
     * Checks if a field has computed/dynamic options configuration
     * Computed options change based on form state or other reactive values
     *
     * @template TModel - The form model type
     * @param field - Field configuration to check
     * @returns True if field has computed options, false otherwise
     * @private
     */
    static hasComputedOptions(field) {
        return ('computedOptions' in field && field.computedOptions !== undefined);
    }
    /**
     * Builds a repeatable group field that manages an array of sub-forms
     *
     * Creates a field that contains multiple instances of the same form structure,
     * allowing users to add and remove items dynamically. Each item is a complete
     * form with its own validation and state management.
     *
     * @template TModel - The form model type
     * @param field - Repeatable group field configuration
     * @param rawValue - Current array value from the model
     * @param parentForm - Reference to the parent form container
     * @param referencePath - Path to this field for nested references
     * @param baseFieldState - Base reactive state for the field
     * @returns Configured repeatable group field with add/remove functionality
     * @private
     */
    static buildRepeatableGroup(field, rawValue, parentForm, referencePath, baseFieldState) {
        const items = Array.isArray(rawValue) ? rawValue : [];
        // Create individual forms for each array item
        const repeatableForms = signal(items.map((item, index) => SignalFormBuilder.createForm({
            model: item,
            fields: field.fields,
            config: FieldUtils.createDefaultConfig(field.config),
            parentForm: parentForm,
            parentPath: `${referencePath}[${index}]`,
        })));
        const repeatableField = {
            ...field,
            error: signal(false),
            touched: signal(false),
            dirty: signal(false),
            value: computed(() => repeatableForms().map((f) => f.getValue())),
            repeatableForms,
            addItem: (initial = {}) => {
                const currentForms = repeatableForms();
                const newIndex = currentForms.length;
                const newForm = SignalFormBuilder.createForm({
                    model: initial,
                    fields: field.fields,
                    config: FieldUtils.createDefaultConfig(field.config),
                    parentForm: parentForm,
                    parentPath: `${referencePath}[${newIndex}]`,
                });
                repeatableForms.update((forms) => [...forms, newForm]);
                repeatableField.dirty.set(true);
                repeatableField.touched.set(true);
            },
            removeItem: (index) => {
                repeatableForms.update((forms) => forms.filter((_, i) => i !== index));
                repeatableField.dirty.set(true);
                repeatableField.touched.set(true);
            },
        };
        return repeatableField;
    }
    /**
     * Builds a nested form group field that contains a sub-form
     *
     * Creates a field that embeds another complete form within the current form.
     * The nested form has its own fields, validation, and state management while
     * being part of the parent form's structure.
     *
     * @template TModel - The form model type
     * @param field - Nested group field configuration
     * @param rawValue - Current nested object value from the model
     * @param parentForm - Reference to the parent form container
     * @param referencePath - Path to this field for nested references
     * @param baseFieldState - Base reactive state for the field
     * @returns Configured nested form field with embedded sub-form
     * @private
     */
    static buildNestedForm(field, rawValue, parentForm, referencePath, baseFieldState) {
        // Create embedded form for the nested object
        const nestedForm = SignalFormBuilder.createForm({
            model: rawValue,
            fields: field.fields,
            config: FieldUtils.createDefaultConfig(field.config),
            parentForm: parentForm,
            parentPath: referencePath,
        });
        const nestedField = {
            ...field,
            ...baseFieldState,
            form: nestedForm,
            fields: nestedForm.fields,
        };
        return nestedField;
    }
}

// # region
// #endregion
/**
 * SignalFormBuilder - Main factory class for creating Signal Forms
 *
 * Provides static methods to create different types of forms:
 * - Single forms with standard fields
 * - Stepped/wizard forms with multiple pages
 * - Array forms for managing collections of items
 *
 * Features:
 * - Type-safe form creation with TypeScript generics
 * - Automatic field binding and validation setup
 * - Reactive computed properties for form state
 * - Built-in save/reset functionality
 * - Parent-child form relationships
 *
 * @example
 * ```typescript
 * const userForm = SignalFormBuilder.createForm({
 *   model: { name: '', email: '' },
 *   fields: [
 *     { name: 'name', type: FormFieldType.TEXT, label: 'Name' },
 *     { name: 'email', type: FormFieldType.TEXT, label: 'Email' }
 *   ],
 *   onSave: (data) => console.log('Saved:', data)
 * });
 * ```
 */
class SignalFormBuilder {
    /**
     * Creates a standard single-page form with specified fields
     *
     * Builds a complete form container with reactive state management,
     * validation, and save functionality. The form automatically binds
     * to the provided model and creates appropriate field components.
     *
     * @template TModel - The TypeScript type of the form data model
     * @param args - Configuration object for the form
     * @param args.model - Initial data model for the form
     * @param args.fields - Array of field configurations
     * @param args.title - Optional form title
     * @param args.config - Layout and behavior configuration
     * @param args.onSave - Callback function when form is saved
     * @param args.parentForm - Parent form if this is a nested form
     * @param args.parentPath - Path prefix for nested form fields
     * @returns Complete form container with reactive state and methods
     *
     * @example
     * ```typescript
     * const contactForm = SignalFormBuilder.createForm({
     *   model: { name: '', email: '', phone: '' },
     *   fields: [
     *     { name: 'name', type: FormFieldType.TEXT, label: 'Full Name' },
     *     { name: 'email', type: FormFieldType.TEXT, label: 'Email' },
     *     { name: 'phone', type: FormFieldType.TEXT, label: 'Phone' }
     *   ],
     *   config: { layout: 'flex', view: 'stacked' },
     *   onSave: (data) => saveContact(data)
     * });
     * ```
     */
    static createForm(args) {
        const form = {};
        const status = signal(FormStatus.Idle);
        // Create all field instances using the factory
        const fields = args.fields.map((field) => FieldFactory.build(field, args.model, form, args.parentPath));
        // Assemble the complete form container with all methods and reactive properties
        Object.assign(form, {
            title: args.title,
            status,
            fields,
            getField: FormEngine.getField(fields),
            anyTouched: FieldUtils.anyTouched(fields),
            anyDirty: FieldUtils.anyDirty(fields),
            value: computed(() => FormEngine.getValueFromFields(fields, form)),
            rawValue: FormEngine.getRawValue(fields),
            getValue: () => form.value(),
            getRawValue: () => form.rawValue(),
            hasSaved: () => FieldUtils.hasSaved(form),
            validateForm: FormEngine.validateForm(fields, form),
            reset: FormEngine.resetForm(fields, { ...args.model }),
            getErrors: FormEngine.getErrors(fields),
            config: FieldUtils.createDefaultConfig(args.config),
            patchValue: FormEngine.patchForm(fields),
            setValue: FormEngine.setFormValue(fields),
            save: FormEngine.runSaveHandler(fields, status, form, args.onSave),
            getParent: () => args.parentForm,
            parentForm: computed(() => args.parentForm),
            saveButtonDisabled: computed(() => {
                const errors = FormEngine.getErrors(fields)();
                const hasErrors = errors.length > 0;
                if (!hasErrors) {
                    return false; // No errors, button should be enabled
                }
                // If all errors are submit-triggered, allow the save button to be enabled
                // so users can re-submit to correct submit-only validation errors
                const allErrorsAreSubmitOnly = errors.every((error) => error.trigger === 'submit');
                return !allErrorsAreSubmitOnly; // Disable only if there are non-submit errors
            }),
        });
        return form;
    }
    /**
     * Creates a multi-step form (wizard) with separate pages/steps
     *
     * Builds a stepped form container that manages multiple form pages,
     * allowing users to navigate between steps while maintaining state.
     * Each step is a complete form with its own fields and validation.
     *
     * @template TModel - The TypeScript type of the form data model
     * @param args - Configuration object for the stepped form
     * @param args.model - Initial data model shared across all steps
     * @param args.steps - Array of step configurations, each with fields
     * @param args.onSave - Callback function when entire form is saved
     * @param args.config - Global configuration for the stepped form
     * @param args.config.canSkipIncompleteSteps - Allow navigation to incomplete steps
     * @returns Stepped form container with navigation and validation methods
     *
     * @example
     * ```typescript
     * const wizardForm = SignalFormBuilder.createSteppedForm({
     *   model: { personal: {}, contact: {}, preferences: {} },
     *   steps: [
     *     {
     *       title: 'Personal Info',
     *       fields: [
     *         { name: 'firstName', type: FormFieldType.TEXT, label: 'First Name' }
     *       ]
     *     },
     *     {
     *       title: 'Contact Info',
     *       fields: [
     *         { name: 'email', type: FormFieldType.TEXT, label: 'Email' }
     *       ]
     *     }
     *   ],
     *   config: { canSkipIncompleteSteps: false }
     * });
     * ```
     */
    static createSteppedForm(args) {
        const currentStep = signal(0);
        const status = signal(FormStatus.Idle);
        // Create individual form containers for each step
        const steps = args.steps.map((step) => SignalFormBuilder.createForm({
            model: args.model,
            fields: step.fields,
            config: step.config,
        }));
        // Computed value that merges data from all steps
        const value = computed(() => steps.reduce((acc, step) => ({ ...acc, ...step.getValue() }), {}));
        /** Validates only the current step */
        const validateStep = () => steps[currentStep()].validateForm();
        /** Checks if current step has no validation errors */
        const isValidStep = () => steps[currentStep()].fields.every((f) => !f.error());
        /** Validates all steps in the form */
        const validateAll = () => steps.every((step) => step.validateForm());
        /** Gets all validation errors from all steps */
        const getErrors = () => steps.flatMap((step) => step.getErrors());
        /**
         * Retrieves a specific field by name from any step
         * @param key - The field name to search for
         * @returns The field instance
         * @throws Error if field is not found in any step
         */
        const getField = (key) => {
            const allFields = steps.flatMap((step) => step.fields);
            const field = allFields.find((f) => f.name === key);
            if (!field) {
                throw new Error(`Field ${String(key)} not found in form`);
            }
            return field;
        };
        /** Resets all steps to their initial state */
        const reset = () => steps.forEach((step) => step.reset());
        // Computed reactive properties for form state
        const anyTouched = computed(() => steps.some((step) => step.anyTouched()));
        const anyDirty = computed(() => steps.some((step) => step.anyDirty()));
        const hasSaved = computed(() => !anyDirty() && !anyTouched() && status() === FormStatus.Success);
        // Create virtual form for save handler with all fields
        const allFields = steps.flatMap((s) => s.fields);
        const virtualForm = {
            ...steps[0],
            fields: allFields,
            getValue: () => value(),
            anyDirty: computed(() => steps.some((s) => s.anyDirty())),
            anyTouched: computed(() => steps.some((s) => s.anyTouched())),
            config: FieldUtils.createDefaultConfig(args.config?.form),
        };
        const saveButtonDisabled = computed(() => {
            const errors = getErrors();
            const hasErrors = errors.length > 0;
            if (!hasErrors) {
                return false; // No errors, button should be enabled
            }
            // If all errors are submit-triggered, allow the save button to be enabled
            // so users can re-submit to correct submit-only validation errors
            const allErrorsAreSubmitOnly = errors.every((error) => error.trigger === 'submit');
            return !allErrorsAreSubmitOnly; // Disable only if there are non-submit errors
        });
        return {
            anyTouched,
            anyDirty,
            steps,
            currentStep,
            value,
            getValue: () => value(),
            validateStep,
            validateAll,
            isValidStep,
            getErrors,
            getField,
            hasSaved,
            reset,
            save: FormEngine.runSaveHandler(allFields, status, virtualForm, args.onSave),
            status,
            config: {
                ...args.config,
                canSkipIncompleteSteps: args.config?.canSkipIncompleteSteps ?? false,
            },
            saveButtonDisabled,
        };
    }
    /**
     * Creates a form for managing an array/collection of items
     *
     * Builds a dynamic form container that manages multiple instances
     * of the same form structure. Useful for managing lists of items
     * where users can add, remove, and edit multiple entries.
     *
     * @template TModel - The TypeScript type of individual items in the array
     * @param args - Configuration object for the array form
     * @param args.model - Initial array of items
     * @param args.fields - Field configuration shared by all items
     * @param args.title - Optional form title
     * @param args.config - Layout and behavior configuration
     * @param args.onSave - Callback when entire array is saved
     * @param args.onItemAdd - Callback when new item is added
     * @param args.onItemRemove - Callback when item is removed
     * @param args.defaultItem - Default values for new items
     * @param args.parentForm - Parent form if this is nested
     * @param args.parentPath - Path prefix for nested forms
     * @returns Array form container with add/remove/manage functionality
     *
     * @example
     * ```typescript
     * const contactsForm = SignalFormBuilder.createFormFromArray({
     *   model: [{ name: '', email: '' }],
     *   fields: [
     *     { name: 'name', type: FormFieldType.TEXT, label: 'Name' },
     *     { name: 'email', type: FormFieldType.TEXT, label: 'Email' }
     *   ],
     *   defaultItem: { name: '', email: '' },
     *   onItemAdd: (item) => console.log('Added:', item),
     *   onItemRemove: (index) => console.log('Removed index:', index)
     * });
     * ```
     */
    static createFormFromArray(args) {
        const formsSignal = signal([]);
        const status = signal(FormStatus.Idle);
        // Create initial forms from the model array
        const initialForms = args.model.map((item, index) => SignalFormBuilder.createForm({
            model: item,
            fields: args.fields,
            config: args.config,
            parentForm: args.parentForm,
            parentPath: args.parentPath
                ? `${args.parentPath}[${index}]`
                : `[${index}]`,
        }));
        formsSignal.set(initialForms);
        /**
         * Adds a new item form to the array
         * @param item - Optional partial data for the new item
         */
        const addItem = (item) => {
            const currentForms = formsSignal();
            const defaultItemMerged = { ...args.defaultItem, ...item };
            const newIndex = currentForms.length;
            const newForm = SignalFormBuilder.createForm({
                model: defaultItemMerged,
                fields: args.fields,
                config: args.config,
                parentForm: args.parentForm,
                parentPath: args.parentPath
                    ? `${args.parentPath}[${newIndex}]`
                    : `[${newIndex}]`,
            });
            formsSignal.update((forms) => [...forms, newForm]);
            args.onItemAdd?.(defaultItemMerged);
        };
        /**
         * Removes an item form from the array at the specified index
         * @param index - The index of the item to remove
         */
        const removeItem = (index) => {
            formsSignal.update((forms) => forms.filter((_, i) => i !== index));
            args.onItemRemove?.(index);
        };
        /**
         * Gets the current values from all item forms in the array
         * @returns Array of all current form values
         */
        const getValue = () => {
            return formsSignal().map((form) => form.getValue());
        };
        /**
         * Validates all item forms in the array
         * @returns True if all forms are valid, false otherwise
         */
        const validateAll = () => {
            return formsSignal().every((form) => form.validateForm());
        };
        /**
         * Gets all validation errors from all item forms
         * Prefixes each error path with the array index
         * @returns Array of all validation errors with indexed paths
         */
        const getErrors = () => {
            return formsSignal().flatMap((form, index) => form.getErrors().map((error) => ({
                ...error,
                path: `[${index}].${error.path}`,
            })));
        };
        /**
         * Saves all item forms if validation passes
         * Updates status to Success or Error based on validation
         */
        const save = () => {
            if (validateAll()) {
                status.set(FormStatus.Success);
                args.onSave?.(getValue());
            }
            else {
                status.set(FormStatus.Error);
            }
        };
        /**
         * Resets all item forms to their original model values
         * Recreates all forms from the initial model array
         */
        const reset = () => {
            const resetForms = args.model.map((item, index) => SignalFormBuilder.createForm({
                model: item,
                fields: args.fields,
                config: args.config,
                parentForm: args.parentForm,
                parentPath: args.parentPath
                    ? `${args.parentPath}[${index}]`
                    : `[${index}]`,
            }));
            formsSignal.set(resetForms);
            status.set(FormStatus.Idle);
        };
        /**
         * Checks if any item form in the array has been touched
         * @returns True if any form has been touched by user interaction
         */
        const anyTouched = () => {
            return formsSignal().some((form) => form.anyTouched());
        };
        /**
         * Checks if any item form in the array has been modified
         * @returns True if any form has dirty (changed) values
         */
        const anyDirty = () => {
            return formsSignal().some((form) => form.anyDirty());
        };
        /**
         * Determines if the save button should be disabled based on validation errors
         * @returns True if save button should be disabled
         */
        const saveButtonDisabled = () => {
            const errors = getErrors();
            const hasErrors = errors.length > 0;
            if (!hasErrors) {
                return false; // No errors, button should be enabled
            }
            // If all errors are submit-triggered, allow the save button to be enabled
            // so users can re-submit to correct submit-only validation errors
            const allErrorsAreSubmitOnly = errors.every((error) => error.trigger === 'submit');
            return !allErrorsAreSubmitOnly; // Disable only if there are non-submit errors
        };
        return {
            title: args.title,
            forms: formsSignal,
            value: getValue,
            addItem,
            removeItem,
            validateAll,
            getErrors,
            save,
            reset,
            anyTouched,
            anyDirty,
            status: () => status(),
            saveButtonDisabled,
        };
    }
}

function isRequired(field) {
    return (field.validators?.some((v) => v.__meta?.required) ?? false);
}

function withMeta(fn, meta) {
    fn.__meta = meta;
    return fn;
}

// Common conversion utilities
class ConversionUtils {
    /**
     * Weight conversions (base unit: kg)
     */
    static weight = {
        kg: {
            label: 'Kilograms',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'lbs':
                        return value * 0.453592;
                    case 'oz':
                        return value * 0.0283495;
                    case 'g':
                        return value / 1000;
                    case 'kg':
                        return value;
                    default:
                        return value;
                }
            },
        },
        lbs: {
            label: 'Pounds',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'kg':
                        return value * 2.20462;
                    case 'oz':
                        return value / 16;
                    case 'g':
                        return value * 0.00220462;
                    case 'lbs':
                        return value;
                    default:
                        return value;
                }
            },
        },
        oz: {
            label: 'Ounces',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'kg':
                        return value * 35.274;
                    case 'lbs':
                        return value * 16;
                    case 'g':
                        return value * 0.035274;
                    case 'oz':
                        return value;
                    default:
                        return value;
                }
            },
        },
        g: {
            label: 'Grams',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'kg':
                        return value * 1000;
                    case 'lbs':
                        return value * 453.592;
                    case 'oz':
                        return value * 28.3495;
                    case 'g':
                        return value;
                    default:
                        return value;
                }
            },
        },
    };
    /**
     * Length conversions (base unit: m)
     */
    static length = {
        m: {
            label: 'Meters',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'ft':
                        return value * 0.3048;
                    case 'in':
                        return value * 0.0254;
                    case 'cm':
                        return value / 100;
                    case 'mm':
                        return value / 1000;
                    case 'm':
                        return value;
                    default:
                        return value;
                }
            },
        },
        ft: {
            label: 'Feet',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'm':
                        return value * 3.28084;
                    case 'in':
                        return value / 12;
                    case 'cm':
                        return value * 0.0328084;
                    case 'mm':
                        return value * 0.00328084;
                    case 'ft':
                        return value;
                    default:
                        return value;
                }
            },
        },
        in: {
            label: 'Inches',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'm':
                        return value * 39.3701;
                    case 'ft':
                        return value * 12;
                    case 'cm':
                        return value * 0.393701;
                    case 'mm':
                        return value * 0.0393701;
                    case 'in':
                        return value;
                    default:
                        return value;
                }
            },
        },
        cm: {
            label: 'Centimeters',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'm':
                        return value * 100;
                    case 'ft':
                        return value * 30.48;
                    case 'in':
                        return value * 2.54;
                    case 'mm':
                        return value / 10;
                    case 'cm':
                        return value;
                    default:
                        return value;
                }
            },
        },
        mm: {
            label: 'Millimeters',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'm':
                        return value * 1000;
                    case 'ft':
                        return value * 304.8;
                    case 'in':
                        return value * 25.4;
                    case 'cm':
                        return value * 10;
                    case 'mm':
                        return value;
                    default:
                        return value;
                }
            },
        },
    };
    /**
     * Temperature conversions (base unit: celsius)
     */
    static temperature = {
        celsius: {
            label: 'Celsius',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'fahrenheit':
                        return ((value - 32) * 5) / 9;
                    case 'kelvin':
                        return value - 273.15;
                    case 'celsius':
                        return value;
                    default:
                        return value;
                }
            },
        },
        fahrenheit: {
            label: 'Fahrenheit',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'celsius':
                        return (value * 9) / 5 + 32;
                    case 'kelvin':
                        return ((value - 273.15) * 9) / 5 + 32;
                    case 'fahrenheit':
                        return value;
                    default:
                        return value;
                }
            },
        },
        kelvin: {
            label: 'Kelvin',
            convert: (value, fromUnit) => {
                switch (fromUnit) {
                    case 'celsius':
                        return value + 273.15;
                    case 'fahrenheit':
                        return ((value - 32) * 5) / 9 + 273.15;
                    case 'kelvin':
                        return value;
                    default:
                        return value;
                }
            },
        },
    };
    /**
     * Common parsers for different number formats
     */
    static parsers = {
        currency: (value, locale = 'en-US', currency = 'USD') => new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value),
        percentage: (value, locale = 'en-US') => new Intl.NumberFormat(locale, {
            style: 'percent',
            minimumFractionDigits: 1,
        }).format(value / 100),
        decimal: (value, locale = 'en-US', fractionDigits = 2) => new Intl.NumberFormat(locale, {
            minimumFractionDigits: fractionDigits,
            maximumFractionDigits: fractionDigits,
        }).format(value),
        integer: (value, locale = 'en-US') => new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(value),
    };
}

// Export all from unit-conversion.model.ts

class FormDropdownOverlayComponent {
    renderer;
    elementRef;
    destroyRef;
    injector;
    options = input.required();
    multiselect = input(false);
    triggerElement = input();
    initialSelection = input();
    ariaListboxId = input.required();
    select = output();
    close = output();
    optionElements = viewChildren('optionElement', { read: ElementRef });
    focusedIndex = signal(-1);
    selectedOptions = signal([]);
    checkIcon = CircleCheck;
    clearIcon = CircleX;
    constructor(renderer, elementRef, destroyRef, injector) {
        this.renderer = renderer;
        this.elementRef = elementRef;
        this.destroyRef = destroyRef;
        this.injector = injector;
    }
    ngOnInit() {
        this.handleSingleOrMultiSelect();
        this.listenToDocumentMouseClicks();
        this.listenToTriggerElementKeyDown();
        this.listenToOptionElementForView();
    }
    handleSingleOrMultiSelect() {
        const selection = this.initialSelection();
        if (!selection) {
            return;
        }
        if (this.multiselect() && Array.isArray(selection)) {
            this.selectedOptions.set(selection);
            return;
        }
        this.selectedOptions.set([selection]);
    }
    selectOption(option) {
        if (this.multiselect()) {
            const current = this.selectedOptions() ?? [];
            const exists = current.some((o) => o.value === option.value);
            const updated = exists
                ? current.filter((o) => o.value !== option.value)
                : [...current, option];
            this.selectedOptions.set(updated);
            this.select.emit(updated);
        }
        else {
            this.selectedOptions.set([option]);
            this.emitSelection();
        }
    }
    emitSelection() {
        const result = this.multiselect()
            ? this.selectedOptions()
            : (this.selectedOptions()[0] ?? null);
        this.select.emit(result);
    }
    toggleOption(option) {
        if (this.multiselect()) {
            this.selectedOptions.update((current) => {
                const exists = current.some((o) => o.value === option.value);
                return exists
                    ? current.filter((o) => o.value !== option.value)
                    : [...current, option];
            });
        }
        else {
            this.selectedOptions.set([option]);
            this.emitSelection();
        }
    }
    setPosition({ top, left, width, }) {
        const el = this.elementRef.nativeElement;
        const widthPx = typeof width === 'string' ? width : `${width}px`;
        Object.entries({
            position: 'absolute',
            top: `${top}px`,
            left: `${left}px`,
            width: widthPx,
            zIndex: 1000,
        }).forEach(([key, val]) => this.renderer.setStyle(el, key, val));
    }
    isSelected(formOption) {
        const selectedOptions = this.selectedOptions();
        if (!this.selectedOptions()) {
            return false;
        }
        return selectedOptions.some((option) => option.value === formOption.value);
    }
    listenToDocumentMouseClicks() {
        fromEvent(document, 'click')
            .pipe(takeUntilDestroyed(this.destroyRef))
            .subscribe((event) => {
            const target = event.target;
            const clickedInsideOverlay = this.elementRef.nativeElement.contains(target);
            const clickedOnTrigger = this.triggerElement()?.contains(target);
            if (!clickedInsideOverlay && !clickedOnTrigger) {
                this.close.emit();
            }
        });
    }
    listenToTriggerElementKeyDown() {
        if (!this.triggerElement()) {
            return;
        }
        fromEvent(this.triggerElement(), 'keydown')
            .pipe(tap$1((event) => this.handleKeydown(event)), takeUntilDestroyed(this.destroyRef))
            .subscribe();
    }
    handleKeydown(event) {
        const options = this.options();
        const max = options.length - 1;
        switch (event.key) {
            case 'ArrowDown':
                event.preventDefault();
                this.focusedIndex.update((i) => (i + 1 > max ? 0 : i + 1));
                break;
            case 'ArrowUp':
                event.preventDefault();
                this.focusedIndex.update((i) => (i - 1 < 0 ? max : i - 1));
                break;
            case 'Enter':
                event.preventDefault();
                const selected = options[this.focusedIndex()];
                if (selected)
                    this.selectOption(selected);
                break;
            case 'Escape':
                this.close.emit();
                break;
        }
    }
    listenToOptionElementForView() {
        effect(() => {
            const index = this.focusedIndex();
            if (index === -1) {
                return;
            }
            const el = this.optionElements().at(index)?.nativeElement;
            if (el) {
                el.scrollIntoView({
                    behavior: 'smooth',
                    block: 'nearest',
                });
            }
        }, {
            injector: this.injector,
        });
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormDropdownOverlayComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.DestroyRef }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: FormDropdownOverlayComponent, isStandalone: true, selector: "form-dropdown-overlay", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, triggerElement: { classPropertyName: "triggerElement", publicName: "triggerElement", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, ariaListboxId: { classPropertyName: "ariaListboxId", publicName: "ariaListboxId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { select: "select", close: "close" }, viewQueries: [{ propertyName: "optionElements", predicate: ["optionElement"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<ul role=\"listbox\" [id]=\"ariaListboxId()\" class=\"dropdown-options\">\n  @for (option of options(); track option.value) {\n    <li\n      class=\"dropdown-option\"\n      [class.selected]=\"isSelected(option)\"\n      [class.focused]=\"focusedIndex() === $index\"\n      [attr.data-index]=\"$index\"\n      [attr.aria-selected]=\"focusedIndex() === $index\"\n      [id]=\"'option-' + i\"\n      (click)=\"selectOption(option)\"\n      #optionElement\n    >\n      {{ option.label }}\n      @if (isSelected(option) && multiselect()) {\n        @if (focusedIndex() === $index) {\n          <lucide-icon class=\"selected-icon\" [img]=\"clearIcon\" />\n        } @else {\n          <lucide-icon class=\"selected-icon\" [img]=\"checkIcon\" />\n        }\n      }\n    </li>\n  }\n</ul>\n", styles: [".dropdown-options{position:absolute;background:var(--signal-form-input-bg);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);box-shadow:var(--signal-form-shadow-lg);max-height:240px;overflow-y:auto;width:100%;list-style:none;padding:.25rem;margin:0;z-index:1000}.dropdown-options .dropdown-option{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;cursor:pointer;border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-base);color:var(--signal-form-input-text);transition:var(--signal-form-transition);margin-bottom:.125rem}.dropdown-options .dropdown-option:last-child{margin-bottom:0}.dropdown-options .dropdown-option:hover{background:var(--signal-form-dropdown-hover)}.dropdown-options .dropdown-option.focused{background:var(--signal-form-dropdown-selected)}.dropdown-options .dropdown-option.focused.selected{background:var(--signal-form-dropdown-selected-hover);color:var(--signal-forms-neutral-0)}.dropdown-options .dropdown-option.focused.selected .selected-icon{color:var(--signal-forms-neutral-0)}.dropdown-options .dropdown-option.selected{background:var(--signal-form-dropdown-selected);color:var(--signal-form-outline-focus);font-weight:500}.dropdown-options .dropdown-option.selected .selected-icon{color:var(--signal-form-outline-focus)}.dropdown-options .dropdown-option .selected-icon{width:1rem;height:1rem;flex-shrink:0;margin-left:.5rem;display:flex;align-items:center;justify-content:center}.dropdown-options::-webkit-scrollbar{width:6px}.dropdown-options::-webkit-scrollbar-track{background:var(--signal-forms-neutral-100);border-radius:3px}.dropdown-options::-webkit-scrollbar-thumb{background:var(--signal-forms-neutral-300);border-radius:3px}.dropdown-options::-webkit-scrollbar-thumb:hover{background:var(--signal-forms-neutral-400)}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormDropdownOverlayComponent, decorators: [{
            type: Component,
            args: [{ selector: 'form-dropdown-overlay', standalone: true, imports: [LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ul role=\"listbox\" [id]=\"ariaListboxId()\" class=\"dropdown-options\">\n  @for (option of options(); track option.value) {\n    <li\n      class=\"dropdown-option\"\n      [class.selected]=\"isSelected(option)\"\n      [class.focused]=\"focusedIndex() === $index\"\n      [attr.data-index]=\"$index\"\n      [attr.aria-selected]=\"focusedIndex() === $index\"\n      [id]=\"'option-' + i\"\n      (click)=\"selectOption(option)\"\n      #optionElement\n    >\n      {{ option.label }}\n      @if (isSelected(option) && multiselect()) {\n        @if (focusedIndex() === $index) {\n          <lucide-icon class=\"selected-icon\" [img]=\"clearIcon\" />\n        } @else {\n          <lucide-icon class=\"selected-icon\" [img]=\"checkIcon\" />\n        }\n      }\n    </li>\n  }\n</ul>\n", styles: [".dropdown-options{position:absolute;background:var(--signal-form-input-bg);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);box-shadow:var(--signal-form-shadow-lg);max-height:240px;overflow-y:auto;width:100%;list-style:none;padding:.25rem;margin:0;z-index:1000}.dropdown-options .dropdown-option{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;cursor:pointer;border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-base);color:var(--signal-form-input-text);transition:var(--signal-form-transition);margin-bottom:.125rem}.dropdown-options .dropdown-option:last-child{margin-bottom:0}.dropdown-options .dropdown-option:hover{background:var(--signal-form-dropdown-hover)}.dropdown-options .dropdown-option.focused{background:var(--signal-form-dropdown-selected)}.dropdown-options .dropdown-option.focused.selected{background:var(--signal-form-dropdown-selected-hover);color:var(--signal-forms-neutral-0)}.dropdown-options .dropdown-option.focused.selected .selected-icon{color:var(--signal-forms-neutral-0)}.dropdown-options .dropdown-option.selected{background:var(--signal-form-dropdown-selected);color:var(--signal-form-outline-focus);font-weight:500}.dropdown-options .dropdown-option.selected .selected-icon{color:var(--signal-form-outline-focus)}.dropdown-options .dropdown-option .selected-icon{width:1rem;height:1rem;flex-shrink:0;margin-left:.5rem;display:flex;align-items:center;justify-content:center}.dropdown-options::-webkit-scrollbar{width:6px}.dropdown-options::-webkit-scrollbar-track{background:var(--signal-forms-neutral-100);border-radius:3px}.dropdown-options::-webkit-scrollbar-thumb{background:var(--signal-forms-neutral-300);border-radius:3px}.dropdown-options::-webkit-scrollbar-thumb:hover{background:var(--signal-forms-neutral-400)}\n"] }]
        }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.DestroyRef }, { type: i0.Injector }] });

class FormDropdownService {
    dropdownRef;
    appRef = inject(ApplicationRef);
    injector = inject(Injector);
    openDropdown(config) {
        this.destroyDropdown();
        this.dropdownRef = config.viewContainerRef.createComponent(FormDropdownOverlayComponent, {
            environmentInjector: this.appRef.injector,
            injector: this.injector,
        });
        const instance = this.dropdownRef.instance;
        this.dropdownRef.setInput('options', config.options);
        this.dropdownRef.setInput('triggerElement', config.reference);
        this.dropdownRef.setInput('multiselect', config.multiselect);
        this.dropdownRef.setInput('ariaListboxId', config.ariaListboxId);
        this.dropdownRef.setInput('initialSelection', config.initialSelection);
        instance.select.subscribe((option) => {
            config.onSelect(option);
            if (!config.multiselect) {
                this.destroyDropdown();
            }
        });
        instance.close.subscribe(() => {
            config.onClose?.();
            this.destroyDropdown();
        });
        requestAnimationFrame(() => {
            const rect = config.reference.getBoundingClientRect();
            instance.setPosition({
                top: rect.height,
                left: 0,
                width: '100%',
            });
        });
    }
    destroyDropdown() {
        this.dropdownRef?.destroy();
        this.dropdownRef = undefined;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormDropdownService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormDropdownService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormDropdownService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }] });

const SIGNAL_FORMS_THEME_CONFIG = new InjectionToken('SIGNAL_FORMS_THEME_CONFIG');
/**
 * Provider function for Signal Forms theme configuration
 * @param config - Theme configuration options
 * @returns Provider array for Angular DI
 */
function provideSignalFormsTheme(config = {}) {
    return [
        {
            provide: SIGNAL_FORMS_THEME_CONFIG,
            useValue: config,
        },
        SignalFormThemeService,
    ];
}
/**
 * Theme Service for Signal Forms
 *
 * Provides centralized theme management for the form system.
 * Manages global theme state and applies theme classes to the HTML element.
 *
 * This service is a singleton that coordinates theme across all forms in the app.
 * When any form requests a theme check, it determines the root form's theme
 * and applies the appropriate class to the HTML element.
 */
class SignalFormThemeService {
    document;
    config;
    _theme = signal('auto');
    _isDarkMode = signal(false);
    /**
     * Current theme mode setting for the service
     */
    theme = this._theme.asReadonly();
    /**
     * Whether dark mode is currently active based on system preference
     */
    isDarkMode = this._isDarkMode.asReadonly();
    constructor(document, config) {
        this.document = document;
        this.config = config;
        // Use default empty config if none provided
        const themeConfig = this.config || {};
        // Set initial theme from config
        if (themeConfig.defaultTheme) {
            this._theme.set(themeConfig.defaultTheme);
        }
        // Initialize dark mode based on system preference
        if (typeof window !== 'undefined') {
            this._isDarkMode.set(window.matchMedia('(prefers-color-scheme: dark)').matches);
            // Listen for system theme changes
            const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
            mediaQuery.addEventListener('change', (e) => {
                this._isDarkMode.set(e.matches);
            });
        }
        // Auto-apply dark mode if configured
        if (themeConfig.darkMode) {
            effect(() => {
                this.updateGlobalDarkMode();
            });
        }
    }
    /**
     * Set the global theme mode
     * @param theme - 'light', 'dark', or 'auto'
     */
    setTheme(theme) {
        this._theme.set(theme);
    }
    /**
     * Toggle between light and dark theme
     */
    toggleTheme() {
        const current = this._theme();
        if (current === 'auto') {
            // If auto, switch to opposite of current system preference
            this.setTheme(this._isDarkMode() ? 'light' : 'dark');
        }
        else {
            // Toggle between light and dark
            this.setTheme(current === 'dark' ? 'light' : 'dark');
        }
    }
    /**
     * Check theme for any form configuration and trigger HTML class updates
     * This method determines the effective theme but doesn't return it for binding
     * Instead, it triggers side effects to update the global HTML classes
     *
     * @param formOrConfig - Form container or form config
     */
    checkTheme(formOrConfig) {
        const effectiveTheme = this.getEffectiveTheme(formOrConfig);
        this.updateHtmlClasses(effectiveTheme);
    }
    /**
     * Apply global dark mode based on service theme setting
     * Used when darkMode is enabled in provider config
     */
    updateGlobalDarkMode() {
        const effectiveTheme = this.getServiceEffectiveTheme();
        this.updateHtmlClasses(effectiveTheme);
    }
    /**
     * Get the effective theme for any form configuration
     * Recursively finds the root form and uses its theme setting
     *
     * @param formOrConfig - Form container or form config
     * @returns 'light' or 'dark'
     */
    getEffectiveTheme(formOrConfig) {
        let config;
        // Handle both form containers and configs
        if ('config' in formOrConfig) {
            // It's a form container - check if it has a parent
            const form = formOrConfig;
            const parentForm = form.parentForm?.();
            if (parentForm) {
                // Not root, recurse to parent
                return this.getEffectiveTheme(parentForm);
            }
            config = form.config;
        }
        else {
            // It's a config object
            config = formOrConfig;
        }
        // We're at the root - determine theme
        const themeMode = config.theme ?? 'light';
        if (themeMode === 'dark') {
            return 'dark';
        }
        if (themeMode === 'light') {
            return 'light';
        }
        // Auto mode - use service theme or fall back to system preference
        return this.getServiceEffectiveTheme();
    }
    /**
     * Get the effective theme from the service (resolves 'auto' to 'light' or 'dark')
     */
    getServiceEffectiveTheme() {
        const theme = this._theme();
        if (theme === 'auto') {
            return this._isDarkMode() ? 'dark' : 'light';
        }
        return theme;
    }
    /**
     * Update HTML classes based on effective theme
     * @param effectiveTheme - The theme to apply
     */
    updateHtmlClasses(effectiveTheme) {
        // Safe to use injected document - will be null in headless environments
        if (!this.document?.documentElement) {
            return;
        }
        const html = this.document.documentElement;
        // Remove existing theme classes
        html.classList.remove('dark-themed-forms', 'light-themed-forms');
        // Add new theme class
        if (effectiveTheme === 'dark') {
            html.classList.add('dark-themed-forms');
        }
        else {
            html.classList.add('light-themed-forms');
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormThemeService, deps: [{ token: DOCUMENT }, { token: SIGNAL_FORMS_THEME_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormThemeService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormThemeService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: () => [{ type: Document, decorators: [{
                    type: Inject,
                    args: [DOCUMENT]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [SIGNAL_FORMS_THEME_CONFIG]
                }] }] });

const unsavedChangesGuard = (component) => {
    if (component.hasUnsavedChanges()) {
        return confirm('You have unsaved changes. Are you sure you want to leave this page?');
    }
    return true;
};

class WordCountService {
    /**
     * Counts words, characters, and lines in the given text
     */
    getWordCount(text) {
        if (!text) {
            return {
                characters: 0,
                words: 0,
                charactersWithSpaces: 0,
                lines: 0,
            };
        }
        const charactersWithSpaces = text.length;
        const characters = text.replace(/\s/g, '').length;
        const lines = text.split('\n').length;
        // Count words by splitting on whitespace and filtering empty strings
        const words = text
            .trim()
            .split(/\s+/)
            .filter((word) => word.length > 0).length;
        return {
            characters,
            words,
            charactersWithSpaces,
            lines,
        };
    }
    /**
     * Formats word count for display
     */
    formatWordCount(stats, format = 'words') {
        switch (format) {
            case 'words':
                return `${stats.words} word${stats.words !== 1 ? 's' : ''}`;
            case 'characters':
                return `${stats.characters} character${stats.characters !== 1 ? 's' : ''}`;
            case 'both':
                return `${stats.words} word${stats.words !== 1 ? 's' : ''}, ${stats.characters} character${stats.characters !== 1 ? 's' : ''}`;
            case 'detailed':
                return `${stats.words} words | ${stats.characters} chars | ${stats.lines} lines`;
            default:
                return `${stats.words} word${stats.words !== 1 ? 's' : ''}`;
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WordCountService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WordCountService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WordCountService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

class SignalValidators {
    static required(message = 'This field is required') {
        return withMeta((val) => (val == null || val === '' ? message : null), {
            required: true,
        });
    }
    static min(min, msg) {
        return (val) => !!val && val < min ? (msg ?? `Must be at least ${min}`) : null;
    }
    static max(max, msg) {
        return (val) => (val > max ? (msg ?? `Must be no more than ${max}`) : null);
    }
    static minLength(min, msg) {
        return (val) => val.length < min ? (msg ?? `Minimum ${min} characters`) : null;
    }
    static maxLength(max, msg) {
        return (val) => val.length > max ? (msg ?? `Maximum ${max} characters`) : null;
    }
    static isPositive(msg = 'Must be a positive number') {
        return (val) => (val <= 0 ? msg : null);
    }
    static hasValue(fieldName) {
        return (_, form) => form.getField(fieldName).value()
            ? null
            : `${String(fieldName)} is required`;
    }
    static matchField(otherKey, msg = 'Fields do not match') {
        return (val, form) => val !== form.getField(otherKey)?.value?.() ? msg : null;
    }
}

/**
 * Variant for guaranteed signal access (same-step dependencies), with full access to the form context.
 */
function withSignalValidation(getDepSignal, validate) {
    return (val, form) => {
        const depValue = getDepSignal()();
        return validate(val, depValue, form);
    };
}

// #region
// #endregion
/**
 * Base directive for form input components that provides common functionality
 * such as validation, computed values, and options filtering.
 *
 * This directive should be extended by all form field components to provide
 * consistent behavior across the form system.
 *
 * @template TField - The specific field type extending RuntimeFields
 * @template TModel - The form model type
 * @template K - The field key type
 *
 * @example
 * ```typescript
 * export class FormTextFieldComponent extends BaseInputDirective<
 *   RuntimeTextSignalField<TModel, K>,
 *   TModel,
 *   K
 * > {
 *   // Component implementation
 * }
 * ```
 */
class BaseInputDirective {
    /**
     * The field configuration and state
     */
    field = input.required();
    /**
     * Computed form reference obtained from the field
     */
    form = computed(() => this.field().getForm());
    /**
     * Angular injector for effect management
     */
    injector = inject(Injector);
    /**
     * Computed property indicating if the field is required based on validators
     */
    isRequired = computed(() => (this.field().validators ?? []).some((validator) => validator.__meta?.required));
    /**
     * Computed property indicating if the field should be hidden
     */
    isHidden = computed(() => {
        const { hidden } = this.field();
        return typeof hidden === 'function' ? hidden(this.form()) : !!hidden;
    });
    /**
     * Computed property indicating if the field should be disabled
     */
    isDisabled = computed(() => {
        const { disabled } = this.field();
        return typeof disabled === 'function' ? disabled(this.form()) : !!disabled;
    });
    /**
     * Computed property that returns filtered options for fields that support options.
     * For fields without options, returns an empty array.
     *
     * Applies dynamic options filtering if a dynamicOptions function is provided.
     */
    filteredOptions = computed(() => {
        const field = this.field();
        const form = this.form();
        // Check if field has options property
        if (!('options' in field) || typeof field.options !== 'function') {
            return [];
        }
        const options = field.options();
        const dynamicOptionsFn = field.dynamicOptions;
        if (typeof dynamicOptionsFn !== 'function') {
            return options;
        }
        return dynamicOptionsFn(form, options, field.value());
    });
    /**
     * Initializes the directive by setting up reactive effects for
     * computed values and value watching
     */
    constructor() {
        this.initializeComputedValueEffect();
        this.watchComputedValueEffect();
    }
    /**
     * Sets up an effect to initialize computed values when the field is first loaded.
     * This only runs once to set the initial computed value.
     *
     * @private
     */
    initializeComputedValueEffect() {
        effect(() => {
            const field = this.field();
            if (!field.computedValue)
                return;
            const initialValue = field.computedValue(this.form());
            this.setValue(initialValue, false);
        }, { injector: this.injector });
    }
    /**
     * Sets up an effect to watch for changes in computed values and update
     * the field value accordingly. This runs whenever dependencies change.
     *
     * @private
     */
    watchComputedValueEffect() {
        effect(() => {
            const field = this.field();
            if (!field.computedValue)
                return;
            const newValue = field.computedValue(this.form());
            this.setValue(newValue, false);
        }, { injector: this.injector });
    }
    /**
     * Updates the field value and optionally marks it as touched and dirty.
     *
     * @param value - The new value to set
     * @param markTouched - Whether to mark the field as touched and dirty (default: true)
     *
     * @protected
     */
    setValue(value, markTouched = true) {
        const field = this.field();
        if (markTouched) {
            field.touched.set(true);
            field.dirty.set(true);
        }
        field.value.set(value);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BaseInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: BaseInputDirective, isStandalone: true, inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BaseInputDirective, decorators: [{
            type: Directive
        }], ctorParameters: () => [] });

class SignalFormHostDirective {
    viewContainerRef;
    constructor(viewContainerRef) {
        this.viewContainerRef = viewContainerRef;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormHostDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.14", type: SignalFormHostDirective, isStandalone: true, selector: "[signalFormHost]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormHostDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[signalFormHost]',
                    standalone: true,
                }]
        }], ctorParameters: () => [{ type: i0.ViewContainerRef }] });

class SignalFormAutocompleteFieldComponent extends BaseInputDirective {
    signalFormHostDirective;
    inputRef = viewChild('autocompleteInputWrapper');
    search = signal('');
    loadedOptions = signal([]);
    showDropdown = signal(false);
    lastQuery = signal('');
    cachedOptions = signal([]);
    destroyRef = inject(DestroyRef);
    dropdownService = inject(FormDropdownService);
    constructor(signalFormHostDirective) {
        super();
        this.signalFormHostDirective = signalFormHostDirective;
        this.searchQueryChangeEffect();
        this.optionsOverlayEffect();
    }
    get hostViewContainerRef() {
        return this.signalFormHostDirective?.viewContainerRef ?? null;
    }
    optionsOverlayEffect() {
        effect(() => {
            if (!this.showDropdown() ||
                !this.loadedOptions().length ||
                !this.inputRef()) {
                return;
            }
            this.dropdownService.openDropdown({
                options: this.loadedOptions(),
                ariaListboxId: `${String(this.field().name)}-listbox`,
                reference: this.inputRef().nativeElement,
                viewContainerRef: this.hostViewContainerRef,
                onSelect: (option) => {
                    if (option) {
                        this.updateValue(option);
                    }
                    this.field().touched.set(true);
                    this.showDropdown.set(false);
                    this.dropdownService.destroyDropdown();
                },
                onClose: () => {
                    this.showDropdown.set(false);
                },
                multiselect: false,
            });
        }, { injector: this.injector });
    }
    updateValue(val) {
        super.setValue(val);
        this.field().dirty.set(true);
    }
    onSelect(option) {
        this.updateValue(option);
        this.showDropdown.set(false);
    }
    handleFocus() {
        const query = this.lastQuery();
        if (!!query && this.cachedOptions().length) {
            this.showDropdown.set(true);
            this.loadedOptions.set(this.cachedOptions());
        }
    }
    searchQueryChangeEffect() {
        effect(() => {
            const query = this.search();
            this.lastQuery.set(query);
            if (this.field().config?.minChars &&
                query.length < (this.field().config?.minChars ?? 0)) {
                this.loadedOptions.set([]);
                return;
            }
            const loader = this.field().loadOptions;
            if (!loader || !query) {
                this.loadedOptions.set([]);
                return;
            }
            const source = loader(query);
            const obs$ = isObservable(source) ? source : from(source);
            const debounced$ = obs$.pipe(takeUntilDestroyed(this.destroyRef));
            const timeout = setTimeout(() => {
                debounced$.subscribe((result) => {
                    this.cachedOptions.set(result); // ✅ cache
                    this.loadedOptions.set(result);
                    this.showDropdown.set(true);
                });
            }, this.field().config?.debounceMs ?? 300);
            return () => clearTimeout(timeout);
        }, { injector: this.injector });
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormAutocompleteFieldComponent, deps: [{ token: SignalFormHostDirective, optional: true }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", type: SignalFormAutocompleteFieldComponent, isStandalone: true, selector: "signal-form-autocomplete-field", viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["autocompleteInputWrapper"], descendants: true, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: SignalFormHostDirective }], ngImport: i0, template: "<div\n  class=\"form-input-wrapper\"\n  [ngClass]=\"showDropdown() ? 'dropdown-open' : ''\"\n  #autocompleteInputWrapper\n>\n  <input\n    class=\"form-input\"\n    type=\"text\"\n    role=\"combobox\"\n    aria-autocomplete=\"list\"\n    [signalModel]=\"field()\"\n    [value]=\"field().value()?.label ?? ''\"\n    (input)=\"search.set($event.target.value)\"\n    (focus)=\"handleFocus()\"\n  />\n</div>\n", styles: [".autocomplete-dropdown{position:absolute;z-index:10;background:#fff;border:1px solid #ccc;width:100%;max-height:200px;overflow-y:auto;list-style:none;padding:unset;margin:0;top:38px;flex-direction:column}.autocomplete-dropdown li{padding:.5rem;cursor:pointer}.autocomplete-dropdown li:hover{background:#f4f4f4}.dropdown-open{border-bottom-left-radius:0;border-bottom-right-radius:0}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormAutocompleteFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-autocomplete-field', standalone: true, imports: [NgClass, SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [SignalFormHostDirective], template: "<div\n  class=\"form-input-wrapper\"\n  [ngClass]=\"showDropdown() ? 'dropdown-open' : ''\"\n  #autocompleteInputWrapper\n>\n  <input\n    class=\"form-input\"\n    type=\"text\"\n    role=\"combobox\"\n    aria-autocomplete=\"list\"\n    [signalModel]=\"field()\"\n    [value]=\"field().value()?.label ?? ''\"\n    (input)=\"search.set($event.target.value)\"\n    (focus)=\"handleFocus()\"\n  />\n</div>\n", styles: [".autocomplete-dropdown{position:absolute;z-index:10;background:#fff;border:1px solid #ccc;width:100%;max-height:200px;overflow-y:auto;list-style:none;padding:unset;margin:0;top:38px;flex-direction:column}.autocomplete-dropdown li{padding:.5rem;cursor:pointer}.autocomplete-dropdown li:hover{background:#f4f4f4}.dropdown-open{border-bottom-left-radius:0;border-bottom-right-radius:0}\n"] }]
        }], ctorParameters: () => [{ type: SignalFormHostDirective, decorators: [{
                    type: Optional
                }] }] });

class SignalFormCheckboxFieldComponent extends BaseInputDirective {
    label = input('');
    extractValue(element) {
        return element.checked;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormCheckboxFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: SignalFormCheckboxFieldComponent, isStandalone: true, selector: "signal-form-checkbox-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "<label class=\"form-checkbox-wrapper\" [attr.for]=\"field().name\">\n  <input class=\"checkbox\" type=\"checkbox\" [signalModel]=\"field()\" />\n  <span class=\"form-checkbox-label\">{{ field().label }}</span>\n</label>\n", styles: [".form-checkbox-label{font-size:var(--signal-form-font-size-xs);color:var(--signal-form-input-text)}.form-checkbox-wrapper{display:flex;justify-content:flex-start;align-items:center;gap:4px}.form-checkbox .checkbox{accent-color:var(--signal-form-outline-focus)}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormCheckboxFieldComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form-checkbox-field', standalone: true, imports: [SignalModelDirective], template: "<label class=\"form-checkbox-wrapper\" [attr.for]=\"field().name\">\n  <input class=\"checkbox\" type=\"checkbox\" [signalModel]=\"field()\" />\n  <span class=\"form-checkbox-label\">{{ field().label }}</span>\n</label>\n", styles: [".form-checkbox-label{font-size:var(--signal-form-font-size-xs);color:var(--signal-form-input-text)}.form-checkbox-wrapper{display:flex;justify-content:flex-start;align-items:center;gap:4px}.form-checkbox .checkbox{accent-color:var(--signal-form-outline-focus)}\n"] }]
        }] });

class SignalFormCheckboxGroupFieldComponent extends BaseInputDirective {
    layoutClass = computed(() => this.field().config?.layout === 'inline'
        ? 'checkbox-group-inline'
        : 'checkbox-group-stacked');
    isChecked = (option) => {
        const val = this.field().value();
        const key = option.value;
        if (this.field().config?.valueType === 'map') {
            const record = val;
            return record ? record[key] : false;
        }
        return val?.includes(key);
    };
    toggleOption(option) {
        const key = option.value;
        const valueType = this.field().config?.valueType ?? 'array';
        let val = this.field().value();
        // Normalize value
        if (!val) {
            val = valueType === 'map' ? {} : [];
        }
        if (valueType === 'map') {
            const current = (typeof val === 'object' && !Array.isArray(val) ? { ...val } : {});
            // Flip the selected value
            current[key] = !current[key];
            // Normalize to include all options with explicit true/false
            const fullRecord = {};
            for (const opt of this.field().options()) {
                const optKey = opt.value;
                fullRecord[optKey] = !!current[optKey];
            }
            this.setValue(fullRecord);
        }
        else {
            const arr = Array.isArray(val) ? val : [];
            this.setValue(arr.includes(key) ? arr.filter((v) => v !== key) : [...arr, key]);
        }
        this.field().touched.set(true);
        this.field().dirty.set(true);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormCheckboxGroupFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormCheckboxGroupFieldComponent, isStandalone: true, selector: "signal-form-checkbox-group-field", usesInheritance: true, ngImport: i0, template: "<fieldset\n  class=\"form-checkbox-group\"\n  [ngClass]=\"layoutClass()\"\n  [attr.aria-invalid]=\"field().error() ? 'true' : 'false'\"\n  role=\"group\"\n>\n  <legend class=\"sr-only\">{{ field().label }}</legend>\n\n  @for (option of field().options(); track option.value) {\n    <label class=\"checkbox-group-option\">\n      <input\n        type=\"checkbox\"\n        class=\"checkbox\"\n        [value]=\"option.value\"\n        [checked]=\"isChecked(option)\"\n        (change)=\"toggleOption(option)\"\n      />\n      <span class=\"label-text\">{{ option.label }}</span>\n    </label>\n  }\n</fieldset>\n", styles: [".form-checkbox-group{display:flex;flex-direction:column;gap:.5rem}.form-checkbox-group.checkbox-group-inline{flex-direction:row;flex-wrap:wrap}.form-checkbox-group.checkbox-group-inline .checkbox-group-option{margin-right:1.5rem}.form-checkbox-group .checkbox-group-option{display:flex;align-items:center;gap:.5rem;font-size:var(--signal-form-font-size-sm);cursor:pointer}.form-checkbox-group .checkbox-group-option .checkbox{width:16px;height:16px;accent-color:var(--signal-form-outline-focus)}.form-checkbox-group .checkbox-group-option .label-text{color:var(--signal-form-input-text)}.form-checkbox-group .checkbox-group-option:has(.checkbox:disabled){opacity:.5}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormCheckboxGroupFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-checkbox-group-field', standalone: true, imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset\n  class=\"form-checkbox-group\"\n  [ngClass]=\"layoutClass()\"\n  [attr.aria-invalid]=\"field().error() ? 'true' : 'false'\"\n  role=\"group\"\n>\n  <legend class=\"sr-only\">{{ field().label }}</legend>\n\n  @for (option of field().options(); track option.value) {\n    <label class=\"checkbox-group-option\">\n      <input\n        type=\"checkbox\"\n        class=\"checkbox\"\n        [value]=\"option.value\"\n        [checked]=\"isChecked(option)\"\n        (change)=\"toggleOption(option)\"\n      />\n      <span class=\"label-text\">{{ option.label }}</span>\n    </label>\n  }\n</fieldset>\n", styles: [".form-checkbox-group{display:flex;flex-direction:column;gap:.5rem}.form-checkbox-group.checkbox-group-inline{flex-direction:row;flex-wrap:wrap}.form-checkbox-group.checkbox-group-inline .checkbox-group-option{margin-right:1.5rem}.form-checkbox-group .checkbox-group-option{display:flex;align-items:center;gap:.5rem;font-size:var(--signal-form-font-size-sm);cursor:pointer}.form-checkbox-group .checkbox-group-option .checkbox{width:16px;height:16px;accent-color:var(--signal-form-outline-focus)}.form-checkbox-group .checkbox-group-option .label-text{color:var(--signal-form-input-text)}.form-checkbox-group .checkbox-group-option:has(.checkbox:disabled){opacity:.5}\n"] }]
        }] });

class SignalFormChipListFieldComponent extends BaseInputDirective {
    selectedValues = computed(() => this.field().value() ?? []);
    isSelected = (option) => !!this.selectedValues().find((val) => val.value === option.value);
    toggleOption(option) {
        const current = this.selectedValues();
        const updated = this.isSelected(option)
            ? current.filter((val) => val.value !== option.value)
            : [...current, option];
        this.setValue(updated);
        this.field().touched.set(true);
        this.field().dirty.set(true);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormChipListFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormChipListFieldComponent, isStandalone: true, selector: "signal-form-chip-list-field", usesInheritance: true, ngImport: i0, template: "<div\n  class=\"chip-option-list\"\n  role=\"group\"\n  [signalModel]=\"field()\"\n  [attr.aria-labelledby]=\"field().name + '-label'\"\n>\n  @for (option of field().options(); track option.value) {\n    <div\n      class=\"chip-option\"\n      role=\"checkbox\"\n      tabindex=\"0\"\n      [class.selected]=\"isSelected(option)\"\n      [attr.aria-checked]=\"isSelected(option)\"\n      (click)=\"toggleOption(option)\"\n      (keydown.enter)=\"toggleOption(option)\"\n      (keydown.space)=\"toggleOption(option); $event.preventDefault()\"\n    >\n      {{ option.label }}\n    </div>\n  }\n</div>\n", styles: [".chip-option-list{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-sm);padding:var(--signal-form-padding-md);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-bg);box-shadow:var(--signal-form-shadow);transition:var(--signal-form-transition)}.chip-option-list .chip-option{display:inline-flex;align-items:center;padding:var(--signal-form-padding-sm) var(--signal-form-padding-lg);border-radius:var(--signal-form-border-radius-sm);border:1px solid var(--signal-form-border-color);background:var(--signal-form-bg);color:var(--signal-form-text);cursor:pointer;font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);transition:var(--signal-form-transition);-webkit-user-select:none;user-select:none}.chip-option-list .chip-option:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.chip-option-list .chip-option:focus{outline:none;border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.chip-option-list .chip-option.selected{background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-color:var(--signal-form-button-primary-bg)}.chip-option-list .chip-option.selected:hover{background:var(--signal-form-button-primary-bg-hover);border-color:var(--signal-form-button-primary-bg-hover)}.selected-chips{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);flex:1;min-width:0}.chip{display:inline-flex;align-items:center;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:var(--signal-form-transition)}.chip .chip-remove{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;padding:0;border:none;background:#fff3;border-radius:var(--signal-form-radius-full);color:var(--signal-form-button-text);cursor:pointer;font-size:var(--signal-form-font-size-xs);transition:var(--signal-form-transition)}.chip .chip-remove:hover{background:#ffffff4d;transform:scale(1.1)}.chip .chip-remove:focus{outline:none;background:#fff6}.dropdown-trigger{display:inline-flex;align-items:center;gap:var(--signal-form-gap-sm);padding:var(--signal-form-padding-sm) var(--signal-form-padding-md);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-bg);color:var(--signal-form-text);cursor:pointer;font-size:var(--signal-form-font-size-sm);min-width:120px;transition:var(--signal-form-transition)}.dropdown-trigger:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.dropdown-trigger:focus{outline:none;border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.dropdown-trigger .dropdown-arrow{opacity:.5;transition:transform .2s}.dropdown-trigger[aria-expanded=true] .dropdown-arrow{transform:rotate(180deg)}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormChipListFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-chip-list-field', standalone: true, imports: [SignalModelDirective], template: "<div\n  class=\"chip-option-list\"\n  role=\"group\"\n  [signalModel]=\"field()\"\n  [attr.aria-labelledby]=\"field().name + '-label'\"\n>\n  @for (option of field().options(); track option.value) {\n    <div\n      class=\"chip-option\"\n      role=\"checkbox\"\n      tabindex=\"0\"\n      [class.selected]=\"isSelected(option)\"\n      [attr.aria-checked]=\"isSelected(option)\"\n      (click)=\"toggleOption(option)\"\n      (keydown.enter)=\"toggleOption(option)\"\n      (keydown.space)=\"toggleOption(option); $event.preventDefault()\"\n    >\n      {{ option.label }}\n    </div>\n  }\n</div>\n", styles: [".chip-option-list{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-sm);padding:var(--signal-form-padding-md);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-bg);box-shadow:var(--signal-form-shadow);transition:var(--signal-form-transition)}.chip-option-list .chip-option{display:inline-flex;align-items:center;padding:var(--signal-form-padding-sm) var(--signal-form-padding-lg);border-radius:var(--signal-form-border-radius-sm);border:1px solid var(--signal-form-border-color);background:var(--signal-form-bg);color:var(--signal-form-text);cursor:pointer;font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);transition:var(--signal-form-transition);-webkit-user-select:none;user-select:none}.chip-option-list .chip-option:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.chip-option-list .chip-option:focus{outline:none;border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.chip-option-list .chip-option.selected{background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-color:var(--signal-form-button-primary-bg)}.chip-option-list .chip-option.selected:hover{background:var(--signal-form-button-primary-bg-hover);border-color:var(--signal-form-button-primary-bg-hover)}.selected-chips{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);flex:1;min-width:0}.chip{display:inline-flex;align-items:center;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:var(--signal-form-transition)}.chip .chip-remove{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;padding:0;border:none;background:#fff3;border-radius:var(--signal-form-radius-full);color:var(--signal-form-button-text);cursor:pointer;font-size:var(--signal-form-font-size-xs);transition:var(--signal-form-transition)}.chip .chip-remove:hover{background:#ffffff4d;transform:scale(1.1)}.chip .chip-remove:focus{outline:none;background:#fff6}.dropdown-trigger{display:inline-flex;align-items:center;gap:var(--signal-form-gap-sm);padding:var(--signal-form-padding-sm) var(--signal-form-padding-md);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-bg);color:var(--signal-form-text);cursor:pointer;font-size:var(--signal-form-font-size-sm);min-width:120px;transition:var(--signal-form-transition)}.dropdown-trigger:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.dropdown-trigger:focus{outline:none;border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.dropdown-trigger .dropdown-arrow{opacity:.5;transition:transform .2s}.dropdown-trigger[aria-expanded=true] .dropdown-arrow{transform:rotate(180deg)}\n"] }]
        }] });

class SignalFormColorFieldComponent extends BaseInputDirective {
    fallbackColor = '#000000';
    currentColor = computed(() => this.field().value() ?? this.fallbackColor);
    isSwatchOnly = computed(() => {
        return this.field().config?.view === 'swatch';
    });
    inputValue = computed(() => this.field().value() ?? this.fallbackColor);
    constructor() {
        super();
        effect(() => {
            if (!CSS.supports('color', this.inputValue())) {
                this.field().error.set('unsupported Color value!');
            }
        });
    }
    onTextInputChange(val) {
        this.setValue(val);
    }
    onColorInputChange(event) {
        const value = event.target.value;
        this.setValue(value);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormColorFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormColorFieldComponent, isStandalone: true, selector: "signal-form-color-field", usesInheritance: true, ngImport: i0, template: "<div class=\"color-field-wrapper\">\n  @if (isSwatchOnly()) {\n    <label class=\"color-swatch\">\n      <input type=\"color\" class=\"sr-only\" [signalModel]=\"field()\" />\n      <span\n        class=\"swatch-display\"\n        [style.background]=\"currentColor()\"\n        aria-hidden=\"true\"\n      ></span>\n    </label>\n  } @else {\n    <div class=\"color-picker-full\">\n      <input type=\"color\" class=\"color-input\" [signalModel]=\"field()\" />\n\n      <input\n        type=\"text\"\n        class=\"hex-input\"\n        [value]=\"inputValue()\"\n        [attr.aria-label]=\"'Hex color code for ' + field().name\"\n        (input)=\"onTextInputChange($event.target.value)\"\n      />\n    </div>\n  }\n</div>\n", styles: [".color-field-wrapper{display:flex;flex-direction:column;gap:8px}.color-swatch{position:relative;display:flex;align-items:center}.color-swatch input[type=color]{opacity:0;width:40px;height:40px;position:absolute;left:0;top:0;cursor:pointer}.color-swatch .swatch-display{width:40px;height:40px;border-radius:50%;border:2px solid var(--signal-form-border-color);box-shadow:var(--signal-form-shadow);cursor:pointer}.color-picker-full{display:flex;align-items:center;gap:12px}.color-picker-full .color-input{width:40px;height:40px;padding:0;border:none;background:none}.color-picker-full .hex-input{flex:1;padding:var(--signal-form-field-padding);border-radius:var(--signal-form-border-radius-sm);border:1px solid var(--signal-form-border-color);background-color:var(--signal-form-input-bg);color:var(--signal-form-input-text);font-family:monospace;transition:var(--signal-form-transition)}.color-picker-full .hex-input:focus{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormColorFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-color-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"color-field-wrapper\">\n  @if (isSwatchOnly()) {\n    <label class=\"color-swatch\">\n      <input type=\"color\" class=\"sr-only\" [signalModel]=\"field()\" />\n      <span\n        class=\"swatch-display\"\n        [style.background]=\"currentColor()\"\n        aria-hidden=\"true\"\n      ></span>\n    </label>\n  } @else {\n    <div class=\"color-picker-full\">\n      <input type=\"color\" class=\"color-input\" [signalModel]=\"field()\" />\n\n      <input\n        type=\"text\"\n        class=\"hex-input\"\n        [value]=\"inputValue()\"\n        [attr.aria-label]=\"'Hex color code for ' + field().name\"\n        (input)=\"onTextInputChange($event.target.value)\"\n      />\n    </div>\n  }\n</div>\n", styles: [".color-field-wrapper{display:flex;flex-direction:column;gap:8px}.color-swatch{position:relative;display:flex;align-items:center}.color-swatch input[type=color]{opacity:0;width:40px;height:40px;position:absolute;left:0;top:0;cursor:pointer}.color-swatch .swatch-display{width:40px;height:40px;border-radius:50%;border:2px solid var(--signal-form-border-color);box-shadow:var(--signal-form-shadow);cursor:pointer}.color-picker-full{display:flex;align-items:center;gap:12px}.color-picker-full .color-input{width:40px;height:40px;padding:0;border:none;background:none}.color-picker-full .hex-input{flex:1;padding:var(--signal-form-field-padding);border-radius:var(--signal-form-border-radius-sm);border:1px solid var(--signal-form-border-color);background-color:var(--signal-form-input-bg);color:var(--signal-form-input-text);font-family:monospace;transition:var(--signal-form-transition)}.color-picker-full .hex-input:focus{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}\n"] }]
        }], ctorParameters: () => [] });

class SignalFormDatetimeFieldComponent extends BaseInputDirective {
    formattedValue = computed(() => {
        const date = this.field().value();
        const format = this.field().config?.format;
        const formatter = this.field().config?.formatterFn;
        if (!date || !(date instanceof Date)) {
            return '';
        }
        return formatter ? formatter(date) : this.defaultFormat(date, format);
    });
    defaultFormat(date, format) {
        // fallback formatting – keep it native or simple
        return date.toISOString().slice(0, 16); // e.g., '2025-06-19T12:34'
    }
    extractValue(el) {
        return new Date(el.value);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormDatetimeFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SignalFormDatetimeFieldComponent, isStandalone: true, selector: "signal-form-datetime-field", usesInheritance: true, ngImport: i0, template: "<input class=\"form-input\" type=\"datetime-local\" [signalModel]=\"field()\" />\n", styles: [""], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormDatetimeFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-datetime-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<input class=\"form-input\" type=\"datetime-local\" [signalModel]=\"field()\" />\n" }]
        }] });

class SignalFormFileFieldComponent extends BaseInputDirective {
    isDragging = signal(false);
    uploadProgress = signal(null);
    imagePreview = signal(null);
    maxSizeBytes = computed(() => (this.field().config?.maxSizeMb ?? 5) * 1024 * 1024);
    isImageFile = computed(() => {
        const file = this.field().value();
        return file && file.type.startsWith('image/');
    });
    fileName = computed(() => {
        const file = this.field().value();
        return file?.name || '';
    });
    onFileChange(event) {
        const input = event.target;
        const file = input.files?.[0];
        if (!file) {
            return;
        }
        const maxSize = this.maxSizeBytes();
        if (file.size > maxSize) {
            this.field().error.set(`File exceeds max size of ${this.field().config?.maxSizeMb ?? 5}MB`);
            return;
        }
        this.field().error.set('');
        this.setValue(file);
        this.uploadProgress.set(0);
        this.simulateUpload(file);
        this.field().touched.set(true);
        if (file.type.startsWith('image/')) {
            this.createImagePreview(file);
        }
        else {
            this.imagePreview.set(null);
        }
    }
    createImagePreview(file) {
        const reader = new FileReader();
        reader.onload = (e) => {
            this.imagePreview.set(e.target?.result);
        };
        reader.readAsDataURL(file);
    }
    simulateUpload(file) {
        let progress = 0;
        const interval = setInterval(() => {
            progress += 10;
            this.uploadProgress.set(progress);
            if (progress >= 100) {
                clearInterval(interval);
                this.uploadProgress.set(null);
            }
        }, 100);
    }
    onDrop(event) {
        event.preventDefault();
        this.isDragging.set(false);
        const file = event.dataTransfer?.files?.[0];
        if (file) {
            const fakeEvent = {
                target: { files: [file] },
            };
            this.onFileChange(fakeEvent);
        }
    }
    onDragOver(event) {
        event.preventDefault();
        this.isDragging.set(true);
    }
    onDragLeave() {
        this.isDragging.set(false);
    }
    openFileDialog(input) {
        input.click();
    }
    removeFile() {
        this.setValue(null);
        this.imagePreview.set(null);
        this.field().touched.set(true);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormFileFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormFileFieldComponent, isStandalone: true, selector: "signal-form-file-field", usesInheritance: true, hostDirectives: [{ directive: SignalFormHostDirective }], ngImport: i0, template: "<div\n  class=\"file-upload-dropzone\"\n  role=\"button\"\n  [signalModel]=\"field()\"\n  [class.dragging]=\"isDragging()\"\n  [class.has-file]=\"!!field().value()\"\n  (drop)=\"onDrop($event)\"\n  (dragover)=\"onDragOver($event)\"\n  (dragleave)=\"onDragLeave()\"\n  (click)=\"openFileDialog(fileInput)\"\n  (keydown.enter)=\"openFileDialog(fileInput)\"\n  (keydown.space)=\"openFileDialog(fileInput); $event.preventDefault()\"\n>\n  @if (imagePreview()) {\n    <div class=\"image-preview-container\">\n      <img [src]=\"imagePreview()\" alt=\"Preview\" class=\"image-preview\" />\n      <button\n        type=\"button\"\n        class=\"remove-file-btn\"\n        (click)=\"removeFile(); $event.stopPropagation()\"\n        aria-label=\"Remove file\"\n      >\n        \u2715\n      </button>\n    </div>\n  } @else if (field().value()) {\n    <div class=\"uploaded-file\">\n      <span class=\"file-name\">{{ fileName() }}</span>\n      <button\n        type=\"button\"\n        class=\"remove-file-btn\"\n        (click)=\"removeFile(); $event.stopPropagation()\"\n        aria-label=\"Remove file\"\n      >\n        \u2715\n      </button>\n    </div>\n  } @else {\n    <div class=\"upload-icon\" aria-hidden=\"true\">\u2601\uFE0F</div>\n    <div class=\"upload-text\">\n      {{ field().config?.uploadText || \"Click or drag a file to upload\" }}\n    </div>\n  }\n\n  <input\n    #fileInput\n    class=\"sr-only\"\n    type=\"file\"\n    [accept]=\"field().config?.accept || '*/*'\"\n    (change)=\"onFileChange($event)\"\n  />\n\n  @if (uploadProgress() !== null) {\n    <div\n      class=\"progress-bar\"\n      role=\"progressbar\"\n      [attr.aria-valuenow]=\"uploadProgress()\"\n      aria-valuemin=\"0\"\n      aria-valuemax=\"100\"\n    >\n      <div class=\"progress\" [style.width.%]=\"uploadProgress()\"></div>\n    </div>\n  }\n</div>\n", styles: [".file-upload-dropzone{border:2px dashed var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-md);padding:2rem;text-align:center;cursor:pointer;transition:var(--signal-form-transition);background:var(--signal-form-input-bg);color:var(--signal-form-input-text);position:relative;min-height:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem}.file-upload-dropzone:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.file-upload-dropzone.dragging{border-color:var(--signal-form-outline-focus);background:var(--signal-forms-primary-50)}.file-upload-dropzone.has-file{border-color:var(--signal-forms-success-500);background:var(--signal-forms-success-50)}.image-preview-container{position:relative;display:flex;flex-direction:column;align-items:center;gap:.5rem}.image-preview{max-width:200px;max-height:200px;border-radius:var(--signal-form-border-radius-md);box-shadow:var(--signal-form-shadow-lg);object-fit:cover}.uploaded-file{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:var(--signal-form-input-bg);border-radius:var(--signal-form-border-radius-sm);box-shadow:var(--signal-form-shadow)}.file-name{font-weight:500;color:var(--signal-form-input-text)}.remove-file-btn{background:var(--signal-forms-danger-500);color:var(--signal-forms-neutral-0);border:none;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;transition:var(--signal-form-transition)}.remove-file-btn:hover{background:var(--signal-forms-danger-600)}.upload-icon{font-size:2rem;margin-bottom:.5rem}.upload-text{color:var(--signal-form-muted);font-size:.875rem}.progress-bar{width:100%;height:4px;background:var(--signal-forms-neutral-200);border-radius:2px;overflow:hidden;margin-top:1rem}.progress{height:100%;background:var(--signal-form-outline-focus);transition:width .3s ease}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormFileFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-file-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [SignalFormHostDirective], template: "<div\n  class=\"file-upload-dropzone\"\n  role=\"button\"\n  [signalModel]=\"field()\"\n  [class.dragging]=\"isDragging()\"\n  [class.has-file]=\"!!field().value()\"\n  (drop)=\"onDrop($event)\"\n  (dragover)=\"onDragOver($event)\"\n  (dragleave)=\"onDragLeave()\"\n  (click)=\"openFileDialog(fileInput)\"\n  (keydown.enter)=\"openFileDialog(fileInput)\"\n  (keydown.space)=\"openFileDialog(fileInput); $event.preventDefault()\"\n>\n  @if (imagePreview()) {\n    <div class=\"image-preview-container\">\n      <img [src]=\"imagePreview()\" alt=\"Preview\" class=\"image-preview\" />\n      <button\n        type=\"button\"\n        class=\"remove-file-btn\"\n        (click)=\"removeFile(); $event.stopPropagation()\"\n        aria-label=\"Remove file\"\n      >\n        \u2715\n      </button>\n    </div>\n  } @else if (field().value()) {\n    <div class=\"uploaded-file\">\n      <span class=\"file-name\">{{ fileName() }}</span>\n      <button\n        type=\"button\"\n        class=\"remove-file-btn\"\n        (click)=\"removeFile(); $event.stopPropagation()\"\n        aria-label=\"Remove file\"\n      >\n        \u2715\n      </button>\n    </div>\n  } @else {\n    <div class=\"upload-icon\" aria-hidden=\"true\">\u2601\uFE0F</div>\n    <div class=\"upload-text\">\n      {{ field().config?.uploadText || \"Click or drag a file to upload\" }}\n    </div>\n  }\n\n  <input\n    #fileInput\n    class=\"sr-only\"\n    type=\"file\"\n    [accept]=\"field().config?.accept || '*/*'\"\n    (change)=\"onFileChange($event)\"\n  />\n\n  @if (uploadProgress() !== null) {\n    <div\n      class=\"progress-bar\"\n      role=\"progressbar\"\n      [attr.aria-valuenow]=\"uploadProgress()\"\n      aria-valuemin=\"0\"\n      aria-valuemax=\"100\"\n    >\n      <div class=\"progress\" [style.width.%]=\"uploadProgress()\"></div>\n    </div>\n  }\n</div>\n", styles: [".file-upload-dropzone{border:2px dashed var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-md);padding:2rem;text-align:center;cursor:pointer;transition:var(--signal-form-transition);background:var(--signal-form-input-bg);color:var(--signal-form-input-text);position:relative;min-height:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem}.file-upload-dropzone:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.file-upload-dropzone.dragging{border-color:var(--signal-form-outline-focus);background:var(--signal-forms-primary-50)}.file-upload-dropzone.has-file{border-color:var(--signal-forms-success-500);background:var(--signal-forms-success-50)}.image-preview-container{position:relative;display:flex;flex-direction:column;align-items:center;gap:.5rem}.image-preview{max-width:200px;max-height:200px;border-radius:var(--signal-form-border-radius-md);box-shadow:var(--signal-form-shadow-lg);object-fit:cover}.uploaded-file{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:var(--signal-form-input-bg);border-radius:var(--signal-form-border-radius-sm);box-shadow:var(--signal-form-shadow)}.file-name{font-weight:500;color:var(--signal-form-input-text)}.remove-file-btn{background:var(--signal-forms-danger-500);color:var(--signal-forms-neutral-0);border:none;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;transition:var(--signal-form-transition)}.remove-file-btn:hover{background:var(--signal-forms-danger-600)}.upload-icon{font-size:2rem;margin-bottom:.5rem}.upload-text{color:var(--signal-form-muted);font-size:.875rem}.progress-bar{width:100%;height:4px;background:var(--signal-forms-neutral-200);border-radius:2px;overflow:hidden;margin-top:1rem}.progress{height:100%;background:var(--signal-form-outline-focus);transition:width .3s ease}\n"] }]
        }] });

class SignalFormMultiselectFieldComponent extends BaseInputDirective {
    showDropdown = signal(false);
    dropdownService = inject(FormDropdownService);
    host = inject(SignalFormHostDirective);
    constructor() {
        super();
        this.dropdownOverlayEffect();
    }
    dropdownOverlayEffect() {
        effect(() => {
            if (!this.showDropdown()) {
                return;
            }
            const reference = this.host.viewContainerRef.element.nativeElement;
            this.dropdownService.openDropdown({
                options: this.field().options(),
                reference,
                viewContainerRef: this.host.viewContainerRef,
                multiselect: true,
                ariaListboxId: `${String(this.field().name)}-listbox`,
                initialSelection: this.field().value(),
                onSelect: (selected) => {
                    this.setValue(selected);
                    this.field().touched.set(true);
                },
                onClose: () => {
                    this.showDropdown.set(false);
                },
            });
        }, {
            injector: this.injector,
        });
    }
    removeChip(option) {
        this.field().value.update((vals) => (vals ?? []).filter((o) => o.value !== option.value));
        this.field().touched.set(true);
    }
    toggleDropdown() {
        this.showDropdown.update((show) => !show);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormMultiselectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormMultiselectFieldComponent, isStandalone: true, selector: "signal-form-multiselect-field", usesInheritance: true, hostDirectives: [{ directive: SignalFormHostDirective }], ngImport: i0, template: "<div\n  class=\"multi-select-wrapper\"\n  #multiselectWrapper\n  role=\"combobox\"\n  [signalModel]=\"field()\"\n  [attr.aria-expanded]=\"showDropdown()\"\n  [attr.aria-multiselectable]=\"true\"\n  [attr.aria-haspopup]=\"'listbox'\"\n  (keydown.enter)=\"toggleDropdown()\"\n>\n  <div\n    class=\"chip-list\"\n    role=\"list\"\n    [attr.aria-label]=\"'Selected ' + field().label + ' items'\"\n    (click)=\"toggleDropdown()\"\n  >\n    @for (item of field().value() ?? []; track item.value) {\n      <div class=\"chip\" role=\"listitem\">\n        {{ item.label }}\n        <button\n          type=\"button\"\n          class=\"remove-btn\"\n          (click)=\"removeChip(item); $event.stopPropagation()\"\n          (keydown.enter)=\"removeChip(item)\"\n          [attr.aria-label]=\"'Remove ' + item.label\"\n        >\n          \u00D7\n        </button>\n      </div>\n    }\n  </div>\n\n  @if (!field().value()?.length) {\n    <div class=\"placeholder\">\n      {{ field().config?.placeholder }}\n    </div>\n  }\n</div>\n", styles: [".multi-select-wrapper{font-size:var(--signal-form-font-size-base);color:var(--signal-form-input-text);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);box-shadow:var(--signal-form-shadow);transition:var(--signal-form-transition);background:var(--signal-form-input-bg);cursor:pointer;display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);min-height:var(--signal-form-input-height);position:relative}.multi-select-wrapper:focus-within{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.multi-select-wrapper:hover{border-color:var(--signal-forms-neutral-400)}.multi-select-wrapper .chip-list{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);align-items:center;flex:1}.multi-select-wrapper .chip{display:inline-flex;align-items:center;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);line-height:1.25;transition:var(--signal-form-transition)}.multi-select-wrapper .chip:hover{background:var(--signal-form-button-primary-bg-hover)}.multi-select-wrapper .chip .remove-btn{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:none;background:#fff3;color:var(--signal-form-button-text);border-radius:var(--signal-form-radius-full);cursor:pointer;font-size:var(--signal-form-font-size-xs);font-weight:var(--signal-form-font-weight-semibold);line-height:1;transition:var(--signal-form-transition)}.multi-select-wrapper .chip .remove-btn:hover{background:#ffffff4d;transform:scale(1.1)}.multi-select-wrapper .chip .remove-btn:focus{outline:none;background:#fff6}.multi-select-wrapper .placeholder{color:var(--signal-form-placeholder-color);font-size:var(--signal-form-font-size-base);font-style:italic;pointer-events:none;position:absolute;top:50%;left:var(--signal-form-padding-md);transform:translateY(-50%);z-index:1;transition:var(--signal-form-transition)}.chip-list:not(:empty)~.multi-select-wrapper .placeholder{display:none}.multi-select-wrapper:has(.chip) .placeholder{display:none}.multi-select-wrapper:empty .placeholder{display:block}.multi-select-wrapper[aria-disabled=true]{background-color:var(--signal-form-disabled-bg);color:var(--signal-form-muted);cursor:not-allowed}.multi-select-wrapper[aria-disabled=true] .chip{background:var(--signal-forms-neutral-200);color:var(--signal-form-muted)}.multi-select-wrapper[aria-disabled=true] .chip .remove-btn{cursor:not-allowed;opacity:.5}.multiselect-container{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);min-height:var(--signal-form-input-height);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-bg);cursor:text;transition:var(--signal-form-transition)}.multiselect-container:focus-within{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.selected-values{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);flex:1;min-width:0}.value-tag{display:inline-flex;align-items:center;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:var(--signal-form-transition)}.value-tag .remove-button{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;padding:0;border:none;background:#fff3;border-radius:var(--signal-form-radius-full);color:var(--signal-form-button-text);cursor:pointer;font-size:var(--signal-form-font-size-xs);font-weight:var(--signal-form-font-weight-semibold);transition:var(--signal-form-transition)}.value-tag .remove-button:hover{background:#ffffff4d;transform:scale(1.1)}.value-tag .remove-button:focus{outline:none;background:#fff6}.placeholder{color:var(--signal-form-muted);font-style:italic}.dropdown-indicator{position:absolute;right:var(--signal-form-padding-md);top:50%;transform:translateY(-50%);pointer-events:none;color:var(--signal-form-muted);transition:var(--signal-form-transition)}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormMultiselectFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-multiselect-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [SignalFormHostDirective], template: "<div\n  class=\"multi-select-wrapper\"\n  #multiselectWrapper\n  role=\"combobox\"\n  [signalModel]=\"field()\"\n  [attr.aria-expanded]=\"showDropdown()\"\n  [attr.aria-multiselectable]=\"true\"\n  [attr.aria-haspopup]=\"'listbox'\"\n  (keydown.enter)=\"toggleDropdown()\"\n>\n  <div\n    class=\"chip-list\"\n    role=\"list\"\n    [attr.aria-label]=\"'Selected ' + field().label + ' items'\"\n    (click)=\"toggleDropdown()\"\n  >\n    @for (item of field().value() ?? []; track item.value) {\n      <div class=\"chip\" role=\"listitem\">\n        {{ item.label }}\n        <button\n          type=\"button\"\n          class=\"remove-btn\"\n          (click)=\"removeChip(item); $event.stopPropagation()\"\n          (keydown.enter)=\"removeChip(item)\"\n          [attr.aria-label]=\"'Remove ' + item.label\"\n        >\n          \u00D7\n        </button>\n      </div>\n    }\n  </div>\n\n  @if (!field().value()?.length) {\n    <div class=\"placeholder\">\n      {{ field().config?.placeholder }}\n    </div>\n  }\n</div>\n", styles: [".multi-select-wrapper{font-size:var(--signal-form-font-size-base);color:var(--signal-form-input-text);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);box-shadow:var(--signal-form-shadow);transition:var(--signal-form-transition);background:var(--signal-form-input-bg);cursor:pointer;display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);min-height:var(--signal-form-input-height);position:relative}.multi-select-wrapper:focus-within{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.multi-select-wrapper:hover{border-color:var(--signal-forms-neutral-400)}.multi-select-wrapper .chip-list{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);align-items:center;flex:1}.multi-select-wrapper .chip{display:inline-flex;align-items:center;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);line-height:1.25;transition:var(--signal-form-transition)}.multi-select-wrapper .chip:hover{background:var(--signal-form-button-primary-bg-hover)}.multi-select-wrapper .chip .remove-btn{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:none;background:#fff3;color:var(--signal-form-button-text);border-radius:var(--signal-form-radius-full);cursor:pointer;font-size:var(--signal-form-font-size-xs);font-weight:var(--signal-form-font-weight-semibold);line-height:1;transition:var(--signal-form-transition)}.multi-select-wrapper .chip .remove-btn:hover{background:#ffffff4d;transform:scale(1.1)}.multi-select-wrapper .chip .remove-btn:focus{outline:none;background:#fff6}.multi-select-wrapper .placeholder{color:var(--signal-form-placeholder-color);font-size:var(--signal-form-font-size-base);font-style:italic;pointer-events:none;position:absolute;top:50%;left:var(--signal-form-padding-md);transform:translateY(-50%);z-index:1;transition:var(--signal-form-transition)}.chip-list:not(:empty)~.multi-select-wrapper .placeholder{display:none}.multi-select-wrapper:has(.chip) .placeholder{display:none}.multi-select-wrapper:empty .placeholder{display:block}.multi-select-wrapper[aria-disabled=true]{background-color:var(--signal-form-disabled-bg);color:var(--signal-form-muted);cursor:not-allowed}.multi-select-wrapper[aria-disabled=true] .chip{background:var(--signal-forms-neutral-200);color:var(--signal-form-muted)}.multi-select-wrapper[aria-disabled=true] .chip .remove-btn{cursor:not-allowed;opacity:.5}.multiselect-container{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);min-height:var(--signal-form-input-height);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-bg);cursor:text;transition:var(--signal-form-transition)}.multiselect-container:focus-within{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.selected-values{display:flex;flex-wrap:wrap;gap:var(--signal-form-gap-xs);flex:1;min-width:0}.value-tag{display:inline-flex;align-items:center;gap:var(--signal-form-gap-xs);padding:var(--signal-form-padding-xs) var(--signal-form-padding-sm);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border-radius:var(--signal-form-border-radius-sm);font-size:var(--signal-form-font-size-sm);font-weight:var(--signal-form-font-weight-medium);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:var(--signal-form-transition)}.value-tag .remove-button{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;padding:0;border:none;background:#fff3;border-radius:var(--signal-form-radius-full);color:var(--signal-form-button-text);cursor:pointer;font-size:var(--signal-form-font-size-xs);font-weight:var(--signal-form-font-weight-semibold);transition:var(--signal-form-transition)}.value-tag .remove-button:hover{background:#ffffff4d;transform:scale(1.1)}.value-tag .remove-button:focus{outline:none;background:#fff6}.placeholder{color:var(--signal-form-muted);font-style:italic}.dropdown-indicator{position:absolute;right:var(--signal-form-padding-md);top:50%;transform:translateY(-50%);pointer-events:none;color:var(--signal-form-muted);transition:var(--signal-form-transition)}\n"] }]
        }], ctorParameters: () => [] });

class SignalFormNumberFieldComponent extends BaseInputDirective {
    NumberInputType = NumberInputType;
    currentUnit = signal('');
    isEditing = signal(false);
    inputType = computed(() => this.field().config?.inputType ?? NumberInputType.STANDARD);
    hasUnitConversion = computed(() => this.inputType() === NumberInputType.UNIT_CONVERSION &&
        !!this.field().config?.unitConversions);
    unitConfig = computed(() => this.field().config?.unitConversions ?? null);
    availableUnits = computed(() => {
        const config = this.unitConfig();
        return config
            ? Object.keys(config.unitConversions).map((key) => ({
                key,
                ...config.unitConversions[key],
            }))
            : [];
    });
    unitPosition = computed(() => this.unitConfig()?.unitPosition ?? 'suffix');
    inputStep = computed(() => {
        const config = this.field().config;
        if (config?.step !== undefined) {
            return config.step;
        }
        switch (this.inputType()) {
            case NumberInputType.CURRENCY:
                return 0.01;
            case NumberInputType.PERCENTAGE:
                return 0.1;
            case NumberInputType.INTEGER:
                return 1;
            default:
                return 'any';
        }
    });
    inputMin = computed(() => this.field().config?.min);
    inputMax = computed(() => this.field().config?.max);
    formattedValue = computed(() => {
        const value = this.field().value();
        if (value === null || value === undefined || isNaN(value)) {
            return '';
        }
        const config = this.field().config;
        const inputType = this.inputType();
        const locale = config?.locale ?? 'en-US';
        // Check for unit-specific parser first
        if (this.hasUnitConversion()) {
            const unitConfig = this.unitConfig();
            const currentUnitKey = this.currentUnit();
            const unitData = unitConfig?.unitConversions[currentUnitKey];
            if (unitData?.parser) {
                return unitData.parser(value);
            }
        }
        // Check for field-level custom parser
        if (config?.parser) {
            return config.parser(value);
        }
        // Apply built-in formatting based on inputType
        switch (inputType) {
            case NumberInputType.CURRENCY:
                const currencyCode = config?.currencyCode ?? 'USD';
                return new Intl.NumberFormat(locale, {
                    style: 'currency',
                    currency: currencyCode,
                }).format(value);
            case NumberInputType.PERCENTAGE:
                return new Intl.NumberFormat(locale, {
                    style: 'percent',
                    minimumFractionDigits: 1,
                }).format(value / 100);
            case NumberInputType.DECIMAL:
                const fractionDigits = config?.decimalPlaces ?? 2;
                return new Intl.NumberFormat(locale, {
                    minimumFractionDigits: fractionDigits,
                    maximumFractionDigits: fractionDigits,
                }).format(value);
            case NumberInputType.INTEGER:
                return new Intl.NumberFormat(locale, {
                    maximumFractionDigits: 0,
                }).format(value);
            default:
                return value.toString();
        }
    });
    showFormattedValue = computed(() => {
        return (this.inputType() !== NumberInputType.STANDARD &&
            !this.isEditing() &&
            !!this.field().value() &&
            this.field().value() !== 0);
    });
    rawValue = computed(() => {
        const value = this.field().value();
        return value?.toString() ?? '';
    });
    onFocusFormattedDisplay() {
        // Switch to raw input mode
        this.isEditing.set(true);
        // Use setTimeout to ensure the input is rendered before focusing
        setTimeout(() => {
            const input = document.querySelector('.form-input[type="number"]');
            if (input) {
                input.focus();
                input.select(); // Select all text for easy editing
            }
        }, 0);
    }
    onBlurRawInput() {
        // Switch back to formatted display mode with a small delay
        // to prevent immediate blur when switching elements
        setTimeout(() => {
            this.isEditing.set(false);
        }, 100);
    }
    ngOnInit() {
        // Initialize unit for unit conversion fields
        if (this.hasUnitConversion()) {
            const defaultUnit = this.unitConfig()?.defaultUnit;
            if (defaultUnit) {
                this.currentUnit.set(defaultUnit);
            }
        }
    }
    onUnitChange(newUnit) {
        if (!this.hasUnitConversion())
            return;
        const currentValue = this.field().value();
        if (currentValue === null ||
            currentValue === undefined ||
            isNaN(currentValue)) {
            this.currentUnit.set(newUnit);
            return;
        }
        const unitConfig = this.unitConfig();
        if (!unitConfig)
            return;
        const oldUnit = this.currentUnit();
        const newUnitConverter = unitConfig.unitConversions[newUnit]?.convert;
        if (newUnitConverter && oldUnit !== newUnit) {
            const convertedValue = newUnitConverter(currentValue, oldUnit);
            // Apply precision rounding if specified
            const precision = unitConfig.precision ?? 2;
            const roundedValue = Math.round(convertedValue * Math.pow(10, precision)) /
                Math.pow(10, precision);
            this.field().value.set(roundedValue);
            this.currentUnit.set(newUnit);
        }
    }
    extractValue(el) {
        const raw = el.value;
        const parsed = parseFloat(raw);
        return isNaN(parsed) ? 0 : parsed;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormNumberFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormNumberFieldComponent, isStandalone: true, selector: "signal-form-number-field", usesInheritance: true, ngImport: i0, template: "<div class=\"form-input-wrapper\">\n  <!-- Unit dropdown (prefix position) -->\n  @if (hasUnitConversion() && unitPosition() === \"prefix\") {\n    <select\n      class=\"form-select form-select-unit form-select-prefix\"\n      [value]=\"currentUnit()\"\n      (change)=\"onUnitChange($any($event.target).value)\"\n    >\n      @for (unit of availableUnits(); track unit.key) {\n        <option [value]=\"unit.key\">{{ unit.label }}</option>\n      }\n    </select>\n  }\n\n  <!-- Number input with conditional formatting -->\n  @if (showFormattedValue()) {\n    <!-- Show formatted value when not focused -->\n    <div\n      class=\"form-input form-input-formatted\"\n      tabindex=\"0\"\n      (focus)=\"onFocusFormattedDisplay()\"\n      (click)=\"onFocusFormattedDisplay()\"\n    >\n      {{ formattedValue() }}\n    </div>\n  } @else {\n    <!-- Show raw input when focused or no formatting needed -->\n    <input\n      type=\"number\"\n      class=\"form-input\"\n      [signalModel]=\"field()\"\n      [step]=\"inputStep()\"\n      [min]=\"inputMin()\"\n      [max]=\"inputMax()\"\n      (blur)=\"onBlurRawInput()\"\n    />\n  }\n\n  <!-- Unit dropdown (suffix position) -->\n  @if (hasUnitConversion() && unitPosition() === \"suffix\") {\n    <select\n      class=\"form-select form-select-unit form-select-suffix\"\n      [value]=\"currentUnit()\"\n      (change)=\"onUnitChange($any($event.target).value)\"\n    >\n      @for (unit of availableUnits(); track unit.key) {\n        <option [value]=\"unit.key\">{{ unit.label }}</option>\n      }\n    </select>\n  }\n</div>\n", styles: [".unit-display{padding:0 .75rem;font-size:var(--signal-form-font-size-sm);color:var(--signal-form-muted);background-color:var(--signal-forms-neutral-100);border-left:1px solid var(--signal-form-border-color);display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }, { kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormNumberFieldComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form-number-field', standalone: true, imports: [SignalModelDirective, CommonModule], template: "<div class=\"form-input-wrapper\">\n  <!-- Unit dropdown (prefix position) -->\n  @if (hasUnitConversion() && unitPosition() === \"prefix\") {\n    <select\n      class=\"form-select form-select-unit form-select-prefix\"\n      [value]=\"currentUnit()\"\n      (change)=\"onUnitChange($any($event.target).value)\"\n    >\n      @for (unit of availableUnits(); track unit.key) {\n        <option [value]=\"unit.key\">{{ unit.label }}</option>\n      }\n    </select>\n  }\n\n  <!-- Number input with conditional formatting -->\n  @if (showFormattedValue()) {\n    <!-- Show formatted value when not focused -->\n    <div\n      class=\"form-input form-input-formatted\"\n      tabindex=\"0\"\n      (focus)=\"onFocusFormattedDisplay()\"\n      (click)=\"onFocusFormattedDisplay()\"\n    >\n      {{ formattedValue() }}\n    </div>\n  } @else {\n    <!-- Show raw input when focused or no formatting needed -->\n    <input\n      type=\"number\"\n      class=\"form-input\"\n      [signalModel]=\"field()\"\n      [step]=\"inputStep()\"\n      [min]=\"inputMin()\"\n      [max]=\"inputMax()\"\n      (blur)=\"onBlurRawInput()\"\n    />\n  }\n\n  <!-- Unit dropdown (suffix position) -->\n  @if (hasUnitConversion() && unitPosition() === \"suffix\") {\n    <select\n      class=\"form-select form-select-unit form-select-suffix\"\n      [value]=\"currentUnit()\"\n      (change)=\"onUnitChange($any($event.target).value)\"\n    >\n      @for (unit of availableUnits(); track unit.key) {\n        <option [value]=\"unit.key\">{{ unit.label }}</option>\n      }\n    </select>\n  }\n</div>\n", styles: [".unit-display{padding:0 .75rem;font-size:var(--signal-form-font-size-sm);color:var(--signal-form-muted);background-color:var(--signal-forms-neutral-100);border-left:1px solid var(--signal-form-border-color);display:flex;align-items:center}\n"] }]
        }] });

class SignalFormPasswordFieldComponent extends BaseInputDirective {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormPasswordFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SignalFormPasswordFieldComponent, isStandalone: true, selector: "signal-form-password-field", usesInheritance: true, ngImport: i0, template: "<div class=\"form-input-wrapper\">\n  <input class=\"form-input\" type=\"password\" [signalModel]=\"field()\" />\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormPasswordFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-password-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"form-input-wrapper\">\n  <input class=\"form-input\" type=\"password\" [signalModel]=\"field()\" />\n</div>\n" }]
        }] });

class SignalFormRadioFieldComponent extends BaseInputDirective {
    /**
     * Convert option value to string for radio input value attribute
     */
    valueToString(value) {
        return String(value);
    }
    /**
     * Check if any options have icons to determine layout style
     */
    hasIcons() {
        return this.field()
            .options()
            .some((option) => option.icon);
    }
    /**
     * Check if an option is currently selected
     */
    isOptionSelected(optionValue) {
        return this.field().value() === optionValue;
    }
    /**
     * Check if icon is a Lucide icon data (prioritized check)
     */
    isLucideIcon(icon) {
        return Array.isArray(icon) && icon.length > 0;
    }
    /**
     * Check if icon is a string (emoji/unicode)
     */
    isStringIcon(icon) {
        return typeof icon === 'string';
    }
    /**
     * Check if icon is a component type (fallback)
     */
    isComponentIcon(icon) {
        return typeof icon === 'function' && !this.isLucideIcon(icon);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormRadioFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormRadioFieldComponent, isStandalone: true, selector: "signal-form-radio-field", usesInheritance: true, ngImport: i0, template: "<fieldset\n  class=\"form-radio-group\"\n  role=\"radiogroup\"\n  [attr.name]=\"field().name\"\n  [attr.aria-invalid]=\"field().error() ? 'true' : 'false'\"\n  [class.card-style]=\"hasIcons()\"\n>\n  <legend class=\"sr-only\">{{ field().label }}</legend>\n\n  @for (option of field().options(); track option.value) {\n    <label class=\"form-radio-option\" [class.card-option]=\"hasIcons()\">\n      <input\n        type=\"radio\"\n        [signalModel]=\"field()\"\n        [value]=\"valueToString(option.value)\"\n        [checked]=\"isOptionSelected(option.value)\"\n        [attr.name]=\"field().name\"\n        class=\"radio-input\"\n      />\n\n      @if (hasIcons()) {\n        <div class=\"card-content\">\n          <div class=\"radio-indicator\"></div>\n          @if (option.icon) {\n            <div class=\"option-icon\">\n              @if (isLucideIcon(option.icon)) {\n                <lucide-icon [img]=\"option.icon\" size=\"32\" />\n              } @else if (isStringIcon(option.icon)) {\n                {{ option.icon }}\n              } @else if (isComponentIcon(option.icon)) {\n                <ng-container *ngComponentOutlet=\"option.icon\"></ng-container>\n              }\n            </div>\n          }\n          <span class=\"option-label\">{{ option.label }}</span>\n        </div>\n      } @else {\n        {{ option.label }}\n      }\n    </label>\n  }\n</fieldset>\n", styles: ["@charset \"UTF-8\";.form-radio-group{display:flex;flex-direction:column;gap:.5rem}.form-radio-group .form-radio-option{display:flex;align-items:center;gap:.5rem;padding:.75rem;border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-input-bg);cursor:pointer;transition:var(--signal-form-transition);font-size:.875rem;color:var(--signal-form-input-text)}.form-radio-group .form-radio-option:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.form-radio-group .form-radio-option .radio-input{margin:0;width:1rem;height:1rem;accent-color:var(--signal-form-button-primary-bg)}.form-radio-group.card-style{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem}.form-radio-group.card-style .form-radio-option.card-option{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:1.5rem 1rem;min-height:120px;border:1px solid var(--signal-form-border-color);border-radius:12px;background:var(--signal-form-input-bg);cursor:pointer;transition:all .25s ease;box-shadow:var(--signal-form-shadow)}.form-radio-group.card-style .form-radio-option.card-option:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg);transform:translateY(-1px);box-shadow:var(--signal-form-shadow-lg)}.form-radio-group.card-style .form-radio-option.card-option .radio-input{position:absolute;opacity:0;width:1px;height:1px;top:0;left:0}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked){border-color:var(--signal-form-button-primary-bg);background:var(--signal-forms-primary-50)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content{color:var(--signal-form-button-primary-bg)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content .radio-indicator{background:var(--signal-form-button-primary-bg);border-color:var(--signal-form-button-primary-bg)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content .radio-indicator:after{opacity:1;transform:scale(1)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content .option-icon{transform:scale(1.05)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:focus){outline:2px solid var(--signal-form-outline-focus);outline-offset:2px}.form-radio-group.card-style .form-radio-option.card-option .card-content{display:flex;flex-direction:column;align-items:center;gap:.75rem;text-align:center;width:100%;transition:var(--signal-form-transition);position:relative}.form-radio-group.card-style .form-radio-option.card-option .card-content .radio-indicator{position:absolute;top:-.5rem;left:-.5rem;width:1.25rem;height:1.25rem;border:2px solid var(--signal-form-border-color);border-radius:50%;background:var(--signal-form-input-bg);display:flex;align-items:center;justify-content:center;transition:var(--signal-form-transition);box-shadow:var(--signal-form-shadow)}.form-radio-group.card-style .form-radio-option.card-option .card-content .radio-indicator:after{content:\"\\2713\";color:var(--signal-forms-neutral-0);font-size:.75rem;font-weight:700;opacity:0;transform:scale(.5);transition:all .2s ease}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-icon{font-size:2.5rem;line-height:1;opacity:.85;transition:all .3s ease;margin-bottom:.25rem}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-label{font-size:.875rem;font-weight:600;line-height:1.3;color:inherit;transition:var(--signal-form-transition)}@media (max-width: 768px){.form-radio-group.card-style{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem}.form-radio-group.card-style .form-radio-option.card-option{min-height:110px;padding:1.25rem .75rem}.form-radio-group.card-style .form-radio-option.card-option .card-content{gap:.5rem}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-icon{font-size:2rem}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-label{font-size:.8rem}}@media (max-width: 480px){.form-radio-group.card-style{grid-template-columns:1fr 1fr}.form-radio-group.card-style .form-radio-option.card-option{min-height:100px;padding:1rem .5rem}}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormRadioFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-radio-field', standalone: true, imports: [SignalModelDirective, NgComponentOutlet, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset\n  class=\"form-radio-group\"\n  role=\"radiogroup\"\n  [attr.name]=\"field().name\"\n  [attr.aria-invalid]=\"field().error() ? 'true' : 'false'\"\n  [class.card-style]=\"hasIcons()\"\n>\n  <legend class=\"sr-only\">{{ field().label }}</legend>\n\n  @for (option of field().options(); track option.value) {\n    <label class=\"form-radio-option\" [class.card-option]=\"hasIcons()\">\n      <input\n        type=\"radio\"\n        [signalModel]=\"field()\"\n        [value]=\"valueToString(option.value)\"\n        [checked]=\"isOptionSelected(option.value)\"\n        [attr.name]=\"field().name\"\n        class=\"radio-input\"\n      />\n\n      @if (hasIcons()) {\n        <div class=\"card-content\">\n          <div class=\"radio-indicator\"></div>\n          @if (option.icon) {\n            <div class=\"option-icon\">\n              @if (isLucideIcon(option.icon)) {\n                <lucide-icon [img]=\"option.icon\" size=\"32\" />\n              } @else if (isStringIcon(option.icon)) {\n                {{ option.icon }}\n              } @else if (isComponentIcon(option.icon)) {\n                <ng-container *ngComponentOutlet=\"option.icon\"></ng-container>\n              }\n            </div>\n          }\n          <span class=\"option-label\">{{ option.label }}</span>\n        </div>\n      } @else {\n        {{ option.label }}\n      }\n    </label>\n  }\n</fieldset>\n", styles: ["@charset \"UTF-8\";.form-radio-group{display:flex;flex-direction:column;gap:.5rem}.form-radio-group .form-radio-option{display:flex;align-items:center;gap:.5rem;padding:.75rem;border:1px solid var(--signal-form-border-color);border-radius:var(--signal-form-border-radius-sm);background:var(--signal-form-input-bg);cursor:pointer;transition:var(--signal-form-transition);font-size:.875rem;color:var(--signal-form-input-text)}.form-radio-group .form-radio-option:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg)}.form-radio-group .form-radio-option .radio-input{margin:0;width:1rem;height:1rem;accent-color:var(--signal-form-button-primary-bg)}.form-radio-group.card-style{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem}.form-radio-group.card-style .form-radio-option.card-option{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:1.5rem 1rem;min-height:120px;border:1px solid var(--signal-form-border-color);border-radius:12px;background:var(--signal-form-input-bg);cursor:pointer;transition:all .25s ease;box-shadow:var(--signal-form-shadow)}.form-radio-group.card-style .form-radio-option.card-option:hover{border-color:var(--signal-form-outline-focus);background:var(--signal-form-input-hover-bg);transform:translateY(-1px);box-shadow:var(--signal-form-shadow-lg)}.form-radio-group.card-style .form-radio-option.card-option .radio-input{position:absolute;opacity:0;width:1px;height:1px;top:0;left:0}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked){border-color:var(--signal-form-button-primary-bg);background:var(--signal-forms-primary-50)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content{color:var(--signal-form-button-primary-bg)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content .radio-indicator{background:var(--signal-form-button-primary-bg);border-color:var(--signal-form-button-primary-bg)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content .radio-indicator:after{opacity:1;transform:scale(1)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:checked) .card-content .option-icon{transform:scale(1.05)}.form-radio-group.card-style .form-radio-option.card-option:has(.radio-input:focus){outline:2px solid var(--signal-form-outline-focus);outline-offset:2px}.form-radio-group.card-style .form-radio-option.card-option .card-content{display:flex;flex-direction:column;align-items:center;gap:.75rem;text-align:center;width:100%;transition:var(--signal-form-transition);position:relative}.form-radio-group.card-style .form-radio-option.card-option .card-content .radio-indicator{position:absolute;top:-.5rem;left:-.5rem;width:1.25rem;height:1.25rem;border:2px solid var(--signal-form-border-color);border-radius:50%;background:var(--signal-form-input-bg);display:flex;align-items:center;justify-content:center;transition:var(--signal-form-transition);box-shadow:var(--signal-form-shadow)}.form-radio-group.card-style .form-radio-option.card-option .card-content .radio-indicator:after{content:\"\\2713\";color:var(--signal-forms-neutral-0);font-size:.75rem;font-weight:700;opacity:0;transform:scale(.5);transition:all .2s ease}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-icon{font-size:2.5rem;line-height:1;opacity:.85;transition:all .3s ease;margin-bottom:.25rem}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-label{font-size:.875rem;font-weight:600;line-height:1.3;color:inherit;transition:var(--signal-form-transition)}@media (max-width: 768px){.form-radio-group.card-style{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem}.form-radio-group.card-style .form-radio-option.card-option{min-height:110px;padding:1.25rem .75rem}.form-radio-group.card-style .form-radio-option.card-option .card-content{gap:.5rem}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-icon{font-size:2rem}.form-radio-group.card-style .form-radio-option.card-option .card-content .option-label{font-size:.8rem}}@media (max-width: 480px){.form-radio-group.card-style{grid-template-columns:1fr 1fr}.form-radio-group.card-style .form-radio-option.card-option{min-height:100px;padding:1rem .5rem}}\n"] }]
        }] });

class SignalFormRatingFieldComponent extends BaseInputDirective {
    minValue = computed(() => this.field().config?.min ?? 1);
    maxValue = computed(() => this.field().config?.max ?? 5);
    stars = computed(() => {
        const min = this.minValue();
        const max = this.maxValue();
        return Array.from({ length: max - min + 1 }, (_, i) => min + i);
    });
    setRating(star) {
        this.setValue(star);
        this.field().touched.set(true);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormRatingFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormRatingFieldComponent, isStandalone: true, selector: "signal-form-rating-field", usesInheritance: true, ngImport: i0, template: "<div class=\"form-rating\" role=\"radiogroup\" [signalModel]=\"field()\">\n  <div class=\"rating-stars\">\n    @for (star of stars(); track star) {\n      <button\n        class=\"star\"\n        type=\"button\"\n        role=\"radio\"\n        [class.filled]=\"field().value() >= star\"\n        [attr.aria-label]=\"'Rate ' + star + ' star'\"\n        [attr.aria-checked]=\"field().value() === star\"\n        (click)=\"setRating(star)\"\n      >\n        \u2605\n      </button>\n    }\n  </div>\n</div>\n", styles: [".rating-stars{display:flex;align-items:center;justify-content:flex-start;gap:8px}.rating-stars .star{color:var(--signal-forms-neutral-300);appearance:none;border:none;outline:none;padding:0!important;width:fit-content;background:transparent;transition:var(--signal-form-transition)}.rating-stars .star.filled{color:var(--signal-forms-warning-500)}.rating-stars .star:hover{color:var(--signal-forms-warning-400)}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormRatingFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-rating-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"form-rating\" role=\"radiogroup\" [signalModel]=\"field()\">\n  <div class=\"rating-stars\">\n    @for (star of stars(); track star) {\n      <button\n        class=\"star\"\n        type=\"button\"\n        role=\"radio\"\n        [class.filled]=\"field().value() >= star\"\n        [attr.aria-label]=\"'Rate ' + star + ' star'\"\n        [attr.aria-checked]=\"field().value() === star\"\n        (click)=\"setRating(star)\"\n      >\n        \u2605\n      </button>\n    }\n  </div>\n</div>\n", styles: [".rating-stars{display:flex;align-items:center;justify-content:flex-start;gap:8px}.rating-stars .star{color:var(--signal-forms-neutral-300);appearance:none;border:none;outline:none;padding:0!important;width:fit-content;background:transparent;transition:var(--signal-form-transition)}.rating-stars .star.filled{color:var(--signal-forms-warning-500)}.rating-stars .star:hover{color:var(--signal-forms-warning-400)}\n"] }]
        }] });

/**
 * Base skeleton component for form fields
 */
class FormFieldSkeletonComponent {
    /**
     * The field name for accessibility
     */
    name = input('field');
    /**
     * The type of skeleton to render
     */
    skeletonType = input('input');
    /**
     * Whether this is a checkbox field (affects label positioning)
     */
    isCheckbox = input(false);
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormFieldSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: FormFieldSkeletonComponent, isStandalone: true, selector: "form-field-skeleton", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, skeletonType: { classPropertyName: "skeletonType", publicName: "skeletonType", isSignal: true, isRequired: false, transformFunction: null }, isCheckbox: { classPropertyName: "isCheckbox", publicName: "isCheckbox", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"form-control\" [attr.data-form-field]=\"name()\">\n  @if (!isCheckbox()) {\n    <div class=\"skeleton-label\"></div>\n  }\n\n  @switch (skeletonType()) {\n    @case (\"input\") {\n      <div class=\"skeleton-input\"></div>\n    }\n    @case (\"textarea\") {\n      <div class=\"skeleton-textarea\"></div>\n    }\n    @case (\"select\") {\n      <div class=\"skeleton-select\">\n        <div class=\"skeleton-select-arrow\"></div>\n      </div>\n    }\n    @case (\"checkbox\") {\n      <div class=\"skeleton-checkbox-wrapper\">\n        <div class=\"skeleton-checkbox\"></div>\n        <div class=\"skeleton-checkbox-label\"></div>\n      </div>\n    }\n    @case (\"radio-group\") {\n      <div class=\"skeleton-radio-group\">\n        @for (i of [1, 2, 3]; track i) {\n          <div class=\"skeleton-radio-option\">\n            <div class=\"skeleton-radio\"></div>\n            <div class=\"skeleton-radio-label\"></div>\n          </div>\n        }\n      </div>\n    }\n    @case (\"multiselect\") {\n      <div class=\"skeleton-multiselect\">\n        <div class=\"skeleton-chip-container\">\n          @for (i of [1, 2]; track i) {\n            <div class=\"skeleton-chip\"></div>\n          }\n        </div>\n      </div>\n    }\n    @case (\"rating\") {\n      <div class=\"skeleton-rating\">\n        @for (i of [1, 2, 3, 4, 5]; track i) {\n          <div class=\"skeleton-star\"></div>\n        }\n      </div>\n    }\n    @case (\"slider\") {\n      <div class=\"skeleton-slider\">\n        <div class=\"skeleton-slider-track\">\n          <div class=\"skeleton-slider-thumb\"></div>\n        </div>\n      </div>\n    }\n    @case (\"file\") {\n      <div class=\"skeleton-file-upload\">\n        <div class=\"skeleton-upload-icon\"></div>\n        <div class=\"skeleton-upload-text\"></div>\n      </div>\n    }\n    @default {\n      <div class=\"skeleton-input\"></div>\n    }\n  }\n\n  <div class=\"skeleton-hint\"></div>\n</div>\n", styles: ["@keyframes shimmer{0%{background-position:-468px 0}to{background-position:468px 0}}.skeleton-hint,.skeleton-upload-text,.skeleton-upload-icon,.skeleton-file-upload,.skeleton-slider-thumb,.skeleton-slider-track,.skeleton-star,.skeleton-chip,.skeleton-multiselect,.skeleton-radio-label,.skeleton-radio,.skeleton-checkbox-label,.skeleton-checkbox,.skeleton-textarea,.skeleton-select-arrow,.skeleton-input,.skeleton-select,.skeleton-label{background:linear-gradient(90deg,var(--signal-forms-neutral-200) 25%,var(--signal-forms-neutral-300) 50%,var(--signal-forms-neutral-200) 75%);background-size:400% 100%;animation:shimmer 1.2s ease-in-out infinite;border-radius:4px}.form-control{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.skeleton-label{height:16px;width:120px;margin-bottom:4px}.skeleton-input,.skeleton-select{height:40px;width:100%;position:relative}.skeleton-select{display:flex;align-items:center;padding-right:40px}.skeleton-select-arrow{position:absolute;right:12px;width:16px;height:16px}.skeleton-textarea{height:80px;width:100%}.skeleton-checkbox-wrapper{display:flex;align-items:center;gap:8px}.skeleton-checkbox{width:20px;height:20px;flex-shrink:0}.skeleton-checkbox-label{height:16px;width:100px}.skeleton-radio-group{display:flex;flex-direction:column;gap:12px}.skeleton-radio-option{display:flex;align-items:center;gap:8px}.skeleton-radio{width:20px;height:20px;border-radius:50%;flex-shrink:0}.skeleton-radio-label{height:16px;width:80px}.skeleton-multiselect{min-height:40px;width:100%;padding:8px}.skeleton-chip-container{display:flex;gap:8px;flex-wrap:wrap}.skeleton-chip{height:24px;width:60px;border-radius:12px}.skeleton-rating{display:flex;gap:4px;align-items:center}.skeleton-star{width:24px;height:24px}.skeleton-slider{padding:12px 0}.skeleton-slider-track{height:8px;width:100%;position:relative}.skeleton-slider-thumb{position:absolute;top:-6px;left:30%;width:20px;height:20px;border-radius:50%}.skeleton-file-upload{height:120px;width:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;border:2px dashed var(--signal-forms-neutral-300);background:var(--signal-forms-neutral-100)}.skeleton-upload-icon{width:40px;height:40px;border-radius:50%}.skeleton-upload-text{height:16px;width:160px}.skeleton-hint{height:14px;width:200px;opacity:.7}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormFieldSkeletonComponent, decorators: [{
            type: Component,
            args: [{ selector: 'form-field-skeleton', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"form-control\" [attr.data-form-field]=\"name()\">\n  @if (!isCheckbox()) {\n    <div class=\"skeleton-label\"></div>\n  }\n\n  @switch (skeletonType()) {\n    @case (\"input\") {\n      <div class=\"skeleton-input\"></div>\n    }\n    @case (\"textarea\") {\n      <div class=\"skeleton-textarea\"></div>\n    }\n    @case (\"select\") {\n      <div class=\"skeleton-select\">\n        <div class=\"skeleton-select-arrow\"></div>\n      </div>\n    }\n    @case (\"checkbox\") {\n      <div class=\"skeleton-checkbox-wrapper\">\n        <div class=\"skeleton-checkbox\"></div>\n        <div class=\"skeleton-checkbox-label\"></div>\n      </div>\n    }\n    @case (\"radio-group\") {\n      <div class=\"skeleton-radio-group\">\n        @for (i of [1, 2, 3]; track i) {\n          <div class=\"skeleton-radio-option\">\n            <div class=\"skeleton-radio\"></div>\n            <div class=\"skeleton-radio-label\"></div>\n          </div>\n        }\n      </div>\n    }\n    @case (\"multiselect\") {\n      <div class=\"skeleton-multiselect\">\n        <div class=\"skeleton-chip-container\">\n          @for (i of [1, 2]; track i) {\n            <div class=\"skeleton-chip\"></div>\n          }\n        </div>\n      </div>\n    }\n    @case (\"rating\") {\n      <div class=\"skeleton-rating\">\n        @for (i of [1, 2, 3, 4, 5]; track i) {\n          <div class=\"skeleton-star\"></div>\n        }\n      </div>\n    }\n    @case (\"slider\") {\n      <div class=\"skeleton-slider\">\n        <div class=\"skeleton-slider-track\">\n          <div class=\"skeleton-slider-thumb\"></div>\n        </div>\n      </div>\n    }\n    @case (\"file\") {\n      <div class=\"skeleton-file-upload\">\n        <div class=\"skeleton-upload-icon\"></div>\n        <div class=\"skeleton-upload-text\"></div>\n      </div>\n    }\n    @default {\n      <div class=\"skeleton-input\"></div>\n    }\n  }\n\n  <div class=\"skeleton-hint\"></div>\n</div>\n", styles: ["@keyframes shimmer{0%{background-position:-468px 0}to{background-position:468px 0}}.skeleton-hint,.skeleton-upload-text,.skeleton-upload-icon,.skeleton-file-upload,.skeleton-slider-thumb,.skeleton-slider-track,.skeleton-star,.skeleton-chip,.skeleton-multiselect,.skeleton-radio-label,.skeleton-radio,.skeleton-checkbox-label,.skeleton-checkbox,.skeleton-textarea,.skeleton-select-arrow,.skeleton-input,.skeleton-select,.skeleton-label{background:linear-gradient(90deg,var(--signal-forms-neutral-200) 25%,var(--signal-forms-neutral-300) 50%,var(--signal-forms-neutral-200) 75%);background-size:400% 100%;animation:shimmer 1.2s ease-in-out infinite;border-radius:4px}.form-control{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.skeleton-label{height:16px;width:120px;margin-bottom:4px}.skeleton-input,.skeleton-select{height:40px;width:100%;position:relative}.skeleton-select{display:flex;align-items:center;padding-right:40px}.skeleton-select-arrow{position:absolute;right:12px;width:16px;height:16px}.skeleton-textarea{height:80px;width:100%}.skeleton-checkbox-wrapper{display:flex;align-items:center;gap:8px}.skeleton-checkbox{width:20px;height:20px;flex-shrink:0}.skeleton-checkbox-label{height:16px;width:100px}.skeleton-radio-group{display:flex;flex-direction:column;gap:12px}.skeleton-radio-option{display:flex;align-items:center;gap:8px}.skeleton-radio{width:20px;height:20px;border-radius:50%;flex-shrink:0}.skeleton-radio-label{height:16px;width:80px}.skeleton-multiselect{min-height:40px;width:100%;padding:8px}.skeleton-chip-container{display:flex;gap:8px;flex-wrap:wrap}.skeleton-chip{height:24px;width:60px;border-radius:12px}.skeleton-rating{display:flex;gap:4px;align-items:center}.skeleton-star{width:24px;height:24px}.skeleton-slider{padding:12px 0}.skeleton-slider-track{height:8px;width:100%;position:relative}.skeleton-slider-thumb{position:absolute;top:-6px;left:30%;width:20px;height:20px;border-radius:50%}.skeleton-file-upload{height:120px;width:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;border:2px dashed var(--signal-forms-neutral-300);background:var(--signal-forms-neutral-100)}.skeleton-upload-icon{width:40px;height:40px;border-radius:50%}.skeleton-upload-text{height:16px;width:160px}.skeleton-hint{height:14px;width:200px;opacity:.7}\n"] }]
        }] });

/**
 * Maps form field types to appropriate skeleton UI
 */
class FormFieldSkeletonMapperComponent {
    /**
     * The form field type to create skeleton for
     */
    fieldType = input.required();
    /**
     * The field name for accessibility
     */
    fieldName = input('field');
    /**
     * Computed skeleton type based on field type
     */
    skeletonType = computed(() => {
        const type = this.fieldType();
        switch (type) {
            case FormFieldType.TEXT:
            case FormFieldType.PASSWORD:
            case FormFieldType.NUMBER:
            case FormFieldType.COLOR:
            case FormFieldType.DATETIME:
                return 'input';
            case FormFieldType.TEXTAREA:
                return 'textarea';
            case FormFieldType.SELECT:
            case FormFieldType.AUTOCOMPLETE:
                return 'select';
            case FormFieldType.CHECKBOX:
                return 'checkbox';
            case FormFieldType.CHECKBOX_GROUP:
            case FormFieldType.RADIO:
                return 'radio-group';
            case FormFieldType.MULTISELECT:
            case FormFieldType.CHIPLIST:
                return 'multiselect';
            case FormFieldType.RATING:
                return 'rating';
            case FormFieldType.SLIDER:
                return 'slider';
            case FormFieldType.FILE:
                return 'file';
            case FormFieldType.SWITCH:
                return 'checkbox';
            default:
                return 'input';
        }
    });
    /**
     * Whether this is a checkbox-style field
     */
    isCheckboxType = computed(() => {
        const type = this.fieldType();
        return type === FormFieldType.CHECKBOX || type === FormFieldType.SWITCH;
    });
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormFieldSkeletonMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: FormFieldSkeletonMapperComponent, isStandalone: true, selector: "form-field-skeleton-mapper", inputs: { fieldType: { classPropertyName: "fieldType", publicName: "fieldType", isSignal: true, isRequired: true, transformFunction: null }, fieldName: { classPropertyName: "fieldName", publicName: "fieldName", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
    <form-field-skeleton
      [name]="fieldName()"
      [skeletonType]="skeletonType()"
      [isCheckbox]="isCheckboxType()"
    />
  `, isInline: true, dependencies: [{ kind: "component", type: FormFieldSkeletonComponent, selector: "form-field-skeleton", inputs: ["name", "skeletonType", "isCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormFieldSkeletonMapperComponent, decorators: [{
            type: Component,
            args: [{
                    selector: 'form-field-skeleton-mapper',
                    standalone: true,
                    changeDetection: ChangeDetectionStrategy.OnPush,
                    imports: [FormFieldSkeletonComponent],
                    template: `
    <form-field-skeleton
      [name]="fieldName()"
      [skeletonType]="skeletonType()"
      [isCheckbox]="isCheckboxType()"
    />
  `,
                }]
        }] });

/**
 * Signal Form Input Item Component
 *
 * Renders individual form fields with support for custom styling, validation,
 * and various field types. This component handles the wrapper, label, input,
 * error messages, and hint text for each field.
 *
 * Features:
 * - Custom styling via modifierClass, stylesFn, and inlineStylesFn
 * - Dynamic validation state styling
 * - Part-specific styling (wrapper, label, input, error, hint)
 * - Reactive computed values
 * - Focus management and error highlighting
 * - ViewEncapsulation.None for external styling support
 *
 * @template TModel - The type of the form model this field belongs to
 */
class SignalFormInputItemComponent {
    renderer;
    host;
    injector;
    /** The field configuration and state for this input item */
    field = input.required();
    /** The parent form container that this field belongs to */
    form = input.required();
    /** Optional index for repeatable fields (used in field naming) */
    index = input();
    /** Form field type constants for template use */
    FormFieldType = FormFieldType;
    /** Tracks whether the component has been initialized with computed values */
    initialized = signal(false);
    /** Service for handling field validation */
    validationService = inject(ValidationService);
    /**
     * Computed name for the field, including index if applicable
     * Used for HTML id, for attributes, and accessibility
     */
    name = computed(() => {
        if (typeof this.index() === 'number') {
            return `${String(this.field().name)}-${this.index()}`;
        }
        return `${String(this.field().name)}`;
    });
    /**
     * Grid area binding for CSS Grid layouts
     * Uses the field name as the grid area identifier
     */
    get gridArea() {
        return this.field()?.name?.toString() ?? null;
    }
    /**
     * Host class binding that applies wrapper-level styling
     * Combines static modifier classes and dynamic styling functions
     * as well as theme based classes too
     */
    get hostClasses() {
        return this.computeWrapperClasses();
    }
    /**
     * Host style binding that applies wrapper-level inline styles
     * Uses the inlineStylesFn to generate dynamic styles
     */
    get hostStyles() {
        return this.computeWrapperStyles();
    }
    /**
     * Computes CSS classes for the field wrapper element
     * Handles static modifierClass and dynamic stylesFn results
     *
     * @returns Space-separated string of CSS classes
     * @private
     */
    computeWrapperClasses() {
        const classes = [];
        const field = this.field();
        const styling = field.styling;
        if (!styling) {
            return '';
        }
        if (styling.modifierClass) {
            classes.push(...styling.modifierClass);
        }
        if (!styling.stylesFn) {
            return classes.join(' ');
        }
        const result = styling.stylesFn(field, this.form());
        if (typeof result === 'string') {
            classes.push(result);
            return classes.join(' ');
        }
        if (Array.isArray(result)) {
            classes.push(...result);
            return classes.join(' ');
        }
        if (result && typeof result === 'object') {
            const wrapperClasses = result.wrapper;
            if (typeof wrapperClasses === 'string') {
                classes.push(wrapperClasses);
            }
            else if (Array.isArray(wrapperClasses)) {
                classes.push(...wrapperClasses);
            }
        }
        return classes.join(' ');
    }
    /**
     * Computes inline styles for the field wrapper element
     *
     * @returns Object containing CSS property-value pairs
     * @private
     */
    computeWrapperStyles() {
        const field = this.field();
        const styling = field.styling;
        if (!styling?.inlineStylesFn) {
            return {};
        }
        const result = styling.inlineStylesFn(field, this.form());
        return result.wrapper || {};
    }
    /**
     * Extracts CSS classes for specific field parts (label, input, error, hint)
     *
     * @param part - The field part to get classes for
     * @returns Space-separated string of CSS classes for the specified part
     * @private
     */
    getPartClasses(part) {
        const styling = this.field().styling;
        if (!styling?.stylesFn) {
            return '';
        }
        const result = styling.stylesFn(this.field(), this.form());
        if (!result || typeof result !== 'object' || Array.isArray(result)) {
            return '';
        }
        const partClasses = result[part];
        if (typeof partClasses === 'string') {
            return partClasses;
        }
        if (Array.isArray(partClasses)) {
            return partClasses.join(' ');
        }
        return '';
    }
    /**
     * Extracts inline styles for specific field parts (label, input, error, hint)
     *
     * @param part - The field part to get styles for
     * @returns Object containing CSS property-value pairs for the specified part
     * @private
     */
    getPartStyles(part) {
        const styling = this.field().styling;
        if (!styling?.inlineStylesFn) {
            return {};
        }
        const result = styling.inlineStylesFn(this.field(), this.form());
        return result[part] || {};
    }
    /** Computed CSS classes for the field label */
    labelClasses = computed(() => this.getPartClasses('label'));
    /** Computed CSS classes for the field input wrapper */
    inputClasses = computed(() => this.getPartClasses('input'));
    /** Computed CSS classes for the field error message */
    errorClasses = computed(() => this.getPartClasses('error'));
    /** Computed CSS classes for the field hint text */
    hintClasses = computed(() => this.getPartClasses('hint'));
    /** Computed inline styles for the field label */
    labelStyles = computed(() => this.getPartStyles('label'));
    /** Computed inline styles for the field input wrapper */
    inputStyles = computed(() => this.getPartStyles('input'));
    /** Computed inline styles for the field error message */
    errorStyles = computed(() => this.getPartStyles('error'));
    /** Computed inline styles for the field hint text */
    hintStyles = computed(() => this.getPartStyles('hint'));
    /**
     * Component constructor
     * Initializes all reactive effects for computed values, validation, and focus management
     *
     * @param renderer - Angular Renderer2 for DOM manipulation
     * @param host - Reference to the component's host element
     * @param injector - Angular injector for effect contexts
     */
    constructor(renderer, host, injector) {
        this.renderer = renderer;
        this.host = host;
        this.injector = injector;
        this.initializeComputedValueEffect();
        this.initializeFormOptionsEffect();
        this.watchComputedValueEffect();
        this.setupValidation();
        this.focusEffect();
    }
    /**
     * Determines if the field is required based on its validators
     *
     * @returns True if the field has required validators
     * @protected
     */
    isRequired() {
        return isRequired({ validators: this.field().validators });
    }
    /**
     * Computed property that returns the combined error message for the field
     * Combines synchronous and asynchronous validation errors
     */
    hasError = computed(() => {
        const field = this.field();
        return this.validationService.getCombinedError(field);
    });
    /**
     * Computed property that determines if the field is currently validating
     * Checks for async validation in progress
     */
    isValidating = computed(() => {
        const field = this.field();
        // Check if field has validating property (not all field types have async validation)
        if ('validating' in field &&
            typeof field.validating === 'function') {
            return field.validating();
        }
        // For fields without async validation, they're never validating
        return false;
    });
    /**
     * Placeholder for form options initialization
     * Reserved for future functionality
     *
     * @private
     */
    initializeFormOptionsEffect() { }
    /**
     * Effect that initializes computed field values on component setup
     * Runs once when the component first loads if the field has a computedValue function
     *
     * @private
     */
    initializeComputedValueEffect() {
        effect(() => {
            const field = this.field();
            if (!field.computedValue || this.initialized())
                return;
            const initialValue = field.computedValue(this.form());
            this.setValue(initialValue, false);
            this.initialized.set(true);
        }, { injector: this.injector });
    }
    /**
     * Effect that watches for changes to computed field values
     * Updates the field value when the computed value function result changes
     *
     * @private
     */
    watchComputedValueEffect() {
        effect(() => {
            const field = this.field();
            if (!field.computedValue || !this.initialized())
                return;
            const newValue = field.computedValue(this.form());
            this.setValue(newValue, false);
        }, { injector: this.injector });
    }
    /**
     * Effect that sets up validation for the field
     * Configures validation triggers, debouncing, and async validation
     *
     * @private
     */
    setupValidation() {
        effect(() => {
            const field = this.field();
            const form = this.form();
            // Set up validation for this field
            this.validationService.setupFieldValidation(field, form);
        }, { injector: this.injector });
    }
    /**
     * Effect that handles field focus behavior
     * Scrolls to and highlights fields when focus is requested,
     * typically when navigating to validation errors
     *
     * @private
     */
    focusEffect() {
        effect(() => {
            const field = this.field();
            if (!field.focus()) {
                return;
            }
            const el = this.host.nativeElement;
            el.scrollIntoView({ behavior: 'smooth', block: 'center' });
            const input = el.querySelector('input');
            input?.focus?.();
            this.renderer.addClass(el, 'form-error-highlight');
            setTimeout(() => {
                this.renderer.removeClass(el, 'form-error-highlight');
                this.field().focus.set(false);
            }, 1000);
        }, { injector: this.injector });
    }
    /**
     * Computed property that determines if the field should be hidden
     * Supports both boolean and function-based hidden conditions
     */
    isHidden = computed(() => {
        const { hidden } = this.field();
        return typeof hidden === 'function' ? hidden(this.form()) : !!hidden;
    });
    /**
     * Computed property that determines if the field should be disabled
     * Supports both boolean and function-based disabled conditions
     */
    isDisabled = computed(() => {
        const { disabled } = this.field();
        return typeof disabled === 'function' ? disabled(this.form()) : !!disabled;
    });
    /**
     * Sets the field value with proper type handling
     * Handles different value types (string, number, boolean, Date, object)
     * and updates the field's touched and dirty states
     *
     * @param value - The value to set on the field
     * @param markTouched - Whether to mark the field as touched and dirty (default: true)
     * @private
     */
    setValue(value, markTouched = true) {
        if (markTouched) {
            this.field().touched.set(true);
            this.field().dirty.set(true);
        }
        if (typeof value === 'number') {
            this.field().value.set(value);
            return;
        }
        if (typeof value === 'boolean') {
            this.field().value.set(value);
            return;
        }
        if (value instanceof Date) {
            this.field().value.set(value);
            return;
        }
        if (typeof value === 'object' && !!value) {
            this.field().value.set(value);
            return;
        }
        const val = value;
        this.field().value.set(val);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormInputItemComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormInputItemComponent, isStandalone: true, selector: "signal-form-input-item", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.gridArea": "this.gridArea", "class": "this.hostClasses", "style": "this.hostStyles" } }, hostDirectives: [{ directive: SignalFormHostDirective }], ngImport: i0, template: "@if (!isHidden()) {\n  <div\n    class=\"form-control\"\n    [class.has-error]=\"hasError()\"\n    [class.is-validating]=\"isValidating()\"\n    [class.is-success]=\"!hasError() && !isValidating() && field().value()\"\n    [attr.data-form-field]=\"name()\"\n    #formControl\n  >\n    @defer (on viewport(formControl)) {\n      @if (field().type !== FormFieldType.CHECKBOX) {\n        <label\n          class=\"form-label\"\n          [class]=\"labelClasses()\"\n          [ngStyle]=\"labelStyles()\"\n          [for]=\"name()\"\n        >\n          {{ field().label }}\n          @if (isRequired()) {\n            <span class=\"required-asterisk\">*</span>\n          }\n          @if (isValidating()) {\n            <span class=\"validation-spinner\">\u27F3</span>\n          }\n        </label>\n      }\n\n      <div\n        class=\"form-field-wrapper\"\n        [class]=\"inputClasses()\"\n        [ngStyle]=\"inputStyles()\"\n      >\n        @switch (field().type) {\n          @case (FormFieldType.TEXT) {\n            <signal-form-text-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.PASSWORD) {\n            <signal-form-password-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.CHECKBOX) {\n            <signal-form-checkbox-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.CHECKBOX_GROUP) {\n            <signal-form-checkbox-group-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.NUMBER) {\n            <signal-form-number-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.AUTOCOMPLETE) {\n            <signal-form-autocomplete-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.TEXTAREA) {\n            <signal-form-textarea-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.SELECT) {\n            <signal-form-select-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.RADIO) {\n            <signal-form-radio-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.DATETIME) {\n            <signal-form-datetime-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.MULTISELECT) {\n            <signal-form-multiselect-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.CHIPLIST) {\n            <signal-form-chip-list-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.FILE) {\n            <signal-form-file-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.COLOR) {\n            <signal-form-color-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.RATING) {\n            <signal-form-rating-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.SLIDER) {\n            <signal-form-slider-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.SWITCH) {\n            <signal-form-switch-field [field]=\"field()\" />\n          }\n        }\n      </div>\n\n      <div class=\"form-caption\">\n        @if (hasError()) {\n          <span\n            class=\"form-error\"\n            [class]=\"errorClasses()\"\n            [ngStyle]=\"errorStyles()\"\n            role=\"alert\"\n            [id]=\"'error-' + name()\"\n          >\n            {{ hasError() }}\n          </span>\n        } @else if (isValidating()) {\n          <span\n            class=\"form-validating\"\n            role=\"status\"\n            [id]=\"'validating-' + name()\"\n          >\n            Validating...\n          </span>\n        } @else if (field().config?.hint) {\n          <span\n            class=\"form-hint\"\n            [class]=\"hintClasses()\"\n            [ngStyle]=\"hintStyles()\"\n            role=\"alert\"\n            [id]=\"'hint-' + name()\"\n          >\n            {{ field().config.hint }}\n          </span>\n        }\n      </div>\n    } @loading (minimum 100ms) {\n      <form-field-skeleton-mapper\n        [fieldType]=\"field().type\"\n        [fieldName]=\"name()\"\n      />\n    } @placeholder {\n      <form-field-skeleton-mapper\n        [fieldType]=\"field().type\"\n        [fieldName]=\"name()\"\n      />\n    }\n  </div>\n}\n", styles: ["signal-form-input-item .form-control.is-validating .form-label{opacity:var(--signal-form-validating-opacity)}signal-form-input-item .form-control.is-validating input,signal-form-input-item .form-control.is-validating textarea,signal-form-input-item .form-control.is-validating select{border-color:var(--signal-form-validating-color);background-color:var(--signal-form-validating-bg)}signal-form-input-item .form-control.premium-field{border:var(--signal-form-premium-border-width) solid var(--signal-form-premium-color);background:var(--signal-form-premium-bg);border-radius:var(--signal-form-premium-radius)}signal-form-input-item .form-control.compact-field .form-field-wrapper{gap:var(--signal-form-field-wrapper-gap-minimal)}signal-form-input-item .form-control.compact-field .form-label{font-size:var(--signal-form-label-subtle-size);margin-bottom:var(--signal-form-field-wrapper-gap-minimal)}signal-form-input-item .form-control.highlighted-field{box-shadow:var(--signal-form-highlight-shadow);border-color:var(--signal-form-highlight-border)}signal-form-input-item .form-field-wrapper{display:flex;align-items:stretch;width:100%}signal-form-input-item .form-field-wrapper.compact{gap:var(--signal-form-field-wrapper-gap-compact)}signal-form-input-item .form-field-wrapper.spaced{gap:var(--signal-form-field-wrapper-gap-spaced)}signal-form-input-item .form-field-wrapper.fullwidth{width:100%}signal-form-input-item .form-field-wrapper>*{flex:1}signal-form-input-item .form-field-wrapper.custom-input-wrapper{background:var(--input-wrapper-bg);border:var(--input-wrapper-border);border-radius:var(--input-wrapper-radius);padding:var(--input-wrapper-padding)}signal-form-input-item .form-field-wrapper:has(>form-dropdown-overlay){position:relative}signal-form-input-item .form-label.label-prominent{font-weight:var(--signal-form-label-prominent-weight);color:var(--signal-form-label-prominent-color);font-size:var(--signal-form-label-prominent-size)}signal-form-input-item .form-label.label-subtle{font-weight:var(--signal-form-label-subtle-weight);color:var(--signal-form-label-subtle-color);font-size:var(--signal-form-label-subtle-size)}signal-form-input-item .form-error.error-prominent{font-weight:var(--signal-form-error-prominent-weight);color:var(--signal-form-error-prominent-color);padding:var(--signal-form-error-prominent-padding);background:var(--signal-form-error-prominent-bg);border-radius:var(--signal-form-border-radius-sm)}signal-form-input-item .form-error.error-subtle{font-weight:var(--signal-form-error-subtle-weight);color:var(--signal-form-error-subtle-color);font-size:var(--signal-form-error-subtle-size)}signal-form-input-item .form-hint.hint-prominent{font-weight:var(--signal-form-hint-prominent-weight);color:var(--signal-form-hint-prominent-color)}signal-form-input-item .form-hint.hint-subtle{font-weight:var(--signal-form-hint-subtle-weight);color:var(--signal-form-hint-subtle-color);font-size:var(--signal-form-hint-subtle-size)}signal-form-input-item .validation-spinner{display:inline-block;margin-left:var(--signal-form-spinner-margin);animation:spin 1s linear infinite;color:var(--signal-form-validating-color)}signal-form-input-item .form-validating{color:var(--signal-form-validating-color);font-size:var(--signal-form-validating-font-size);font-style:italic}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FormFieldSkeletonMapperComponent, selector: "form-field-skeleton-mapper", inputs: ["fieldType", "fieldName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None, deferBlockDependencies: [() => [i2.NgStyle, Promise.resolve().then(function () { return index; }).then(m => m.SignalFormAutocompleteFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormTextFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormPasswordFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormTextareaFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormNumberFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormSelectFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormRadioFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormCheckboxFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormCheckboxGroupFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormDatetimeFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormColorFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormSwitchFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormSliderFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormFileFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormRatingFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormMultiselectFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormChipListFieldComponent)]] });
}
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "19.2.14", ngImport: i0, type: SignalFormInputItemComponent, resolveDeferredDeps: () => [Promise.resolve().then(function () { return index; }).then(m => m.SignalFormAutocompleteFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormTextFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormPasswordFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormTextareaFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormNumberFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormSelectFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormRadioFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormCheckboxFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormCheckboxGroupFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormDatetimeFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormColorFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormSwitchFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormSliderFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormFileFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormRatingFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormMultiselectFieldComponent), Promise.resolve().then(function () { return index; }).then(m => m.SignalFormChipListFieldComponent)], resolveMetadata: (SignalFormAutocompleteFieldComponent, SignalFormTextFieldComponent, SignalFormPasswordFieldComponent, SignalFormTextareaFieldComponent, SignalFormNumberFieldComponent, SignalFormSelectFieldComponent, SignalFormRadioFieldComponent, SignalFormCheckboxFieldComponent, SignalFormCheckboxGroupFieldComponent, SignalFormDatetimeFieldComponent, SignalFormColorFieldComponent, SignalFormSwitchFieldComponent, SignalFormSliderFieldComponent, SignalFormFileFieldComponent, SignalFormRatingFieldComponent, SignalFormMultiselectFieldComponent, SignalFormChipListFieldComponent) => ({ decorators: [{
                type: Component,
                args: [{ changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, selector: 'signal-form-input-item', imports: [
                            CommonModule,
                            SignalFormAutocompleteFieldComponent,
                            SignalFormTextFieldComponent,
                            SignalFormPasswordFieldComponent,
                            SignalFormTextareaFieldComponent,
                            SignalFormNumberFieldComponent,
                            SignalFormSelectFieldComponent,
                            SignalFormRadioFieldComponent,
                            SignalFormCheckboxFieldComponent,
                            SignalFormCheckboxGroupFieldComponent,
                            SignalFormDatetimeFieldComponent,
                            SignalFormColorFieldComponent,
                            SignalFormSwitchFieldComponent,
                            SignalFormSliderFieldComponent,
                            SignalFormFileFieldComponent,
                            SignalFormRatingFieldComponent,
                            SignalFormMultiselectFieldComponent,
                            SignalFormChipListFieldComponent,
                            FormFieldSkeletonMapperComponent,
                        ], hostDirectives: [SignalFormHostDirective], encapsulation: ViewEncapsulation.None, template: "@if (!isHidden()) {\n  <div\n    class=\"form-control\"\n    [class.has-error]=\"hasError()\"\n    [class.is-validating]=\"isValidating()\"\n    [class.is-success]=\"!hasError() && !isValidating() && field().value()\"\n    [attr.data-form-field]=\"name()\"\n    #formControl\n  >\n    @defer (on viewport(formControl)) {\n      @if (field().type !== FormFieldType.CHECKBOX) {\n        <label\n          class=\"form-label\"\n          [class]=\"labelClasses()\"\n          [ngStyle]=\"labelStyles()\"\n          [for]=\"name()\"\n        >\n          {{ field().label }}\n          @if (isRequired()) {\n            <span class=\"required-asterisk\">*</span>\n          }\n          @if (isValidating()) {\n            <span class=\"validation-spinner\">\u27F3</span>\n          }\n        </label>\n      }\n\n      <div\n        class=\"form-field-wrapper\"\n        [class]=\"inputClasses()\"\n        [ngStyle]=\"inputStyles()\"\n      >\n        @switch (field().type) {\n          @case (FormFieldType.TEXT) {\n            <signal-form-text-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.PASSWORD) {\n            <signal-form-password-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.CHECKBOX) {\n            <signal-form-checkbox-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.CHECKBOX_GROUP) {\n            <signal-form-checkbox-group-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.NUMBER) {\n            <signal-form-number-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.AUTOCOMPLETE) {\n            <signal-form-autocomplete-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.TEXTAREA) {\n            <signal-form-textarea-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.SELECT) {\n            <signal-form-select-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.RADIO) {\n            <signal-form-radio-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.DATETIME) {\n            <signal-form-datetime-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.MULTISELECT) {\n            <signal-form-multiselect-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.CHIPLIST) {\n            <signal-form-chip-list-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.FILE) {\n            <signal-form-file-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.COLOR) {\n            <signal-form-color-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.RATING) {\n            <signal-form-rating-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.SLIDER) {\n            <signal-form-slider-field [field]=\"field()\" />\n          }\n          @case (FormFieldType.SWITCH) {\n            <signal-form-switch-field [field]=\"field()\" />\n          }\n        }\n      </div>\n\n      <div class=\"form-caption\">\n        @if (hasError()) {\n          <span\n            class=\"form-error\"\n            [class]=\"errorClasses()\"\n            [ngStyle]=\"errorStyles()\"\n            role=\"alert\"\n            [id]=\"'error-' + name()\"\n          >\n            {{ hasError() }}\n          </span>\n        } @else if (isValidating()) {\n          <span\n            class=\"form-validating\"\n            role=\"status\"\n            [id]=\"'validating-' + name()\"\n          >\n            Validating...\n          </span>\n        } @else if (field().config?.hint) {\n          <span\n            class=\"form-hint\"\n            [class]=\"hintClasses()\"\n            [ngStyle]=\"hintStyles()\"\n            role=\"alert\"\n            [id]=\"'hint-' + name()\"\n          >\n            {{ field().config.hint }}\n          </span>\n        }\n      </div>\n    } @loading (minimum 100ms) {\n      <form-field-skeleton-mapper\n        [fieldType]=\"field().type\"\n        [fieldName]=\"name()\"\n      />\n    } @placeholder {\n      <form-field-skeleton-mapper\n        [fieldType]=\"field().type\"\n        [fieldName]=\"name()\"\n      />\n    }\n  </div>\n}\n", styles: ["signal-form-input-item .form-control.is-validating .form-label{opacity:var(--signal-form-validating-opacity)}signal-form-input-item .form-control.is-validating input,signal-form-input-item .form-control.is-validating textarea,signal-form-input-item .form-control.is-validating select{border-color:var(--signal-form-validating-color);background-color:var(--signal-form-validating-bg)}signal-form-input-item .form-control.premium-field{border:var(--signal-form-premium-border-width) solid var(--signal-form-premium-color);background:var(--signal-form-premium-bg);border-radius:var(--signal-form-premium-radius)}signal-form-input-item .form-control.compact-field .form-field-wrapper{gap:var(--signal-form-field-wrapper-gap-minimal)}signal-form-input-item .form-control.compact-field .form-label{font-size:var(--signal-form-label-subtle-size);margin-bottom:var(--signal-form-field-wrapper-gap-minimal)}signal-form-input-item .form-control.highlighted-field{box-shadow:var(--signal-form-highlight-shadow);border-color:var(--signal-form-highlight-border)}signal-form-input-item .form-field-wrapper{display:flex;align-items:stretch;width:100%}signal-form-input-item .form-field-wrapper.compact{gap:var(--signal-form-field-wrapper-gap-compact)}signal-form-input-item .form-field-wrapper.spaced{gap:var(--signal-form-field-wrapper-gap-spaced)}signal-form-input-item .form-field-wrapper.fullwidth{width:100%}signal-form-input-item .form-field-wrapper>*{flex:1}signal-form-input-item .form-field-wrapper.custom-input-wrapper{background:var(--input-wrapper-bg);border:var(--input-wrapper-border);border-radius:var(--input-wrapper-radius);padding:var(--input-wrapper-padding)}signal-form-input-item .form-field-wrapper:has(>form-dropdown-overlay){position:relative}signal-form-input-item .form-label.label-prominent{font-weight:var(--signal-form-label-prominent-weight);color:var(--signal-form-label-prominent-color);font-size:var(--signal-form-label-prominent-size)}signal-form-input-item .form-label.label-subtle{font-weight:var(--signal-form-label-subtle-weight);color:var(--signal-form-label-subtle-color);font-size:var(--signal-form-label-subtle-size)}signal-form-input-item .form-error.error-prominent{font-weight:var(--signal-form-error-prominent-weight);color:var(--signal-form-error-prominent-color);padding:var(--signal-form-error-prominent-padding);background:var(--signal-form-error-prominent-bg);border-radius:var(--signal-form-border-radius-sm)}signal-form-input-item .form-error.error-subtle{font-weight:var(--signal-form-error-subtle-weight);color:var(--signal-form-error-subtle-color);font-size:var(--signal-form-error-subtle-size)}signal-form-input-item .form-hint.hint-prominent{font-weight:var(--signal-form-hint-prominent-weight);color:var(--signal-form-hint-prominent-color)}signal-form-input-item .form-hint.hint-subtle{font-weight:var(--signal-form-hint-subtle-weight);color:var(--signal-form-hint-subtle-color);font-size:var(--signal-form-hint-subtle-size)}signal-form-input-item .validation-spinner{display:inline-block;margin-left:var(--signal-form-spinner-margin);animation:spin 1s linear infinite;color:var(--signal-form-validating-color)}signal-form-input-item .form-validating{color:var(--signal-form-validating-color);font-size:var(--signal-form-validating-font-size);font-style:italic}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
            }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.Injector }], propDecorators: { gridArea: [{
                    type: HostBinding,
                    args: ['style.gridArea']
                }], hostClasses: [{
                    type: HostBinding,
                    args: ['class']
                }], hostStyles: [{
                    type: HostBinding,
                    args: ['style']
                }] } }) });

class SignalFormRepeatableFieldComponent {
    repeatableForms = input.required();
    heading = input('');
    fields = input.required();
    addItem = output();
    removeItem = output();
    formsList = computed(() => this.repeatableForms());
    plus = PlusCircleIcon;
    minus = MinusCircleIcon;
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormRepeatableFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormRepeatableFieldComponent, isStandalone: true, selector: "signal-form-repeatable-field", inputs: { repeatableForms: { classPropertyName: "repeatableForms", publicName: "repeatableForms", isSignal: true, isRequired: true, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { addItem: "addItem", removeItem: "removeItem" }, ngImport: i0, template: "<section class=\"repeatable-form-group\">\n  <h3 class=\"form-group-heading\">{{ heading() }}</h3>\n\n  @for (groupForm of repeatableForms(); track $index; let i = $index) {\n    <section class=\"repeatable-forms-row-container\">\n      <section class=\"repeatable-forms-row\">\n        @for (field of groupForm.fields; track $index; let j = $index) {\n          <signal-form-input-item\n            [field]=\"field\"\n            [form]=\"groupForm\"\n            [index]=\"i\"\n          />\n        }\n      </section>\n      <aside class=\"repeatable-forms-actions-container\">\n        <lucide-icon\n          class=\"row-action\"\n          [img]=\"minus\"\n          [size]=\"16\"\n          (click)=\"removeItem.emit(i)\"\n          (keydown.enter)=\"removeItem.emit(i)\"\n          tabindex=\"0\"\n        />\n        <lucide-icon\n          class=\"row-action\"\n          [img]=\"plus\"\n          [size]=\"16\"\n          (click)=\"addItem.emit()\"\n          (keydown.enter)=\"addItem.emit()\"\n          tabindex=\"0\"\n        />\n      </aside>\n    </section>\n  } @empty {\n    <lucide-icon\n      class=\"row-action\"\n      [img]=\"plus\"\n      [size]=\"16\"\n      (click)=\"addItem.emit()\"\n      (keydown.enter)=\"addItem.emit()\"\n      tabindex=\"0\"\n    />\n  }\n</section>\n", styles: [".repeatable-form-group{padding-bottom:1rem;border-top:1px solid #e2efef;border-bottom:1px solid #e2efef}.repeatable-form-group:has(+.repeatable-form-group){border-bottom:none}.form-group-heading{margin:0;text-transform:capitalize;color:var(--signal-form-heading-color);font-size:var(--signal-form-heading-font-size);line-height:var(--signal-form-heading-line-height)}.repeatable-forms-row-container{display:flex;gap:8px}.repeatable-forms-row{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-start;align-items:flex-start}.repeatable-forms-actions-container{display:flex;gap:8px;align-items:center;justify-content:center}.repeatable-forms-actions-container .row-action{color:var(--signal-form-text);cursor:pointer;margin-top:16px}.repeatable-forms-actions-container .row-action:hover{color:var(--signal-form-primary-700)}\n"], dependencies: [{ kind: "component", type: SignalFormInputItemComponent, selector: "signal-form-input-item", inputs: ["field", "form", "index"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormRepeatableFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-repeatable-field', standalone: true, imports: [SignalFormInputItemComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"repeatable-form-group\">\n  <h3 class=\"form-group-heading\">{{ heading() }}</h3>\n\n  @for (groupForm of repeatableForms(); track $index; let i = $index) {\n    <section class=\"repeatable-forms-row-container\">\n      <section class=\"repeatable-forms-row\">\n        @for (field of groupForm.fields; track $index; let j = $index) {\n          <signal-form-input-item\n            [field]=\"field\"\n            [form]=\"groupForm\"\n            [index]=\"i\"\n          />\n        }\n      </section>\n      <aside class=\"repeatable-forms-actions-container\">\n        <lucide-icon\n          class=\"row-action\"\n          [img]=\"minus\"\n          [size]=\"16\"\n          (click)=\"removeItem.emit(i)\"\n          (keydown.enter)=\"removeItem.emit(i)\"\n          tabindex=\"0\"\n        />\n        <lucide-icon\n          class=\"row-action\"\n          [img]=\"plus\"\n          [size]=\"16\"\n          (click)=\"addItem.emit()\"\n          (keydown.enter)=\"addItem.emit()\"\n          tabindex=\"0\"\n        />\n      </aside>\n    </section>\n  } @empty {\n    <lucide-icon\n      class=\"row-action\"\n      [img]=\"plus\"\n      [size]=\"16\"\n      (click)=\"addItem.emit()\"\n      (keydown.enter)=\"addItem.emit()\"\n      tabindex=\"0\"\n    />\n  }\n</section>\n", styles: [".repeatable-form-group{padding-bottom:1rem;border-top:1px solid #e2efef;border-bottom:1px solid #e2efef}.repeatable-form-group:has(+.repeatable-form-group){border-bottom:none}.form-group-heading{margin:0;text-transform:capitalize;color:var(--signal-form-heading-color);font-size:var(--signal-form-heading-font-size);line-height:var(--signal-form-heading-line-height)}.repeatable-forms-row-container{display:flex;gap:8px}.repeatable-forms-row{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-start;align-items:flex-start}.repeatable-forms-actions-container{display:flex;gap:8px;align-items:center;justify-content:center}.repeatable-forms-actions-container .row-action{color:var(--signal-form-text);cursor:pointer;margin-top:16px}.repeatable-forms-actions-container .row-action:hover{color:var(--signal-form-primary-700)}\n"] }]
        }] });

class SignalFormSelectFieldComponent extends BaseInputDirective {
    showDropdown = signal(false);
    displayText = computed(() => this.displayValue());
    dropdownService = inject(FormDropdownService);
    host = inject(SignalFormHostDirective);
    constructor() {
        super();
        this.dropdownOverlayEffect();
    }
    dropdownOverlayEffect() {
        effect(() => {
            if (!this.showDropdown()) {
                return;
            }
            const reference = this.host.viewContainerRef.element.nativeElement;
            const currentValue = this.field().value();
            const currentOption = this.findOptionByValue(currentValue);
            this.dropdownService.openDropdown({
                options: this.field().options(),
                reference,
                viewContainerRef: this.host.viewContainerRef,
                ariaListboxId: `${String(this.field().name)}-listbox`,
                multiselect: false,
                initialSelection: currentOption,
                onSelect: (selected) => {
                    this.setValue(selected);
                    this.field().touched.set(true);
                    this.showDropdown.set(false);
                },
                onClose: () => {
                    this.showDropdown.set(false);
                },
            });
        }, {
            injector: this.injector,
        });
    }
    toggleDropdown() {
        this.showDropdown.update((show) => !show);
    }
    findOptionByValue(value) {
        if (!value) {
            return undefined;
        }
        const options = this.field().options();
        if (typeof value === 'object' && 'value' in value) {
            // If value is already a FormOption, find by value property
            return options.find((opt) => opt.value === value.value);
        }
        // Otherwise find by raw value
        return options.find((opt) => opt.value === value);
    }
    displayValue() {
        const value = this.field().value();
        if (!value) {
            return this.field().config?.placeholder ?? '';
        }
        if (typeof value === 'object' && 'label' in value) {
            return value.label;
        }
        const option = this.findOptionByValue(value);
        return option?.label ?? this.field().config?.placeholder ?? '';
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SignalFormSelectFieldComponent, isStandalone: true, selector: "signal-form-select-field", usesInheritance: true, hostDirectives: [{ directive: SignalFormHostDirective }], ngImport: i0, template: "<div\n  class=\"form-select-wrapper\"\n  role=\"combobox\"\n  [signalModel]=\"field()\"\n  [attr.aria-expanded]=\"showDropdown()\"\n  (click)=\"toggleDropdown()\"\n  (keydown.enter)=\"toggleDropdown()\"\n>\n  <div class=\"form-select-display\">\n    {{ displayValue() || field().config?.placeholder }}\n  </div>\n  <div class=\"form-select-arrow\">\u25BE</div>\n</div>\n", styles: [".form-select-wrapper{border:1px solid var(--signal-form-border-color);padding:var(--signal-form-field-padding);border-radius:var(--signal-form-border-radius-sm);cursor:pointer;display:flex;justify-content:space-between;align-items:center;min-height:40px;background:var(--signal-form-select-bg);color:var(--signal-form-input-text);transition:var(--signal-form-transition);box-shadow:var(--signal-form-shadow)}.form-select-wrapper:hover{border-color:var(--signal-forms-neutral-400)}.form-select-wrapper:focus-within{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.form-select-display{flex-grow:1;color:var(--signal-form-input-text)}.form-select-arrow{margin-left:8px;color:var(--signal-form-muted)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSelectFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-select-field', standalone: true, imports: [CommonModule, SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [SignalFormHostDirective], template: "<div\n  class=\"form-select-wrapper\"\n  role=\"combobox\"\n  [signalModel]=\"field()\"\n  [attr.aria-expanded]=\"showDropdown()\"\n  (click)=\"toggleDropdown()\"\n  (keydown.enter)=\"toggleDropdown()\"\n>\n  <div class=\"form-select-display\">\n    {{ displayValue() || field().config?.placeholder }}\n  </div>\n  <div class=\"form-select-arrow\">\u25BE</div>\n</div>\n", styles: [".form-select-wrapper{border:1px solid var(--signal-form-border-color);padding:var(--signal-form-field-padding);border-radius:var(--signal-form-border-radius-sm);cursor:pointer;display:flex;justify-content:space-between;align-items:center;min-height:40px;background:var(--signal-form-select-bg);color:var(--signal-form-input-text);transition:var(--signal-form-transition);box-shadow:var(--signal-form-shadow)}.form-select-wrapper:hover{border-color:var(--signal-forms-neutral-400)}.form-select-wrapper:focus-within{border-color:var(--signal-form-outline-focus);box-shadow:0 0 0 2px var(--signal-form-outline-focus-shadow)}.form-select-display{flex-grow:1;color:var(--signal-form-input-text)}.form-select-arrow{margin-left:8px;color:var(--signal-form-muted)}\n"] }]
        }], ctorParameters: () => [] });

class SignalFormSliderFieldComponent extends BaseInputDirective {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSliderFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SignalFormSliderFieldComponent, isStandalone: true, selector: "signal-form-slider-field", usesInheritance: true, ngImport: i0, template: "<div class=\"slider-wrapper\">\n  <input type=\"range\" class=\"slider\" [signalModel]=\"field()\" />\n  {{ field().value() }}\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSliderFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-slider-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"slider-wrapper\">\n  <input type=\"range\" class=\"slider\" [signalModel]=\"field()\" />\n  {{ field().value() }}\n</div>\n" }]
        }] });

class SignalFormSwitchFieldComponent extends BaseInputDirective {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSwitchFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SignalFormSwitchFieldComponent, isStandalone: true, selector: "signal-form-switch-field", usesInheritance: true, ngImport: i0, template: "<label class=\"switch-wrapper\">\n  <input\n    type=\"checkbox\"\n    role=\"switch\"\n    class=\"switch-input\"\n    [signalModel]=\"field()\"\n  />\n  <span class=\"switch-slider\" aria-hidden=\"true\"></span>\n</label>\n", styles: [".switch-wrapper{position:relative;display:inline-block;width:44px;height:24px}.switch-wrapper .switch-input{opacity:0;width:0;height:0}.switch-wrapper .switch-slider{position:absolute;cursor:pointer;background-color:var(--signal-forms-neutral-300);border-radius:34px;inset:0;transition:.3s}.switch-wrapper .switch-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:var(--signal-form-input-bg);border-radius:50%;transition:.3s}.switch-wrapper .switch-input:checked+.switch-slider{background-color:var(--signal-forms-success-500)}.switch-wrapper .switch-input:checked+.switch-slider:before{transform:translate(20px)}.switch-wrapper .switch-input:disabled+.switch-slider{background-color:var(--signal-form-disabled-bg);cursor:not-allowed}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSwitchFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-switch-field', standalone: true, imports: [SignalModelDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<label class=\"switch-wrapper\">\n  <input\n    type=\"checkbox\"\n    role=\"switch\"\n    class=\"switch-input\"\n    [signalModel]=\"field()\"\n  />\n  <span class=\"switch-slider\" aria-hidden=\"true\"></span>\n</label>\n", styles: [".switch-wrapper{position:relative;display:inline-block;width:44px;height:24px}.switch-wrapper .switch-input{opacity:0;width:0;height:0}.switch-wrapper .switch-slider{position:absolute;cursor:pointer;background-color:var(--signal-forms-neutral-300);border-radius:34px;inset:0;transition:.3s}.switch-wrapper .switch-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:var(--signal-form-input-bg);border-radius:50%;transition:.3s}.switch-wrapper .switch-input:checked+.switch-slider{background-color:var(--signal-forms-success-500)}.switch-wrapper .switch-input:checked+.switch-slider:before{transform:translate(20px)}.switch-wrapper .switch-input:disabled+.switch-slider{background-color:var(--signal-form-disabled-bg);cursor:not-allowed}\n"] }]
        }] });

class WordCountComponent {
    /** The text to count words for */
    text = input('');
    /** Display format for the word count */
    format = input('words');
    /** Whether to show compact styling */
    compact = input(false);
    stats = computed(() => this.wordCountService.getWordCount(this.text()));
    formattedCount = computed(() => this.wordCountService.formatWordCount(this.stats(), this.format()));
    wordCountService = inject(WordCountService);
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WordCountComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: WordCountComponent, isStandalone: true, selector: "signal-form-word-count", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
    <div class="word-count" [class.word-count-compact]="compact()">
      <span class="word-count-text">{{ formattedCount() }}</span>
    </div>
  `, isInline: true, styles: [".word-count{display:flex;align-items:center;justify-content:flex-end;margin-top:.25rem}.word-count .word-count-text{font-size:.75rem;color:#6b7280;font-weight:400;-webkit-user-select:none;user-select:none}.word-count.word-count-compact{margin-top:.125rem}.word-count.word-count-compact .word-count-text{font-size:.6875rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WordCountComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-word-count', standalone: true, imports: [CommonModule], template: `
    <div class="word-count" [class.word-count-compact]="compact()">
      <span class="word-count-text">{{ formattedCount() }}</span>
    </div>
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".word-count{display:flex;align-items:center;justify-content:flex-end;margin-top:.25rem}.word-count .word-count-text{font-size:.75rem;color:#6b7280;font-weight:400;-webkit-user-select:none;user-select:none}.word-count.word-count-compact{margin-top:.125rem}.word-count.word-count-compact .word-count-text{font-size:.6875rem}\n"] }]
        }] });

class SignalFormTextFieldComponent extends BaseInputDirective {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormTextFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormTextFieldComponent, isStandalone: true, selector: "signal-form-text-field", usesInheritance: true, ngImport: i0, template: "<div class=\"form-input-wrapper\">\n  <input type=\"text\" class=\"form-input\" [signalModel]=\"field()\" />\n</div>\n\n@if (field().config?.wordCount) {\n  <signal-form-word-count\n    [text]=\"field().value()\"\n    format=\"characters\"\n    [compact]=\"true\"\n  />\n}\n", dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }, { kind: "component", type: WordCountComponent, selector: "signal-form-word-count", inputs: ["text", "format", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormTextFieldComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form-text-field', standalone: true, imports: [SignalModelDirective, WordCountComponent], template: "<div class=\"form-input-wrapper\">\n  <input type=\"text\" class=\"form-input\" [signalModel]=\"field()\" />\n</div>\n\n@if (field().config?.wordCount) {\n  <signal-form-word-count\n    [text]=\"field().value()\"\n    format=\"characters\"\n    [compact]=\"true\"\n  />\n}\n" }]
        }] });

class SignalFormTextareaFieldComponent extends BaseInputDirective {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormTextareaFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormTextareaFieldComponent, isStandalone: true, selector: "signal-form-textarea-field", usesInheritance: true, ngImport: i0, template: "<div class=\"form-input-wrapper\">\n  <textarea class=\"form-input\" [signalModel]=\"field()\">{{\n    field().value()\n  }}</textarea>\n</div>\n\n@if (field().config?.wordCount) {\n  <signal-form-word-count\n    [text]=\"field().value()\"\n    format=\"both\"\n    [compact]=\"false\"\n  />\n}\n", styles: ["textarea.form-input{min-height:80px;resize:vertical;background-color:var(--signal-form-textarea-bg)}\n"], dependencies: [{ kind: "directive", type: SignalModelDirective, selector: "[signalModel]", inputs: ["signalModel"] }, { kind: "component", type: WordCountComponent, selector: "signal-form-word-count", inputs: ["text", "format", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormTextareaFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 'signal-form-textarea-field', standalone: true, imports: [SignalModelDirective, WordCountComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"form-input-wrapper\">\n  <textarea class=\"form-input\" [signalModel]=\"field()\">{{\n    field().value()\n  }}</textarea>\n</div>\n\n@if (field().config?.wordCount) {\n  <signal-form-word-count\n    [text]=\"field().value()\"\n    format=\"both\"\n    [compact]=\"false\"\n  />\n}\n", styles: ["textarea.form-input{min-height:80px;resize:vertical;background-color:var(--signal-form-textarea-bg)}\n"] }]
        }] });

var index = /*#__PURE__*/Object.freeze({
    __proto__: null,
    SignalFormAutocompleteFieldComponent: SignalFormAutocompleteFieldComponent,
    SignalFormCheckboxFieldComponent: SignalFormCheckboxFieldComponent,
    SignalFormCheckboxGroupFieldComponent: SignalFormCheckboxGroupFieldComponent,
    SignalFormChipListFieldComponent: SignalFormChipListFieldComponent,
    SignalFormColorFieldComponent: SignalFormColorFieldComponent,
    SignalFormDatetimeFieldComponent: SignalFormDatetimeFieldComponent,
    SignalFormFileFieldComponent: SignalFormFileFieldComponent,
    SignalFormMultiselectFieldComponent: SignalFormMultiselectFieldComponent,
    SignalFormNumberFieldComponent: SignalFormNumberFieldComponent,
    SignalFormPasswordFieldComponent: SignalFormPasswordFieldComponent,
    SignalFormRadioFieldComponent: SignalFormRadioFieldComponent,
    SignalFormRatingFieldComponent: SignalFormRatingFieldComponent,
    SignalFormRepeatableFieldComponent: SignalFormRepeatableFieldComponent,
    SignalFormSelectFieldComponent: SignalFormSelectFieldComponent,
    SignalFormSliderFieldComponent: SignalFormSliderFieldComponent,
    SignalFormSwitchFieldComponent: SignalFormSwitchFieldComponent,
    SignalFormTextFieldComponent: SignalFormTextFieldComponent,
    SignalFormTextareaFieldComponent: SignalFormTextareaFieldComponent
});

class FieldTraversalUtils {
    static findFieldByPath(form, path) {
        const segments = this.parsePath(path);
        let currentField;
        let currentForm = form;
        for (let i = 0; i < segments.length; i++) {
            const segment = segments[i];
            if (segment.type === 'field') {
                currentField = currentForm.fields.find((f) => f.name === segment.name);
                if (!currentField) {
                    return undefined;
                }
                if (i < segments.length - 1) {
                    if (this.isFieldWithForm(currentField)) {
                        currentForm = currentField.form;
                    }
                    else if (this.isRepeatableField(currentField)) {
                        continue;
                    }
                    else {
                        return undefined;
                    }
                }
            }
            else if (segment.type === 'index') {
                if (currentField && this.isRepeatableField(currentField)) {
                    const forms = currentField.repeatableForms();
                    if (Array.isArray(forms) && forms[segment.index]) {
                        currentForm = forms[segment.index];
                    }
                    else {
                        return undefined;
                    }
                }
                else {
                    return undefined;
                }
            }
        }
        return currentField;
    }
    static parsePath(path) {
        const segments = [];
        const parts = path.split(/[\.\[\]]+/).filter(Boolean);
        let i = 0;
        while (i < parts.length) {
            const part = parts[i];
            if (!isNaN(Number(part))) {
                segments.push({ type: 'index', index: Number(part) });
            }
            else {
                segments.push({ type: 'field', name: part });
            }
            i++;
        }
        return segments;
    }
    static isFieldWithForm(field) {
        return 'form' in field && this.isSignalFormContainer(field.form);
    }
    static isRepeatableField(field) {
        return ('repeatableForms' in field &&
            typeof field.repeatableForms === 'function');
    }
    static isSignalFormContainer(value) {
        return (value !== null &&
            typeof value === 'object' &&
            'fields' in value &&
            Array.isArray(value.fields) &&
            'getField' in value &&
            typeof value.getField === 'function');
    }
}

class SignalFormErrorSummaryComponent {
    form = input.required();
    fieldsEls = viewChildren('formFields');
    currentIndex = signal(0);
    injector = inject(Injector);
    errors = computed(() => {
        if ('steps' in this.form()) {
            return this.form().steps.flatMap((step) => step.getErrors());
        }
        return this.form().getErrors();
    });
    currentError = computed(() => {
        const all = this.errors();
        return all.length ? all[this.currentIndex() % all.length] : null;
    });
    focusNext() {
        if (!this.errors().length) {
            return;
        }
        this.currentIndex.update((i) => (i + 1) % this.errors().length);
        this.focusCurrent();
    }
    focusPrevious() {
        const errs = this.errors();
        if (!errs.length) {
            return;
        }
        this.currentIndex.update((i) => (i - 1 + errs.length) % errs.length);
        this.focusCurrent();
    }
    focusCurrent() {
        const err = this.currentError();
        if (!err) {
            return;
        }
        const isStepped = 'steps' in this.form();
        if (isStepped) {
            this.focusSteppedError(err);
        }
        else {
            this.focusNestedError(err);
        }
    }
    focusSteppedError(err) {
        const steppedForm = this.form();
        // Find the step that contains a field matching the full path
        const stepIndex = steppedForm.steps.findIndex((step) => FieldTraversalUtils.findFieldByPath(step, err.path));
        if (stepIndex !== -1) {
            steppedForm.currentStep.set(stepIndex);
            effect(() => {
                this.focusNestedError(err, steppedForm.steps[stepIndex]);
            }, { injector: this.injector });
        }
    }
    focusNestedError(err, form = this.form()) {
        if (err.focusField) {
            err.focusField();
            return;
        }
        const field = FieldTraversalUtils.findFieldByPath(form, err.path);
        if (field?.focus) {
            field.focus.set(true);
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormErrorSummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormErrorSummaryComponent, isStandalone: true, selector: "signal-form-error-summary", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "fieldsEls", predicate: ["formFields"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (currentError()) {\n  <div class=\"form-error-summary\">\n    <button type=\"button\" (click)=\"focusPrevious()\">&#x25C0;</button>\n    <span\n      tabindex=\"0\"\n      class=\"form-error-summary-message\"\n      (click)=\"focusCurrent()\"\n      (keydown.enter)=\"focusCurrent()\"\n      >{{ currentError()?.message ?? currentError() }}</span\n    >\n    <button type=\"button\" (click)=\"focusNext()\">&#x25B6;</button>\n  </div>\n}\n", styles: [".form-error-summary{background-color:var(--signal-form-error-bg);color:var(--signal-form-error-text);padding:var(--signal-form-error-summary-padding);display:flex;align-items:center;justify-content:space-between;gap:var(--signal-form-error-summary-gap);border-radius:var(--signal-form-border-radius);font-weight:var(--signal-form-error-summary-font-weight);max-width:100%;margin-bottom:var(--signal-form-error-summary-margin-bottom)}.form-error-summary-message{cursor:pointer}.form-error-summary button{appearance:none!important;border:none;background:transparent;color:var(--signal-form-error-text);outline:none;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormErrorSummaryComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form-error-summary', standalone: true, template: "@if (currentError()) {\n  <div class=\"form-error-summary\">\n    <button type=\"button\" (click)=\"focusPrevious()\">&#x25C0;</button>\n    <span\n      tabindex=\"0\"\n      class=\"form-error-summary-message\"\n      (click)=\"focusCurrent()\"\n      (keydown.enter)=\"focusCurrent()\"\n      >{{ currentError()?.message ?? currentError() }}</span\n    >\n    <button type=\"button\" (click)=\"focusNext()\">&#x25B6;</button>\n  </div>\n}\n", styles: [".form-error-summary{background-color:var(--signal-form-error-bg);color:var(--signal-form-error-text);padding:var(--signal-form-error-summary-padding);display:flex;align-items:center;justify-content:space-between;gap:var(--signal-form-error-summary-gap);border-radius:var(--signal-form-border-radius);font-weight:var(--signal-form-error-summary-font-weight);max-width:100%;margin-bottom:var(--signal-form-error-summary-margin-bottom)}.form-error-summary-message{cursor:pointer}.form-error-summary button{appearance:none!important;border:none;background:transparent;color:var(--signal-form-error-text);outline:none;cursor:pointer}\n"] }]
        }] });

const expandCollapse = trigger('expandCollapse', [
    state('open', style({
        height: '*',
        opacity: 1,
        overflow: 'hidden',
    })),
    state('closed', style({
        height: '0px',
        opacity: 0,
        overflow: 'hidden',
    })),
    transition('open <=> closed', animate('600ms ease-in')),
    transition('closed <=> open', animate('600ms ease-out')),
]);
const growFadeIn = trigger('growFadeIn', [
    transition(':enter', [
        style({ opacity: 0, transform: 'scaleY(0.95)', height: 0 }),
        animate('350ms ease-out', style({ opacity: 1, transform: 'scaleY(1)', height: '*' })),
    ]),
]);
const fadeInOut = trigger('fadeInOut', [
    transition(':enter', [
        style({ opacity: 0 }),
        animate('150ms ease-out', style({ opacity: 1 })),
    ]),
    transition(':leave', [animate('150ms ease-in', style({ opacity: 0 }))]),
]);

class CollapsableSectionComponent {
    collapsedInitially = input(false);
    bodyTemplate = input();
    summaryTemplate = input();
    bodyTemplateContext = input();
    summaryTemplateContext = input();
    collapsing = signal(false);
    collapsed = signal(false);
    chevronDown = ChevronDownCircleIcon;
    chevronUp = ChevronUpCircleIcon;
    constructor() {
        // Initialize collapsed state from input
        effect(() => {
            this.collapsed.set(this.collapsedInitially());
        });
    }
    toggle() {
        if (!this.collapsed()) {
            this.collapsing.set(true);
            return;
        }
        this.collapsed.set(false);
        this.collapsing.set(false);
    }
    shouldShow = computed(() => !this.collapsed() || this.collapsing());
    onDone(event) {
        if (event.toState === 'closed') {
            this.collapsed.set(true);
            this.collapsing.set(false);
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CollapsableSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: CollapsableSectionComponent, isStandalone: true, selector: "collapsable-section", inputs: { collapsedInitially: { classPropertyName: "collapsedInitially", publicName: "collapsedInitially", isSignal: true, isRequired: false, transformFunction: null }, bodyTemplate: { classPropertyName: "bodyTemplate", publicName: "bodyTemplate", isSignal: true, isRequired: false, transformFunction: null }, summaryTemplate: { classPropertyName: "summaryTemplate", publicName: "summaryTemplate", isSignal: true, isRequired: false, transformFunction: null }, bodyTemplateContext: { classPropertyName: "bodyTemplateContext", publicName: "bodyTemplateContext", isSignal: true, isRequired: false, transformFunction: null }, summaryTemplateContext: { classPropertyName: "summaryTemplateContext", publicName: "summaryTemplateContext", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section class=\"collapsable-header\">\n  <div class=\"collapsable-header-content\">\n    @if (summaryTemplate?.(); as summaryTemplate) {\n      <ng-container\n        *ngTemplateOutlet=\"summaryTemplate; context: summaryTemplateContext?.()\"\n      />\n    } @else {\n      <ng-content select=\"[summary]\" />\n    }\n  </div>\n  <aside class=\"collapsable-button-container\">\n    <lucide-icon\n      class=\"collapsable-button\"\n      [img]=\"shouldShow() ? chevronDown : chevronUp\"\n      (click)=\"toggle()\"\n    />\n  </aside>\n</section>\n@if (shouldShow()) {\n  <div\n    [@expandCollapse]=\"collapsing() ? 'closed' : 'open'\"\n    (@expandCollapse.done)=\"onDone($event)\"\n  >\n    <div @growFadeIn>\n      @if (bodyTemplate?.(); as bodyTemplate) {\n        <ng-container\n          *ngTemplateOutlet=\"bodyTemplate; context: bodyTemplateContext?.()\"\n        />\n      } @else {\n        <ng-content select=\"[body]\" />\n      }\n    </div>\n  </div>\n}\n", styles: [".collapsable-header{display:flex}.collapsable-header-content{flex:1}.collapsable-header .collapsable-button-container{flex:0}.collapsable-button-container{display:flex;justify-content:center;align-items:center;padding:8px 12px}.collapsable-button{cursor:pointer;color:#828689}.collapsable-button:hover{color:#00aeff}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], animations: [expandCollapse, growFadeIn], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CollapsableSectionComponent, decorators: [{
            type: Component,
            args: [{ selector: 'collapsable-section', standalone: true, imports: [NgTemplateOutlet, LucideAngularModule], animations: [expandCollapse, growFadeIn], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"collapsable-header\">\n  <div class=\"collapsable-header-content\">\n    @if (summaryTemplate?.(); as summaryTemplate) {\n      <ng-container\n        *ngTemplateOutlet=\"summaryTemplate; context: summaryTemplateContext?.()\"\n      />\n    } @else {\n      <ng-content select=\"[summary]\" />\n    }\n  </div>\n  <aside class=\"collapsable-button-container\">\n    <lucide-icon\n      class=\"collapsable-button\"\n      [img]=\"shouldShow() ? chevronDown : chevronUp\"\n      (click)=\"toggle()\"\n    />\n  </aside>\n</section>\n@if (shouldShow()) {\n  <div\n    [@expandCollapse]=\"collapsing() ? 'closed' : 'open'\"\n    (@expandCollapse.done)=\"onDone($event)\"\n  >\n    <div @growFadeIn>\n      @if (bodyTemplate?.(); as bodyTemplate) {\n        <ng-container\n          *ngTemplateOutlet=\"bodyTemplate; context: bodyTemplateContext?.()\"\n        />\n      } @else {\n        <ng-content select=\"[body]\" />\n      }\n    </div>\n  </div>\n}\n", styles: [".collapsable-header{display:flex}.collapsable-header-content{flex:1}.collapsable-header .collapsable-button-container{flex:0}.collapsable-button-container{display:flex;justify-content:center;align-items:center;padding:8px 12px}.collapsable-button{cursor:pointer;color:#828689}.collapsable-button:hover{color:#00aeff}\n"] }]
        }], ctorParameters: () => [] });

class SignalFormFieldsComponent {
    fields = input.required();
    form = input.required();
    index = input(null);
    isRoot = input(true);
    signalFormParent = input(false);
    // Required theme service injection
    signalFormThemeService = inject(SignalFormThemeService);
    formFieldType = FormFieldType;
    visibleFields = computed(() => this.fields().filter((f) => !f.isHidden?.()));
    isGridAreaConfig(config) {
        if (!config) {
            return false;
        }
        return config?.layout === 'grid-area' || 'gridArea' in config;
    }
    gridTemplateAreas = computed(() => {
        const config = this.form().config;
        if (!this.isGridAreaConfig(config)) {
            return null;
        }
        return config?.gridArea.map((row) => `"${row.join(' ')}"`).join(' ');
    });
    isGridLayout() {
        return this.isGridAreaConfig(this.form().config);
    }
    formLayoutClass = computed(() => {
        switch (this.form()?.config?.view) {
            case 'collapsable':
                return 'form-group-collapsable';
            case 'row':
                return 'form-group-row';
            default:
                return 'form-group-stacked';
        }
    });
    get hostClass() {
        return this.formLayoutClass();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormFieldsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormFieldsComponent, isStandalone: true, selector: "signal-form-fields", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null }, isRoot: { classPropertyName: "isRoot", publicName: "isRoot", isSignal: true, isRequired: false, transformFunction: null }, signalFormParent: { classPropertyName: "signalFormParent", publicName: "signalFormParent", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.grid-template-areas": "gridTemplateAreas()", "class.grid": "isGridLayout()", "class": "this.hostClass" } }, hostDirectives: [{ directive: SignalFormHostDirective }], ngImport: i0, template: "@if (isRoot() && !signalFormParent() && form().config?.view === \"collapsable\") {\n  <collapsable-section class=\"collapsable-fields\">\n    <ng-container summary>\n      {{ form().title }}\n    </ng-container>\n    <ng-container body *ngTemplateOutlet=\"fieldBlockTemplate\" />\n  </collapsable-section>\n} @else {\n  <ng-container *ngTemplateOutlet=\"fieldBlockTemplate\" />\n}\n\n<ng-template #fieldBlockTemplate>\n  @for (field of visibleFields?.(); track field.name) {\n    @if (field.type === formFieldType.REPEATABLE_GROUP) {\n      <signal-form-repeatable-field\n        [repeatableForms]=\"field.repeatableForms()\"\n        [fields]=\"field.fields\"\n        [heading]=\"field.heading\"\n        [style.gridArea]=\"field.name\"\n        (addItem)=\"field.addItem()\"\n        (removeItem)=\"field.removeItem($event)\"\n      />\n    } @else {\n      @if (field.fields?.length) {\n        <section\n          class=\"nested-form-section\"\n          [class]=\"'form-group-' + (field.config?.view ?? 'stacked')\"\n          [style.gridArea]=\"field.name\"\n        >\n          @if (field.config?.view === \"collapsable\") {\n            <collapsable-section\n              [summaryTemplate]=\"titleTemplate\"\n              [bodyTemplate]=\"fieldsTemplate\"\n              [summaryTemplateContext]=\"{ $implicit: field, i: index() }\"\n              [bodyTemplateContext]=\"{ $implicit: field, i: index() }\"\n            />\n          } @else {\n            <ng-container\n              *ngTemplateOutlet=\"\n                titleTemplate;\n                context: { $implicit: field, i: index() }\n              \"\n            />\n            <ng-container\n              *ngTemplateOutlet=\"\n                fieldsTemplate;\n                context: { $implicit: field, i: index() }\n              \"\n            />\n          }\n        </section>\n      } @else {\n        <signal-form-input-item\n          [field]=\"field\"\n          [form]=\"form()\"\n          [index]=\"index()\"\n        />\n      }\n    }\n  }\n</ng-template>\n\n<ng-template #titleTemplate let-field>\n  <div class=\"nested-form-title\">\n    <h2 class=\"nested-form-heading\">{{ field.heading }}</h2>\n    <h4 class=\"nested-form-subheading\">{{ field.subheading }}</h4>\n  </div>\n</ng-template>\n\n<ng-template #fieldsTemplate let-field let-i=\"i\">\n  <div class=\"nested-form-fields\">\n    <signal-form-fields\n      [form]=\"field.form\"\n      [fields]=\"field.fields\"\n      [index]=\"i\"\n      [isRoot]=\"false\"\n    />\n  </div>\n</ng-template>\n", styles: [":host.form-group-row{flex-direction:row}:host.form-group-row>signal-form-input-item{flex:1 1}:host.grid{display:grid;width:100%;grid-template-columns:minmax(0,1fr)}.nested-form-section{border-top:1px solid var(--signal-form-section-border);border-bottom:1px solid var(--signal-form-section-border);display:grid;width:100%;padding:var(--signal-form-nested-padding);content-visibility:auto;contain-intrinsic-size:0 200px}.nested-form-section:has(+.nested-form-section){border-bottom:none}.nested-form-title{display:flex;flex-direction:column;gap:var(--signal-form-nested-title-gap);margin-bottom:var(--signal-form-nested-margin-bottom)}.nested-form-heading{margin:0;text-transform:capitalize;color:var(--signal-form-heading-color);font-size:var(--signal-form-heading-font-size);line-height:var(--signal-form-heading-line-height)}.nested-form-subheading{margin:0;font-weight:400;color:var(--signal-form-subheading-color);font-size:var(--signal-form-subheading-font-size);text-transform:capitalize}.nested-form-fields{display:flex;width:100%}.nested-form-fields>signal-form-fields{width:100%}.form-group-stacked{content-visibility:auto;contain-intrinsic-size:0 100px}.form-group-stacked .nested-form-title{margin-bottom:var(--signal-form-group-gap)}.form-group-stacked .nested-form-fields{display:flex;flex-direction:column;gap:var(--signal-form-group-gap-lg)}.form-group-row{display:flex;gap:var(--signal-form-group-gap-xl);content-visibility:auto;contain-intrinsic-size:0 100px}.form-group-row .nested-form-section .nested-form-title{flex:0 0 var(--signal-form-nested-title-width)}.form-group-row .nested-form-section:has(>.nested-form-fields .form-group-stacked) .nested-form-title{flex:1 1 50%}.form-group-row .nested-form-fields{display:flex;flex-wrap:wrap;flex:1;gap:var(--signal-form-group-gap) var(--signal-form-group-gap-xl)}.collapsable-fields{content-visibility:auto;contain-intrinsic-size:0 300px;width:100%}\n"], dependencies: [{ kind: "component", type: SignalFormFieldsComponent, selector: "signal-form-fields", inputs: ["fields", "form", "index", "isRoot", "signalFormParent"] }, { kind: "component", type: SignalFormInputItemComponent, selector: "signal-form-input-item", inputs: ["field", "form", "index"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: CollapsableSectionComponent, selector: "collapsable-section", inputs: ["collapsedInitially", "bodyTemplate", "summaryTemplate", "bodyTemplateContext", "summaryTemplateContext"] }, { kind: "component", type: SignalFormRepeatableFieldComponent, selector: "signal-form-repeatable-field", inputs: ["repeatableForms", "heading", "fields"], outputs: ["addItem", "removeItem"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormFieldsComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form-fields', imports: [
                        SignalFormInputItemComponent,
                        NgTemplateOutlet,
                        CollapsableSectionComponent,
                        SignalFormRepeatableFieldComponent,
                    ], standalone: true, hostDirectives: [SignalFormHostDirective], host: {
                        '[style.grid-template-areas]': 'gridTemplateAreas()',
                        '[class.grid]': 'isGridLayout()',
                    }, template: "@if (isRoot() && !signalFormParent() && form().config?.view === \"collapsable\") {\n  <collapsable-section class=\"collapsable-fields\">\n    <ng-container summary>\n      {{ form().title }}\n    </ng-container>\n    <ng-container body *ngTemplateOutlet=\"fieldBlockTemplate\" />\n  </collapsable-section>\n} @else {\n  <ng-container *ngTemplateOutlet=\"fieldBlockTemplate\" />\n}\n\n<ng-template #fieldBlockTemplate>\n  @for (field of visibleFields?.(); track field.name) {\n    @if (field.type === formFieldType.REPEATABLE_GROUP) {\n      <signal-form-repeatable-field\n        [repeatableForms]=\"field.repeatableForms()\"\n        [fields]=\"field.fields\"\n        [heading]=\"field.heading\"\n        [style.gridArea]=\"field.name\"\n        (addItem)=\"field.addItem()\"\n        (removeItem)=\"field.removeItem($event)\"\n      />\n    } @else {\n      @if (field.fields?.length) {\n        <section\n          class=\"nested-form-section\"\n          [class]=\"'form-group-' + (field.config?.view ?? 'stacked')\"\n          [style.gridArea]=\"field.name\"\n        >\n          @if (field.config?.view === \"collapsable\") {\n            <collapsable-section\n              [summaryTemplate]=\"titleTemplate\"\n              [bodyTemplate]=\"fieldsTemplate\"\n              [summaryTemplateContext]=\"{ $implicit: field, i: index() }\"\n              [bodyTemplateContext]=\"{ $implicit: field, i: index() }\"\n            />\n          } @else {\n            <ng-container\n              *ngTemplateOutlet=\"\n                titleTemplate;\n                context: { $implicit: field, i: index() }\n              \"\n            />\n            <ng-container\n              *ngTemplateOutlet=\"\n                fieldsTemplate;\n                context: { $implicit: field, i: index() }\n              \"\n            />\n          }\n        </section>\n      } @else {\n        <signal-form-input-item\n          [field]=\"field\"\n          [form]=\"form()\"\n          [index]=\"index()\"\n        />\n      }\n    }\n  }\n</ng-template>\n\n<ng-template #titleTemplate let-field>\n  <div class=\"nested-form-title\">\n    <h2 class=\"nested-form-heading\">{{ field.heading }}</h2>\n    <h4 class=\"nested-form-subheading\">{{ field.subheading }}</h4>\n  </div>\n</ng-template>\n\n<ng-template #fieldsTemplate let-field let-i=\"i\">\n  <div class=\"nested-form-fields\">\n    <signal-form-fields\n      [form]=\"field.form\"\n      [fields]=\"field.fields\"\n      [index]=\"i\"\n      [isRoot]=\"false\"\n    />\n  </div>\n</ng-template>\n", styles: [":host.form-group-row{flex-direction:row}:host.form-group-row>signal-form-input-item{flex:1 1}:host.grid{display:grid;width:100%;grid-template-columns:minmax(0,1fr)}.nested-form-section{border-top:1px solid var(--signal-form-section-border);border-bottom:1px solid var(--signal-form-section-border);display:grid;width:100%;padding:var(--signal-form-nested-padding);content-visibility:auto;contain-intrinsic-size:0 200px}.nested-form-section:has(+.nested-form-section){border-bottom:none}.nested-form-title{display:flex;flex-direction:column;gap:var(--signal-form-nested-title-gap);margin-bottom:var(--signal-form-nested-margin-bottom)}.nested-form-heading{margin:0;text-transform:capitalize;color:var(--signal-form-heading-color);font-size:var(--signal-form-heading-font-size);line-height:var(--signal-form-heading-line-height)}.nested-form-subheading{margin:0;font-weight:400;color:var(--signal-form-subheading-color);font-size:var(--signal-form-subheading-font-size);text-transform:capitalize}.nested-form-fields{display:flex;width:100%}.nested-form-fields>signal-form-fields{width:100%}.form-group-stacked{content-visibility:auto;contain-intrinsic-size:0 100px}.form-group-stacked .nested-form-title{margin-bottom:var(--signal-form-group-gap)}.form-group-stacked .nested-form-fields{display:flex;flex-direction:column;gap:var(--signal-form-group-gap-lg)}.form-group-row{display:flex;gap:var(--signal-form-group-gap-xl);content-visibility:auto;contain-intrinsic-size:0 100px}.form-group-row .nested-form-section .nested-form-title{flex:0 0 var(--signal-form-nested-title-width)}.form-group-row .nested-form-section:has(>.nested-form-fields .form-group-stacked) .nested-form-title{flex:1 1 50%}.form-group-row .nested-form-fields{display:flex;flex-wrap:wrap;flex:1;gap:var(--signal-form-group-gap) var(--signal-form-group-gap-xl)}.collapsable-fields{content-visibility:auto;contain-intrinsic-size:0 300px;width:100%}\n"] }]
        }], propDecorators: { hostClass: [{
                type: HostBinding,
                args: ['class']
            }] } });

var signalFormFields_component = /*#__PURE__*/Object.freeze({
    __proto__: null,
    SignalFormFieldsComponent: SignalFormFieldsComponent
});

class SignalFormSaveButtonComponent {
    form = input.required();
    submitButtonText = input('Save');
    showReset = input(false);
    onSave = output();
    buttonText = computed(() => {
        const status = this.form().status();
        switch (status) {
            case FormStatus.Submitting:
                return 'Saving...';
            case FormStatus.Success:
                return 'Saved successfully';
            default:
                return this.submitButtonText();
        }
    });
    isDisabled = computed(() => {
        const status = this.form().status();
        return !this.form().anyTouched() || status === FormStatus.Submitting;
    });
    buttonClass = computed(() => {
        const status = this.form().status();
        const classes = ['form-button'];
        if (status === FormStatus.Submitting) {
            classes.push('saving');
        }
        else if (status === FormStatus.Success) {
            classes.push('success');
        }
        else if (this.form().hasSaved()) {
            classes.push('saved');
        }
        return classes.join(' ');
    });
    save() {
        if (!this.form().validateForm()) {
            return;
        }
        this.form().save();
        this.onSave.emit(this.form().getValue());
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSaveButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormSaveButtonComponent, isStandalone: true, selector: "signal-form-save-button", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, submitButtonText: { classPropertyName: "submitButtonText", publicName: "submitButtonText", isSignal: true, isRequired: false, transformFunction: null }, showReset: { classPropertyName: "showReset", publicName: "showReset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSave: "onSave" }, ngImport: i0, template: "<section class=\"form-button-row\">\n  @if (showReset()) {\n    <button class=\"form-button\" type=\"button\" (click)=\"form().reset()\">\n      Reset\n    </button>\n  }\n\n  <button\n    type=\"submit\"\n    [disabled]=\"isDisabled()\"\n    [class]=\"buttonClass()\"\n    (click)=\"save()\"\n  >\n    {{ buttonText() }}\n  </button>\n</section>\n", styles: [".form-button.saving{background-color:var(--signal-form-button-primary-bg);cursor:wait;position:relative}.form-button.saving:after{content:\"\";position:absolute;left:8px;top:50%;transform:translateY(-50%);width:12px;height:12px;border:2px solid transparent;border-top:2px solid currentColor;border-radius:50%;animation:spin 1s linear infinite}.form-button.success{background-color:var(--signal-form-success-color);color:var(--signal-forms-neutral-50)}.form-button.success:hover{background-color:var(--signal-form-success-color)}.form-button.saved{opacity:.8}@keyframes spin{0%{transform:translateY(-50%) rotate(0)}to{transform:translateY(-50%) rotate(360deg)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormSaveButtonComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form-save-button', standalone: true, template: "<section class=\"form-button-row\">\n  @if (showReset()) {\n    <button class=\"form-button\" type=\"button\" (click)=\"form().reset()\">\n      Reset\n    </button>\n  }\n\n  <button\n    type=\"submit\"\n    [disabled]=\"isDisabled()\"\n    [class]=\"buttonClass()\"\n    (click)=\"save()\"\n  >\n    {{ buttonText() }}\n  </button>\n</section>\n", styles: [".form-button.saving{background-color:var(--signal-form-button-primary-bg);cursor:wait;position:relative}.form-button.saving:after{content:\"\";position:absolute;left:8px;top:50%;transform:translateY(-50%);width:12px;height:12px;border:2px solid transparent;border-top:2px solid currentColor;border-radius:50%;animation:spin 1s linear infinite}.form-button.success{background-color:var(--signal-form-success-color);color:var(--signal-forms-neutral-50)}.form-button.success:hover{background-color:var(--signal-form-success-color)}.form-button.saved{opacity:.8}@keyframes spin{0%{transform:translateY(-50%) rotate(0)}to{transform:translateY(-50%) rotate(360deg)}}\n"] }]
        }] });

var StepStatus;
(function (StepStatus) {
    StepStatus["Complete"] = "complete";
    StepStatus["Error"] = "error";
    StepStatus["InComplete"] = "incomplete";
})(StepStatus || (StepStatus = {}));
class SignalFormStepperNavComponent {
    hasSaved = input();
    steps = input.required();
    currentStep = model.required();
    doesStepHaveErrors = signal([]);
    isStepComplete = signal([]);
    requiredPerStepMap = signal({});
    stepStatus = StepStatus;
    squareCheckIcon = SquareCheck;
    injector = inject(Injector);
    constructor() {
        this.isStepCompleteOrErrorEffect();
        this.requiredPerStepMapEffect();
    }
    goToStep(index) {
        const steps = this.steps();
        const canSkip = true;
        if (index <= this.currentStep() ||
            canSkip ||
            steps[this.currentStep()].validateForm()) {
            this.currentStep.set(index);
        }
    }
    isStepCompleteOrErrorEffect() {
        effect(() => {
            const validatedMap = this.steps().map((step) => step.validateForm());
            this.isStepComplete.set(validatedMap);
            const errorsMap = this.steps().map((step) => step.getErrors().length > 0);
            this.doesStepHaveErrors.set(errorsMap);
        }, { injector: this.injector });
    }
    requiredPerStepMapEffect() {
        effect(() => {
            if (this.steps()) {
                this.initRequiredPerStepMap();
            }
        }, { injector: this.injector });
    }
    initRequiredPerStepMap() {
        const requiredPerStepMap = this.steps().reduce((prev, step, i) => {
            const requiredPerStep = step.fields.filter((field) => field.validators?.some((validator) => validator.__meta
                ?.required));
            const stepStatus = requiredPerStep.map((step) => {
                if (!!step.error()) {
                    return StepStatus.Error;
                }
                if (!!step.value()) {
                    return StepStatus.Complete;
                }
                return StepStatus.InComplete;
            });
            return {
                ...prev,
                [i]: computed(() => stepStatus),
            };
        }, {});
        this.requiredPerStepMap.set(requiredPerStepMap);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormStepperNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormStepperNavComponent, isStandalone: true, selector: "signal-form-stepper-nav", inputs: { hasSaved: { classPropertyName: "hasSaved", publicName: "hasSaved", isSignal: true, isRequired: false, transformFunction: null }, steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, currentStep: { classPropertyName: "currentStep", publicName: "currentStep", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { currentStep: "currentStepChange" }, ngImport: i0, template: "<div class=\"stepper-nav-ui\">\n  <div class=\"stepper-track\">\n    @for (step of steps(); track $index) {\n      <div class=\"stepper-node\">\n        <button\n          class=\"step-circle\"\n          [class.completed]=\"isStepComplete()[$index]\"\n          [class.error]=\"doesStepHaveErrors()[$index]\"\n          [class.active]=\"$index === currentStep()\"\n          (click)=\"currentStep.set($index)\"\n        >\n          @if (isStepComplete()[$index]) {\n            \u2713\n          } @else {\n            {{ $index + 1 }}\n          }\n        </button>\n\n        <aside class=\"step-line-container\">\n          @for (stepsStatus of requiredPerStepMap?.()[$index](); track $index) {\n            <div\n              class=\"step-line\"\n              [class.filled]=\"isStepComplete()[$index]\"\n              [ngClass]=\"stepsStatus\"\n            ></div>\n          }\n        </aside>\n\n        @if ($last) {\n          <lucide-icon\n            [class.completed-form]=\"hasSaved()\"\n            class=\"finish-form\"\n            [img]=\"squareCheckIcon\"\n            [size]=\"28\"\n          />\n        }\n      </div>\n    }\n  </div>\n</div>\n", styles: [".stepper-nav-ui{display:flex;justify-content:center;margin-bottom:var(--signal-form-stepper-margin-bottom)}.stepper-nav-ui .stepper-track,.stepper-nav-ui .stepper-track .stepper-node{display:flex;align-items:center;gap:var(--signal-form-stepper-gap)}.stepper-nav-ui .stepper-track .stepper-node .step-circle{width:var(--signal-form-stepper-step-size);height:var(--signal-form-stepper-step-size);border-radius:var(--signal-form-stepper-step-border-radius);border:var(--signal-form-stepper-step-border-width) solid var(--signal-form-stepper-default-border);background-color:var(--signal-form-stepper-default-bg);display:flex;align-items:center;justify-content:center;font-weight:var(--signal-form-stepper-step-font-weight);transition:var(--signal-form-stepper-transition);cursor:pointer}.stepper-nav-ui .stepper-track .stepper-node .step-circle.active{border-color:var(--signal-form-stepper-active-border)!important}.stepper-nav-ui .stepper-track .stepper-node .step-circle.completed{background-color:var(--signal-form-stepper-completed-bg);color:var(--signal-form-stepper-completed-text);border-color:var(--signal-form-stepper-completed-border)}.stepper-nav-ui .stepper-track .stepper-node .step-circle.error{border-color:var(--signal-form-stepper-error-border);color:var(--signal-form-stepper-error-text)}.stepper-nav-ui .stepper-track .stepper-node .step-line{width:var(--signal-form-stepper-line-width);height:var(--signal-form-stepper-line-height);background:var(--signal-form-stepper-line-default)}.stepper-nav-ui .stepper-track .stepper-node .step-line-container{display:flex;gap:var(--signal-form-stepper-line-gap)}.stepper-nav-ui .stepper-track .stepper-node .step-line.complete{background:var(--signal-form-stepper-line-complete)}.stepper-nav-ui .stepper-track .stepper-node .step-line.error{background:var(--signal-form-stepper-line-error)}.stepper-nav-ui .stepper-track .stepper-node .finish-form{display:flex;justify-content:center;align-items:center}.stepper-nav-ui .stepper-track .stepper-node .finish-form.completed-form{color:var(--signal-form-stepper-completed-bg)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormStepperNavComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, LucideAngularModule], selector: 'signal-form-stepper-nav', standalone: true, template: "<div class=\"stepper-nav-ui\">\n  <div class=\"stepper-track\">\n    @for (step of steps(); track $index) {\n      <div class=\"stepper-node\">\n        <button\n          class=\"step-circle\"\n          [class.completed]=\"isStepComplete()[$index]\"\n          [class.error]=\"doesStepHaveErrors()[$index]\"\n          [class.active]=\"$index === currentStep()\"\n          (click)=\"currentStep.set($index)\"\n        >\n          @if (isStepComplete()[$index]) {\n            \u2713\n          } @else {\n            {{ $index + 1 }}\n          }\n        </button>\n\n        <aside class=\"step-line-container\">\n          @for (stepsStatus of requiredPerStepMap?.()[$index](); track $index) {\n            <div\n              class=\"step-line\"\n              [class.filled]=\"isStepComplete()[$index]\"\n              [ngClass]=\"stepsStatus\"\n            ></div>\n          }\n        </aside>\n\n        @if ($last) {\n          <lucide-icon\n            [class.completed-form]=\"hasSaved()\"\n            class=\"finish-form\"\n            [img]=\"squareCheckIcon\"\n            [size]=\"28\"\n          />\n        }\n      </div>\n    }\n  </div>\n</div>\n", styles: [".stepper-nav-ui{display:flex;justify-content:center;margin-bottom:var(--signal-form-stepper-margin-bottom)}.stepper-nav-ui .stepper-track,.stepper-nav-ui .stepper-track .stepper-node{display:flex;align-items:center;gap:var(--signal-form-stepper-gap)}.stepper-nav-ui .stepper-track .stepper-node .step-circle{width:var(--signal-form-stepper-step-size);height:var(--signal-form-stepper-step-size);border-radius:var(--signal-form-stepper-step-border-radius);border:var(--signal-form-stepper-step-border-width) solid var(--signal-form-stepper-default-border);background-color:var(--signal-form-stepper-default-bg);display:flex;align-items:center;justify-content:center;font-weight:var(--signal-form-stepper-step-font-weight);transition:var(--signal-form-stepper-transition);cursor:pointer}.stepper-nav-ui .stepper-track .stepper-node .step-circle.active{border-color:var(--signal-form-stepper-active-border)!important}.stepper-nav-ui .stepper-track .stepper-node .step-circle.completed{background-color:var(--signal-form-stepper-completed-bg);color:var(--signal-form-stepper-completed-text);border-color:var(--signal-form-stepper-completed-border)}.stepper-nav-ui .stepper-track .stepper-node .step-circle.error{border-color:var(--signal-form-stepper-error-border);color:var(--signal-form-stepper-error-text)}.stepper-nav-ui .stepper-track .stepper-node .step-line{width:var(--signal-form-stepper-line-width);height:var(--signal-form-stepper-line-height);background:var(--signal-form-stepper-line-default)}.stepper-nav-ui .stepper-track .stepper-node .step-line-container{display:flex;gap:var(--signal-form-stepper-line-gap)}.stepper-nav-ui .stepper-track .stepper-node .step-line.complete{background:var(--signal-form-stepper-line-complete)}.stepper-nav-ui .stepper-track .stepper-node .step-line.error{background:var(--signal-form-stepper-line-error)}.stepper-nav-ui .stepper-track .stepper-node .finish-form{display:flex;justify-content:center;align-items:center}.stepper-nav-ui .stepper-track .stepper-node .finish-form.completed-form{color:var(--signal-form-stepper-completed-bg)}\n"] }]
        }], ctorParameters: () => [] });

class SignalFormStepperComponent {
    form = input.required();
    onSave = output();
    afterSaveCompletes = output();
    saveDisabled = computed(() => !(this.form().anyDirty() && this.form().anyTouched()));
    submitButtonText = computed(() => {
        const status = this.form().status();
        switch (status) {
            case FormStatus.Submitting:
                return 'Saving...';
            case FormStatus.Success:
                return 'Saved successfully';
            default:
                return 'Submit';
        }
    });
    submitButtonClass = computed(() => {
        const status = this.form().status();
        const classes = ['form-button'];
        if (status === FormStatus.Submitting) {
            classes.push('saving');
        }
        else if (status === FormStatus.Success) {
            classes.push('success');
        }
        else if (this.form().hasSaved()) {
            classes.push('saved');
        }
        return classes.join(' ');
    });
    constructor() {
        effect(() => {
            if (this.form().hasSaved()) {
                this.afterSaveCompletes.emit();
            }
        });
    }
    next() {
        if (this.form().validateStep() &&
            this.form().currentStep() < this.form().steps.length - 1) {
            this.form().currentStep.set(this.form().currentStep() + 1);
        }
    }
    previous() {
        this.form().currentStep.set(this.form().currentStep() - 1);
    }
    save() {
        this.form().save();
        this.onSave.emit(this.form().value());
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormStepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SignalFormStepperComponent, isStandalone: true, selector: "signal-form-stepper", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onSave: "onSave", afterSaveCompletes: "afterSaveCompletes" }, host: { properties: { "class.signal-form-container": "true" } }, ngImport: i0, template: "@if (form(); as form) {\n  <form onsubmit=\"event.preventDefault();\">\n    <signal-form-stepper-nav\n      [steps]=\"form.steps\"\n      [hasSaved]=\"form.hasSaved()\"\n      [(currentStep)]=\"form.currentStep\"\n    />\n\n    <div class=\"stepper-body\">\n      @for (step of form.steps; track $index) {\n        <div [hidden]=\"$index !== form.currentStep()\">\n          @defer (on viewport; prefetch on idle) {\n            <signal-form-fields\n              [form]=\"step\"\n              [fields]=\"step.fields\"\n              #formFields\n            />\n          } @placeholder {\n            <div></div>\n          }\n        </div>\n      }\n    </div>\n\n    <signal-form-error-summary [form]=\"form\" />\n\n    <div class=\"stepper-actions\">\n      <button\n        class=\"form-button\"\n        (click)=\"previous()\"\n        [disabled]=\"form.currentStep() === 0\"\n      >\n        Previous\n      </button>\n      @if (form.currentStep() < form.steps.length - 1) {\n        <button\n          class=\"form-button\"\n          (click)=\"next()\"\n          [disabled]=\"!form.isValidStep()\"\n        >\n          Next\n        </button>\n      }\n      @if (form.currentStep() === form.steps.length - 1) {\n        <button\n          [class]=\"submitButtonClass()\"\n          [disabled]=\"!!form.getErrors().length\"\n          (click)=\"save()\"\n        >\n          {{ submitButtonText() }}\n        </button>\n      }\n    </div>\n  </form>\n}\n", styles: [".stepper-actions{display:flex;gap:.5rem}.stepper-body{margin-bottom:1rem}\n"], dependencies: [{ kind: "component", type: SignalFormStepperNavComponent, selector: "signal-form-stepper-nav", inputs: ["hasSaved", "steps", "currentStep"], outputs: ["currentStepChange"] }, { kind: "component", type: SignalFormErrorSummaryComponent, selector: "signal-form-error-summary", inputs: ["form"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [Promise.resolve().then(function () { return signalFormFields_component; }).then(m => m.SignalFormFieldsComponent)]] });
}
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "19.2.14", ngImport: i0, type: SignalFormStepperComponent, resolveDeferredDeps: () => [Promise.resolve().then(function () { return signalFormFields_component; }).then(m => m.SignalFormFieldsComponent)], resolveMetadata: SignalFormFieldsComponent => ({ decorators: [{
                type: Component,
                args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form-stepper', imports: [
                            SignalFormFieldsComponent,
                            SignalFormStepperNavComponent,
                            SignalFormErrorSummaryComponent,
                        ], host: {
                            '[class.signal-form-container]': 'true',
                        }, template: "@if (form(); as form) {\n  <form onsubmit=\"event.preventDefault();\">\n    <signal-form-stepper-nav\n      [steps]=\"form.steps\"\n      [hasSaved]=\"form.hasSaved()\"\n      [(currentStep)]=\"form.currentStep\"\n    />\n\n    <div class=\"stepper-body\">\n      @for (step of form.steps; track $index) {\n        <div [hidden]=\"$index !== form.currentStep()\">\n          @defer (on viewport; prefetch on idle) {\n            <signal-form-fields\n              [form]=\"step\"\n              [fields]=\"step.fields\"\n              #formFields\n            />\n          } @placeholder {\n            <div></div>\n          }\n        </div>\n      }\n    </div>\n\n    <signal-form-error-summary [form]=\"form\" />\n\n    <div class=\"stepper-actions\">\n      <button\n        class=\"form-button\"\n        (click)=\"previous()\"\n        [disabled]=\"form.currentStep() === 0\"\n      >\n        Previous\n      </button>\n      @if (form.currentStep() < form.steps.length - 1) {\n        <button\n          class=\"form-button\"\n          (click)=\"next()\"\n          [disabled]=\"!form.isValidStep()\"\n        >\n          Next\n        </button>\n      }\n      @if (form.currentStep() === form.steps.length - 1) {\n        <button\n          [class]=\"submitButtonClass()\"\n          [disabled]=\"!!form.getErrors().length\"\n          (click)=\"save()\"\n        >\n          {{ submitButtonText() }}\n        </button>\n      }\n    </div>\n  </form>\n}\n", styles: [".stepper-actions{display:flex;gap:.5rem}.stepper-body{margin-bottom:1rem}\n"] }]
            }], ctorParameters: () => [], propDecorators: null }) });

class SignalFormComponent {
    form = input.required();
    submitButtonText = input('Save');
    formSubmit = output();
    disabled = computed(() => this.form().saveButtonDisabled());
    buttonText = computed(() => {
        const status = this.form().status();
        switch (status) {
            case FormStatus.Submitting:
                return 'Saving...';
            case FormStatus.Success:
                return 'Saved successfully';
            default:
                return this.submitButtonText();
        }
    });
    buttonClass = computed(() => {
        const status = this.form().status();
        const classes = ['form-button'];
        if (status === FormStatus.Submitting) {
            classes.push('saving');
        }
        else if (status === FormStatus.Success) {
            classes.push('success');
        }
        else if (this.form().hasSaved()) {
            classes.push('saved');
        }
        return classes.join(' ');
    });
    hasUnsavedChanges() {
        return this.form().anyDirty();
    }
    onBeforeUnload(event) {
        if (this.hasUnsavedChanges()) {
            event.preventDefault();
        }
    }
    submitForm() {
        const isValid = this.form().validateForm();
        if (isValid) {
            this.form().save();
            this.formSubmit.emit(this.form());
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: SignalFormComponent, isStandalone: true, selector: "signal-form", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, submitButtonText: { classPropertyName: "submitButtonText", publicName: "submitButtonText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formSubmit: "formSubmit" }, host: { listeners: { "window:beforeunload": "onBeforeUnload($event)" } }, ngImport: i0, template: "<div class=\"signal-form-wrapper\">\n  <h2 class=\"signal-form-title\">{{ form().title }}</h2>\n  <form\n    (submit)=\"submitForm(); $event.preventDefault()\"\n    class=\"signal-form-content\"\n  >\n    <div class=\"form-fields-container\">\n      <signal-form-fields\n        [fields]=\"form().fields\"\n        [form]=\"form()\"\n        [signalFormParent]=\"true\"\n      />\n    </div>\n\n    <div class=\"form-bottom-sticky\">\n      <signal-form-error-summary [form]=\"form()\" />\n      <button type=\"submit\" [disabled]=\"disabled()\" [class]=\"buttonClass()\">\n        {{ buttonText() }}\n      </button>\n    </div>\n  </form>\n</div>\n", styles: [".signal-form-wrapper{display:flex;flex-direction:column;min-height:var(--signal-form-min-height);max-height:var(--signal-form-max-height);height:100%;width:100%;max-width:var(--signal-form-max-width);background:var(--signal-form-bg);border-radius:var(--signal-form-border-radius-md);box-shadow:var(--signal-form-shadow-lg);overflow:hidden}.signal-form-wrapper.full-page{height:var(--signal-form-min-height)}.signal-form-title{font-size:var(--signal-form-title-font-size);font-weight:var(--signal-form-title-font-weight);margin:var(--signal-form-title-margin);padding:var(--signal-form-title-padding);color:var(--signal-form-title-color)}.signal-form-content{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.form-fields-container{flex:1;overflow-y:auto;padding:var(--signal-form-padding);display:flex;flex-direction:column;gap:var(--signal-form-fields-gap);content-visibility:auto;contain-intrinsic-size:0 500px}.form-bottom-sticky{--sticky-footer-height: var(--sticky-footer-height);position:sticky;bottom:0;width:100%;min-height:var(--sticky-footer-height);background:var(--signal-form-bg);border-top:1px solid var(--signal-form-border-top-color);padding:var(--signal-form-padding);box-shadow:var(--signal-form-shadow-top);display:flex;flex-direction:column;gap:var(--signal-form-bottom-gap)}.form-button{width:100%;padding:var(--signal-form-button-padding-full);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border:none;border-radius:var(--signal-form-border-radius-md);font-weight:var(--signal-form-button-font-weight);cursor:pointer;transition:var(--signal-form-transition-bg)}.form-button:hover:not(:disabled){background:var(--signal-form-button-primary-bg-hover)}.form-button:disabled{background:var(--signal-form-button-disabled-bg);cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: SignalFormFieldsComponent, selector: "signal-form-fields", inputs: ["fields", "form", "index", "isRoot", "signalFormParent"] }, { kind: "component", type: SignalFormErrorSummaryComponent, selector: "signal-form-error-summary", inputs: ["form"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SignalFormComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'signal-form', imports: [SignalFormFieldsComponent, SignalFormErrorSummaryComponent], standalone: true, template: "<div class=\"signal-form-wrapper\">\n  <h2 class=\"signal-form-title\">{{ form().title }}</h2>\n  <form\n    (submit)=\"submitForm(); $event.preventDefault()\"\n    class=\"signal-form-content\"\n  >\n    <div class=\"form-fields-container\">\n      <signal-form-fields\n        [fields]=\"form().fields\"\n        [form]=\"form()\"\n        [signalFormParent]=\"true\"\n      />\n    </div>\n\n    <div class=\"form-bottom-sticky\">\n      <signal-form-error-summary [form]=\"form()\" />\n      <button type=\"submit\" [disabled]=\"disabled()\" [class]=\"buttonClass()\">\n        {{ buttonText() }}\n      </button>\n    </div>\n  </form>\n</div>\n", styles: [".signal-form-wrapper{display:flex;flex-direction:column;min-height:var(--signal-form-min-height);max-height:var(--signal-form-max-height);height:100%;width:100%;max-width:var(--signal-form-max-width);background:var(--signal-form-bg);border-radius:var(--signal-form-border-radius-md);box-shadow:var(--signal-form-shadow-lg);overflow:hidden}.signal-form-wrapper.full-page{height:var(--signal-form-min-height)}.signal-form-title{font-size:var(--signal-form-title-font-size);font-weight:var(--signal-form-title-font-weight);margin:var(--signal-form-title-margin);padding:var(--signal-form-title-padding);color:var(--signal-form-title-color)}.signal-form-content{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.form-fields-container{flex:1;overflow-y:auto;padding:var(--signal-form-padding);display:flex;flex-direction:column;gap:var(--signal-form-fields-gap);content-visibility:auto;contain-intrinsic-size:0 500px}.form-bottom-sticky{--sticky-footer-height: var(--sticky-footer-height);position:sticky;bottom:0;width:100%;min-height:var(--sticky-footer-height);background:var(--signal-form-bg);border-top:1px solid var(--signal-form-border-top-color);padding:var(--signal-form-padding);box-shadow:var(--signal-form-shadow-top);display:flex;flex-direction:column;gap:var(--signal-form-bottom-gap)}.form-button{width:100%;padding:var(--signal-form-button-padding-full);background:var(--signal-form-button-primary-bg);color:var(--signal-form-button-text);border:none;border-radius:var(--signal-form-border-radius-md);font-weight:var(--signal-form-button-font-weight);cursor:pointer;transition:var(--signal-form-transition-bg)}.form-button:hover:not(:disabled){background:var(--signal-form-button-primary-bg-hover)}.form-button:disabled{background:var(--signal-form-button-disabled-bg);cursor:not-allowed}\n"] }]
        }], propDecorators: { onBeforeUnload: [{
                type: HostListener,
                args: ['window:beforeunload', ['$event']]
            }] } });

/*
 * Public API Surface of signal-template-forms
 */
// Core functionality

/**
 * Generated bundle index. Do not edit.
 */

export { BaseInputDirective, CollapsableSectionComponent, ConversionUtils, FieldFactory, FieldRoleAttributesService, FieldUtils, FormDropdownOverlayComponent, FormDropdownService, FormFieldSkeletonComponent, FormFieldSkeletonMapperComponent, FormFieldType, FormStatus, NumberInputType, SIGNAL_FORMS_THEME_CONFIG, SignalFormAutocompleteFieldComponent, SignalFormBuilder, SignalFormCheckboxFieldComponent, SignalFormCheckboxGroupFieldComponent, SignalFormChipListFieldComponent, SignalFormColorFieldComponent, SignalFormComponent, SignalFormDatetimeFieldComponent, SignalFormErrorSummaryComponent, SignalFormFieldsComponent, SignalFormFileFieldComponent, SignalFormHostDirective, SignalFormInputItemComponent, SignalFormMultiselectFieldComponent, SignalFormNumberFieldComponent, SignalFormPasswordFieldComponent, SignalFormRadioFieldComponent, SignalFormRatingFieldComponent, SignalFormRepeatableFieldComponent, SignalFormSaveButtonComponent, SignalFormSelectFieldComponent, SignalFormSliderFieldComponent, SignalFormStepperComponent, SignalFormStepperNavComponent, SignalFormSwitchFieldComponent, SignalFormTextFieldComponent, SignalFormTextareaFieldComponent, SignalFormThemeService, SignalModelDirective, SignalValidators, ValidationService, WordCountComponent, WordCountService, isRequired, provideSignalFormsTheme, unsavedChangesGuard, withMeta, withSignalValidation };
//# sourceMappingURL=signal-template-forms.mjs.map