UNPKG

@sixbell-telco/sdk

Version:

A collection of reusable components designed for use in Sixbell Telco Angular projects

1,056 lines 57.1 kB
import { CdkListbox, CdkOption } from '@angular/cdk/listbox';
import * as i1 from '@angular/cdk/overlay';
import { OverlayModule } from '@angular/cdk/overlay';
import { PortalModule } from '@angular/cdk/portal';
import * as i0 from '@angular/core';
import { inject, input, model, output, signal, viewChild, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
import { toObservable, toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { TranslateService, TranslatePipe } from '@ngx-translate/core';
import { cn } from '@sixbell-telco/sdk/utils/cn';
import { cva } from 'class-variance-authority';
import { BadgeComponent } from '@sixbell-telco/sdk/components/badge';
import { IconComponent } from '@sixbell-telco/sdk/components/icon';
import { matSearchOutline, matKeyboardArrowDownOutline, matCheckOutline, matCloseOutline } from '@sixbell-telco/sdk/components/icon/material/outline';
import { Subject, switchMap, startWith, debounceTime, distinctUntilChanged } from 'rxjs';

/**
 * @fileoverview Combobox Component
 *
 * A comprehensive combobox component with advanced features including:
 * - Single and multiple selection modes
 * - Real-time search with debouncing
 * - Keyboard navigation support (Arrow keys, Enter, Space)
 * - Form validation integration
 * - Accessibility compliance with CDK Listbox
 * - Customizable styling variants and sizes
 * - Animation states for smooth transitions
 */
/* eslint-disable @typescript-eslint/no-explicit-any */
// ==============================================================================
// IMPORTS
// ==============================================================================
// Angular CDK
// ==============================================================================
// COMPONENT STYLES
// ==============================================================================
/**
 * Class variance authority configuration for combobox styling
 */
const comboboxComponent = cva(['w-full', 'min-w-fit', 'text-pretty', 'font-body', 'font-normal', 'select', 'leading-normal', 'bg-none'], {
    variants: {
        variant: {
            primary: ['select-primary'],
            secondary: ['select-secondary'],
            tertiary: ['select-tertiary'],
            accent: ['select-accent'],
            info: ['select-info'],
            success: ['select-success'],
            error: ['select-error'],
            warning: ['select-warning'],
        },
        size: {
            xs: ['select-xs'],
            sm: ['select-sm'],
            md: ['select-md'],
            lg: ['select-lg'],
            xl: ['select-xl'],
        },
    },
    compoundVariants: [],
    defaultVariants: {
        variant: 'secondary',
        size: 'md',
    },
});
// ==============================================================================
// COMPONENT
// ==============================================================================
/**
 * A customizable combobox component with search functionality, multiple selection modes,
 * and keyboard navigation support. Implements Angular's ControlValueAccessor interface
 * for seamless integration with reactive forms.
 *
 * @example
 * ```html
 * <st-combobox
 *   [(value)]="selectedValue"
 *   [options]="availableOptions"
 *   displayKey="name"
 *   valueKey="id"
 *   placeholder="Select an option"
 *   variant="primary"
 *   size="md">
 * </st-combobox>
 * ```
 *
 * @example
 * ```typescript
 * // Multiple selection
 * <st-combobox
 *   [(value)]="selectedValues"
 *   [options]="options"
 *   selectionType="multiple"
 *   [allowClear]="true">
 * </st-combobox>
 * ```
 */
class ComboboxComponent {
    // ==============================================================================
    // SERVICES
    // ==============================================================================
    /**
     * Angular translation service for internationalization
     * @internal
     */
    translateService = inject(TranslateService);
    // ==============================================================================
    // INPUT PROPERTIES
    // ==============================================================================
    /**
     * The visual style variant of the combobox
     * @default 'secondary'
     */
    variant = input('secondary');
    /**
     * The size variant of the combobox
     * @default 'md'
     */
    size = input('md');
    /**
     * Whether to use ghost (transparent) styling
     * @default false
     */
    ghost = input(false);
    /**
     * The HTML name attribute for the underlying form control
     */
    name = input(null);
    /**
     * Placeholder text displayed when no option is selected
     */
    placeholder = input('');
    /**
     * Whether the combobox is disabled
     * @default false
     */
    disabled = model(false);
    /**
     * Label text displayed above the combobox
     */
    label = input('');
    /**
     * Parent form group for reactive forms integration
     */
    parentForm = input(null);
    /**
     * The form control name when used within a reactive form
     */
    formControlName = input('');
    /**
     * Whether to show a clear button next to the selected value(s) in the trigger
     * Works for both single and multiple selection modes
     * @default true
     */
    allowClear = input(true);
    /**
     * Whether to show a "Clear All" button at the bottom of the dropdown for multiple selection
     * Only applies when selectionType is 'multiple'
     * @default true
     */
    allowClearAll = input(true);
    /**
     * Placeholder text for the search input field
     * @default 'Search...'
     */
    searchPlaceholder = input('');
    /**
     * Text displayed when no search results are found
     * @default 'No results found'
     */
    noResultsText = input('');
    /**
     * The current selected value(s)
     * For single selection: any | null
     * For multiple selection: any[]
     */
    value = model(null);
    /**
     * Array of available options to select from
     */
    options = input([]);
    /**
     * The object property to use for display text when options are objects
     * Falls back to common property names if not specified
     */
    displayKey = input('');
    /**
     * The object property to use as the value when options are objects
     * Uses the entire object if not specified
     */
    valueKey = input('');
    /**
     * Selection mode: 'single' for single selection, 'multiple' for multi-select
     * @default 'single'
     */
    selectionType = input('single');
    /**
     * Whether to enable debounced search functionality
     * @default false
     */
    searchDebounced = input(false);
    /**
     * Debounce time in milliseconds for search input
     * @default 300
     */
    searchDebounceTime = input(300);
    /**
     * Event emitted when combobox loses focus
     */
    blurred = output();
    /**
     * Event emitted when selection changes
     */
    valueUpdated = output();
    /**
     * Event emitted when search query changes with filtered results
     */
    searchQuery = output();
    // ==============================================================================
    // ICONS
    // ==============================================================================
    /** Search icon for the search input */
    iconSearch = matSearchOutline;
    /** Dropdown chevron icon */
    iconChevronDown = matKeyboardArrowDownOutline;
    /** Check icon for selected options */
    iconCheck = matCheckOutline;
    /** Close/clear icon */
    iconClose = matCloseOutline;
    // ==============================================================================
    // PRIVATE PROPERTIES
    // ==============================================================================
    /** Internal search input value */
    searchValue = signal('');
    /** Processed search term after debouncing */
    searchTerm = signal('');
    /** RxJS subject for search input handling */
    searchSubject = new Subject();
    /** Current animation state of the overlay */
    animationState = signal('closed');
    /** Trigger for blur events in form validation */
    blurTrigger = signal(0);
    /** Active option index for keyboard navigation */
    activeOptionIndex = signal(-1);
    // ==============================================================================
    // VIEW CHILDREN
    // ==============================================================================
    /** Reference to the trigger button element */
    triggerRef = viewChild('trigger');
    /** Reference to the search input element */
    searchInputRef = viewChild('searchInput');
    /** Reference to the options list container */
    optionsList = viewChild('optionsList');
    /** Reference to the CDK listbox for keyboard navigation */
    listbox = viewChild('listbox');
    // ==============================================================================
    // SIGNALS
    // ==============================================================================
    /** Width of the trigger button for overlay positioning */
    triggerWidth = signal(0);
    // ==============================================================================
    // FORM CONTROL INTEGRATION
    // ==============================================================================
    /** Form control reference for reactive forms */
    formControl = computed(() => this.parentForm()?.get(this.formControlName()));
    /** Observable stream of form control */
    formControl$ = toObservable(this.formControl);
    /** Stream of form control status changes */
    statusChanges$ = this.formControl$.pipe(switchMap((control) => control?.statusChanges.pipe(startWith(control.status)) || []));
    /** Stream of form control value changes */
    stateChanges$ = this.formControl$.pipe(switchMap((control) => control?.valueChanges.pipe(startWith(control.value)) || []));
    /** Signal for form control status */
    statusSignal = toSignal(this.statusChanges$);
    /** Signal for form control state */
    stateSignal = toSignal(this.stateChanges$);
    // ==============================================================================
    // CONTROL VALUE ACCESSOR
    // ==============================================================================
    /** Callback function for value changes */
    onChange = (value) => { };
    /** Callback function for touch events */
    onTouched = () => { };
    // ==============================================================================
    // CONSTRUCTOR
    // ==============================================================================
    constructor() {
        this.setupSearchHandling();
        this.setupSearchQueryEmission();
    }
    // ==============================================================================
    // COMPUTED PROPERTIES
    // ==============================================================================
    /**
     * Filtered options based on current search term
     * @returns Array of options that match the search criteria
     */
    filteredOptions = computed(() => {
        const options = this.options();
        const search = this.searchTerm().toLowerCase().trim();
        if (!search)
            return options;
        return options.filter((option) => {
            const displayValue = this.getDisplayValue(option).toLowerCase();
            return displayValue.includes(search);
        });
    });
    /**
     * Whether the overlay is currently open or in the process of opening/closing
     * @returns True if overlay is visible or animating
     */
    isOpen = computed(() => {
        const state = this.animationState();
        return state === 'opening' || state === 'open' || state === 'closing';
    });
    /**
     * Whether the overlay is currently animating to open state
     * @returns True if overlay is opening or open
     */
    isAnimatingOpen = computed(() => {
        const state = this.animationState();
        return state === 'opening' || state === 'open';
    });
    /**
     * Current animation state for template binding
     * @returns Current animation state
     */
    dataState = computed(() => this.animationState());
    /**
     * Display value for the selected option(s)
     * @returns Formatted display string based on selection type and current value
     */
    displayValue = computed(() => {
        const selectionType = this.selectionType();
        const currentValue = this.value();
        if (selectionType === 'multiple') {
            const selected = Array.isArray(currentValue) ? currentValue : [];
            if (selected.length === 0)
                return this.translatedPlaceholder();
            // Validate that all selected values exist in options
            const validSelected = selected.filter((value) => this.isValidValue(value));
            if (validSelected.length === 0)
                return this.translatedPlaceholder();
            if (validSelected.length === 1)
                return this.getDisplayValue(validSelected[0]);
            return `${validSelected.length} items selected`;
        }
        else {
            // For single selection, only show display value if it's valid
            if (!currentValue || !this.isValidValue(currentValue)) {
                return this.translatedPlaceholder();
            }
            return this.getDisplayValue(currentValue);
        }
    });
    /**
     * Display information for multiple selection showing first item and additional count
     * @returns Object with first item display and additional count, or null for single selection
     */
    multipleDisplayInfo = computed(() => {
        const selectionType = this.selectionType();
        const currentValue = this.value();
        if (selectionType === 'multiple') {
            const selected = Array.isArray(currentValue) ? currentValue : [];
            if (selected.length === 0)
                return null;
            if (selected.length === 1)
                return { firstItem: this.getDisplayValue(selected[0]), additionalCount: 0 };
            return {
                firstItem: this.getDisplayValue(selected[0]),
                additionalCount: selected.length - 1,
            };
        }
        return null;
    });
    /**
     * Whether the clear button should be shown
     * @returns True if there's a valid selected value and clear is allowed
     */
    showClearButton = computed(() => {
        if (!this.allowClear() || this.disabled()) {
            return false;
        }
        const currentValue = this.value();
        if (!currentValue)
            return false;
        if (this.selectionType() === 'multiple') {
            // For multiple selection, check if we have any valid values
            const selected = Array.isArray(currentValue) ? currentValue : [];
            return selected.some((value) => this.isValidValue(value));
        }
        else {
            // For single selection, check if the value is valid
            return this.isValidValue(currentValue);
        }
    });
    /**
     * Base CSS classes for the combobox component
     * @internal
     */
    componentClass = computed(() => {
        return cn(comboboxComponent({
            variant: this.variant(),
            size: this.size(),
        }), { 'select-ghost': this.ghost() });
    });
    /**
     * Error state CSS classes
     * @internal
     */
    errorClass = computed(() => {
        return cn(comboboxComponent({
            variant: 'error',
            size: this.size(),
        }), { 'select-ghost': this.ghost() });
    });
    /**
     * Success state CSS classes
     * @internal
     */
    successClass = computed(() => {
        return cn(comboboxComponent({
            variant: 'success',
            size: this.size(),
        }), { 'select-ghost': this.ghost() });
    });
    /**
     * Validation-aware CSS classes based on form control state
     * @internal
     */
    validationClass = computed(() => {
        const control = this.formControl();
        const trigger = this.blurTrigger();
        if (!control)
            return this.componentClass();
        const isTouched = control.touched || trigger > 0;
        this.statusSignal();
        this.stateSignal();
        if (control.dirty || isTouched) {
            if (control.invalid)
                return this.errorClass();
            if (control.valid)
                return this.successClass();
        }
        return this.componentClass();
    });
    /**
     * CSS classes for the search input field
     * @internal
     */
    searchInputClass = computed(() => {
        return cn('input bg-base-200 w-full border-transparent focus-within:outline-none focus:border-transparent focus:outline-none', {
            'input-xs': this.size() === 'xs',
            'input-sm': this.size() === 'sm',
            'input-md': this.size() === 'md',
            'input-lg': this.size() === 'lg',
            'input-xl': this.size() === 'xl',
        });
    });
    /**
     * CSS classes for the options menu
     * @internal
     */
    menuClass = computed(() => {
        return cn('menu flex-nowrap max-h-72 overflow-y-auto overscroll-contain w-full p-0 focus-within:outline-none focus:outline-none focus-visible:outline-none', {
            'menu-xs': this.size() === 'xs',
            'menu-sm': this.size() === 'sm',
            'menu-md': this.size() === 'md',
            'menu-lg': this.size() === 'lg',
            'menu-xl': this.size() === 'xl',
        });
    });
    /**
     * CSS classes for the badge
     * @internal
     */
    badgeClass = computed(() => {
        const size = this.size();
        switch (size) {
            case 'xs':
            case 'sm':
                return 'xs';
            case 'md':
                return 'sm';
            case 'lg':
                return 'md';
            case 'xl':
                return 'lg';
            default:
                return 'sm';
        }
    });
    /**
     * Translated search placeholder text
     * @returns Translated placeholder or input value
     */
    translatedSearchPlaceholder = computed(() => {
        const customPlaceholder = this.searchPlaceholder();
        return customPlaceholder || this.translateService.instant('sdk.combobox.searchPlaceholder');
    });
    /**
     * Translated main placeholder text
     * @returns Translated placeholder or input value
     */
    translatedPlaceholder = computed(() => {
        const customPlaceholder = this.placeholder();
        return customPlaceholder || this.translateService.instant('sdk.combobox.placeholder');
    });
    /**
     * Translated no results text
     * @returns Translated no results text or input value
     */
    translatedNoResultsText = computed(() => {
        const customNoResults = this.noResultsText();
        return customNoResults || this.translateService.instant('sdk.combobox.noResultsFound');
    });
    /**
     * Current search input value for template binding
     * @returns Current search value
     */
    get currentSearchValue() {
        return this.searchValue();
    }
    // ==============================================================================
    // LIFECYCLE METHODS
    // ==============================================================================
    /**
     * Angular lifecycle hook called after view initialization
     * Updates the trigger width for proper overlay positioning
     */
    ngAfterViewInit() {
        this.updateTriggerWidth();
    }
    // ==============================================================================
    // CONTROL VALUE ACCESSOR IMPLEMENTATION
    // ==============================================================================
    /**
     * Writes a new value to the component
     * @param value - The new value to set
     */
    writeValue(value) {
        // Only set the value if it's null/undefined or if it's valid
        if (!value) {
            this.value.set(value);
            return;
        }
        if (this.selectionType() === 'multiple') {
            // For multiple selection, filter out invalid values
            if (Array.isArray(value)) {
                const validValues = value.filter((v) => this.isValidValue(v));
                this.value.set(validValues.length > 0 ? validValues : []);
            }
            else {
                this.value.set([]);
            }
        }
        else {
            // For single selection, only set if valid
            const isValid = this.isValidValue(value);
            this.value.set(isValid ? value : null);
        }
    }
    /**
     * Registers a callback function to be called when the value changes
     * @param fn - The callback function
     */
    registerOnChange(fn) {
        this.onChange = fn;
    }
    /**
     * Registers a callback function to be called when the component is touched
     * @param fn - The callback function
     */
    registerOnTouched(fn) {
        this.onTouched = fn;
    }
    // ==============================================================================
    // PUBLIC METHODS
    // ==============================================================================
    /**
     * Toggles the overlay open/closed state
     * Handles focus management and animation state transitions
     */
    toggleOverlay() {
        const currentState = this.animationState();
        if (currentState === 'closed') {
            this.updateTriggerWidth();
            this.animationState.set('opening');
            // Focus search input after opening
            setTimeout(() => {
                this.searchInputRef()?.nativeElement?.focus();
                // Set first option as active by default
                this.setDefaultActiveOption();
            }, 100);
        }
        else if (currentState === 'open') {
            this.animationState.set('closing');
        }
    }
    /**
     * Handles animation end events to update state
     * @param event - The animation event
     */
    onAnimationEnd(event) {
        if (event.animationName === 'fade-in-up') {
            this.animationState.set('open');
        }
        else if (event.animationName === 'fade-out-down') {
            this.animationState.set('closed');
            this.clearSearch();
            this.triggerBlur();
            // Return focus to trigger button for better keyboard navigation
            setTimeout(() => {
                this.triggerRef()?.nativeElement?.focus();
            }, 0);
        }
    }
    /**
     * Handles overlay detachment
     * Cleans up state and triggers blur
     */
    handleDetach() {
        this.animationState.set('closed');
        this.clearSearch();
        this.triggerBlur();
    }
    /**
     * Handles clicks outside the component
     * @param event - The mouse event
     */
    handleClickOutside(event) {
        if (this.animationState() === 'open') {
            this.animationState.set('closing');
        }
        event.stopPropagation();
        this.triggerBlur();
    }
    /**
     * Handles search input changes
     * @param event - The input event
     */
    handleSearchInput(event) {
        const target = event.target;
        const searchValue = target.value;
        this.searchValue.set(searchValue);
        if (this.searchDebounced()) {
            this.searchSubject.next(searchValue);
        }
        else {
            this.searchTerm.set(searchValue);
            // Set first option as active after search
            setTimeout(() => this.setDefaultActiveOption(), 0);
        }
    }
    /**
     * Handles option selection
     * @param option - The selected option
     */
    handleOptionSelect(option) {
        const selectionType = this.selectionType();
        const currentValue = this.value();
        if (selectionType === 'multiple') {
            const currentValues = Array.isArray(currentValue) ? currentValue : [];
            const optionValue = this.getOptionValue(option);
            const isSelected = this.isOptionSelected(option);
            let newValues;
            if (isSelected) {
                newValues = currentValues.filter((value) => this.getOptionValue(value) !== optionValue);
            }
            else {
                newValues = [...currentValues, option];
            }
            this.value.set(newValues);
            this.onChange(newValues);
            this.valueUpdated.emit(newValues);
        }
        else {
            this.value.set(option);
            this.onChange(option);
            this.valueUpdated.emit(option);
            this.animationState.set('closing');
        }
        this.onTouched();
    }
    /**
     * Handles CDK listbox selection changes
     * @param event - The selection change event
     */
    handleSelectionChange(event) {
        if (event.option) {
            this.handleOptionSelect(event.option.value);
        }
    }
    /**
     * Handles keyboard events for option selection
     * @param event - The keyboard event
     */
    handleListboxKeydown(event) {
        if (event.key === 'Enter' || event.key === ' ') {
            event.preventDefault();
            event.stopPropagation();
            const focusedOption = this.optionsList()?.nativeElement.querySelector('.cdk-option-active');
            if (focusedOption) {
                const optionIndex = parseInt(focusedOption.getAttribute('data-option-index') || '0');
                const filteredOptions = this.filteredOptions();
                if (optionIndex >= 0 && optionIndex < filteredOptions.length) {
                    this.handleOptionSelect(filteredOptions[optionIndex]);
                }
            }
        }
    }
    /**
     * Handles keyboard events on the search input for navigation and selection
     * @param event - The keyboard event
     */
    handleSearchKeydown(event) {
        const filteredOptions = this.filteredOptions();
        const currentIndex = this.activeOptionIndex();
        switch (event.key) {
            case 'ArrowDown': {
                event.preventDefault();
                event.stopPropagation();
                const nextIndex = currentIndex < filteredOptions.length - 1 ? currentIndex + 1 : 0;
                this.setActiveOption(nextIndex);
                break;
            }
            case 'ArrowUp': {
                event.preventDefault();
                event.stopPropagation();
                const prevIndex = currentIndex > 0 ? currentIndex - 1 : filteredOptions.length - 1;
                this.setActiveOption(prevIndex);
                break;
            }
            case 'Enter':
                event.preventDefault();
                event.stopPropagation();
                if (currentIndex >= 0 && currentIndex < filteredOptions.length) {
                    this.handleOptionSelect(filteredOptions[currentIndex]);
                }
                break;
            case 'Tab': {
                // Allow Tab only to clear button in multiple selection mode
                const isMultipleSelection = this.selectionType() === 'multiple';
                const hasSelectedValues = this.value() && this.isArray(this.value()) && this.value().length > 0;
                if (isMultipleSelection && hasSelectedValues) {
                    // Allow default Tab behavior to go to clear button
                    break;
                }
                else {
                    // Prevent tabbing out of the search input
                    event.preventDefault();
                    event.stopPropagation();
                }
                break;
            }
            case 'Escape':
                event.preventDefault();
                event.stopPropagation();
                this.animationState.set('closing');
                this.activeOptionIndex.set(-1);
                break;
            default:
                // For other keys (including space), set first option as active for typing
                setTimeout(() => this.setDefaultActiveOption(), 0);
                break;
        }
    }
    /**
     * Handles keyboard events on the clear all button
     * @param event - The keyboard event
     */
    handleClearButtonKeydown(event) {
        if (event.key === 'Tab') {
            // Prevent default Tab behavior and focus back to search input
            event.preventDefault();
            event.stopPropagation();
            this.searchInputRef()?.nativeElement?.focus();
        }
    }
    /**
     * Handles keyboard events on the trigger button
     * @param event - The keyboard event
     */
    handleTriggerKeydown(event) {
        switch (event.key) {
            case 'ArrowDown':
            case 'ArrowUp':
            case 'Enter':
            case ' ':
                event.preventDefault();
                event.stopPropagation();
                if (this.animationState() === 'closed') {
                    this.toggleOverlay();
                }
                else {
                    // If already open, handle navigation
                    this.handleListboxKeydown(event);
                }
                break;
            case 'Escape':
                if (this.animationState() === 'open') {
                    event.preventDefault();
                    event.stopPropagation();
                    this.animationState.set('closing');
                }
                break;
        }
    }
    /**
     * Clears the current selection
     */
    clearSelection() {
        if (this.selectionType() === 'multiple') {
            this.value.set([]);
            this.onChange([]);
            this.valueUpdated.emit([]);
        }
        else {
            this.value.set(null);
            this.onChange(null);
            this.valueUpdated.emit(null);
        }
        this.onTouched();
        // Focus back to search input after clearing
        setTimeout(() => {
            this.searchInputRef()?.nativeElement?.focus();
            // Set first option as active by default after clearing
            this.setDefaultActiveOption();
        }, 0);
    }
    /**
     * Handles clear selection button click
     * @param event - The click event
     */
    handleClearSelection(event) {
        event.stopPropagation();
        this.clearSelection();
    }
    /**
     * Checks if an option is currently selected
     * @param option - The option to check
     * @returns True if the option is selected
     */
    isOptionSelected(option) {
        const selectionType = this.selectionType();
        const currentValue = this.value();
        const optionValue = this.getOptionValue(option);
        if (selectionType === 'multiple') {
            const selected = Array.isArray(currentValue) ? currentValue : [];
            return selected.some((value) => this.getOptionValue(value) === optionValue);
        }
        else {
            return currentValue && this.getOptionValue(currentValue) === optionValue;
        }
    }
    /**
     * Checks if an option is currently active for keyboard navigation
     * @param index - The option index
     * @returns True if the option is active
     */
    isActiveOption(index) {
        return this.activeOptionIndex() === index;
    }
    /**
     * Gets the display value for an option
     * @param option - The option to get display value for
     * @returns The display string for the option
     */
    getDisplayValue(option) {
        if (!option)
            return '';
        const displayKey = this.displayKey();
        if (displayKey && typeof option === 'object') {
            return option[displayKey] || '';
        }
        if (typeof option === 'object') {
            const commonKeys = ['name', 'label', 'text', 'title', 'displayName'];
            for (const key of commonKeys) {
                if (option[key] !== undefined && option[key] !== null) {
                    return String(option[key]);
                }
            }
            const keys = Object.keys(option);
            for (const key of keys) {
                const value = option[key];
                if (typeof value === 'string' || typeof value === 'number') {
                    return String(value);
                }
            }
            return JSON.stringify(option);
        }
        return String(option);
    }
    /**
     * Type guard to check if a value is an array
     * @param value - The value to check
     * @returns True if the value is an array
     */
    isArray(value) {
        return Array.isArray(value);
    }
    // ==============================================================================
    // PRIVATE METHODS
    // ==============================================================================
    /**
     * Updates the trigger button width for overlay positioning
     */
    updateTriggerWidth() {
        if (this.triggerRef) {
            const width = this.triggerRef()?.nativeElement.offsetWidth;
            if (!width)
                return;
            this.triggerWidth.set(width);
        }
    }
    /**
     * Gets the value property from an option
     * @param option - The option to get value from
     * @returns The option value
     */
    getOptionValue(option) {
        if (!option)
            return option;
        const valueKey = this.valueKey();
        return valueKey && typeof option === 'object' ? option[valueKey] : option;
    }
    /**
     * Checks if a value is valid (exists in options)
     * @param value - The value to validate
     * @returns True if the value exists in the options array
     */
    isValidValue(value) {
        if (!value)
            return false;
        const options = this.options();
        return options.some((option) => {
            if (typeof option === 'string' && typeof value === 'string') {
                return option === value;
            }
            // For objects, check if the value matches the option or its value property
            const optionValue = this.getOptionValue(option);
            const currentValue = this.getOptionValue(value);
            return optionValue === currentValue || option === value;
        });
    }
    /**
     * Triggers blur event for form validation
     */
    triggerBlur() {
        this.blurTrigger.update((val) => val + 1);
        this.onTouched();
        this.blurred.emit(this.value());
    }
    /**
     * Clears the search input and term
     */
    clearSearch() {
        this.searchValue.set('');
        this.searchTerm.set('');
        this.activeOptionIndex.set(-1);
    }
    /**
     * Sets up reactive search handling with debouncing
     * The debounce time updates reactively when the input changes
     */
    setupSearchHandling() {
        const debounceTime$ = toObservable(this.searchDebounceTime);
        debounceTime$
            .pipe(switchMap((currentDebounceTime) => {
            return this.searchSubject.pipe(debounceTime(currentDebounceTime), distinctUntilChanged());
        }), takeUntilDestroyed())
            .subscribe((searchValue) => {
            this.searchTerm.set(searchValue);
            // Set first option as active after debounced search
            setTimeout(() => this.setDefaultActiveOption(), 0);
        });
    }
    /**
     * Sets the active option for keyboard navigation
     * @param index - The index of the option to make active
     */
    setActiveOption(index) {
        this.activeOptionIndex.set(index);
        this.updateActiveOptionVisually();
    }
    /**
     * Sets the first option as active by default
     */
    setDefaultActiveOption() {
        const filteredOptions = this.filteredOptions();
        if (filteredOptions.length > 0) {
            this.setActiveOption(0);
        }
    }
    /**
     * Updates the visual state of the active option
     */
    updateActiveOptionVisually() {
        if (!this.optionsList()?.nativeElement)
            return;
        // Remove active class from all options
        const allOptions = this.optionsList()?.nativeElement.querySelectorAll('[data-option-index]');
        if (!allOptions)
            return;
        allOptions.forEach((option) => {
            option.classList.remove('cdk-option-active');
        });
        // Add active class to current option
        const currentIndex = this.activeOptionIndex();
        if (currentIndex >= 0) {
            const activeOption = this.optionsList()?.nativeElement.querySelector(`[data-option-index="${currentIndex}"]`);
            if (activeOption) {
                activeOption.classList.add('cdk-option-active');
                // Scroll into view if needed
                activeOption.scrollIntoView({ block: 'nearest' });
            }
        }
    }
    /**
     * Sets up the search query emission effect
     * Emits search results whenever the search term or options change
     */
    setupSearchQueryEmission() {
        effect(() => {
            const query = this.searchTerm();
            const results = this.filteredOptions();
            // Emit the search query event with current query and filtered results
            this.searchQuery.emit({ query, results });
        });
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: ComboboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: ComboboxComponent, isStandalone: true, selector: "st-combobox", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ghost: { classPropertyName: "ghost", publicName: "ghost", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, parentForm: { classPropertyName: "parentForm", publicName: "parentForm", isSignal: true, isRequired: false, transformFunction: null }, formControlName: { classPropertyName: "formControlName", publicName: "formControlName", isSignal: true, isRequired: false, transformFunction: null }, allowClear: { classPropertyName: "allowClear", publicName: "allowClear", isSignal: true, isRequired: false, transformFunction: null }, allowClearAll: { classPropertyName: "allowClearAll", publicName: "allowClearAll", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, displayKey: { classPropertyName: "displayKey", publicName: "displayKey", isSignal: true, isRequired: false, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: false, transformFunction: null }, selectionType: { classPropertyName: "selectionType", publicName: "selectionType", isSignal: true, isRequired: false, transformFunction: null }, searchDebounced: { classPropertyName: "searchDebounced", publicName: "searchDebounced", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceTime: { classPropertyName: "searchDebounceTime", publicName: "searchDebounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", value: "valueChange", blurred: "blurred", valueUpdated: "valueUpdated", searchQuery: "searchQuery" }, providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                useExisting: ComboboxComponent,
                multi: true,
            },
        ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "optionsList", first: true, predicate: ["optionsList"], descendants: true, isSignal: true }, { propertyName: "listbox", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], ngImport: i0, template: "@let isOpen = this.isOpen(); @let isAnimatingOpen = this.isAnimatingOpen(); @let triggerWidth = this.triggerWidth();\n@let dataState = this.dataState(); @let filteredOptions = this.filteredOptions(); @let displayValue = this.displayValue();\n@let multipleDisplayInfo = this.multipleDisplayInfo(); @let label = this.label();\n\n<fieldset class=\"fieldset p-0\">\n\t@if (label) {\n\t\t<legend class=\"fieldset-legend font-body pt-0 text-pretty\">{{ label }}</legend>\n\t}\n\n\t<button\n\t\t#trigger\n\t\t[class]=\"validationClass()\"\n\t\t(click)=\"toggleOverlay()\"\n\t\t(keydown)=\"handleTriggerKeydown($event)\"\n\t\ttype=\"button\"\n\t\tcdkOverlayOrigin\n\t\taria-haspopup=\"true\"\n\t\trole=\"combobox\"\n\t\t[attr.aria-controls]=\"'combobox-list'\"\n\t\t[attr.aria-expanded]=\"isOpen\"\n\t\t[disabled]=\"disabled()\"\n\t>\n\t\t<span class=\"flex-1 truncate text-left\">\n\t\t\t@if (selectionType() === 'multiple' && multipleDisplayInfo) {\n\t\t\t\t<span class=\"inline-flex items-center gap-2\">\n\t\t\t\t\t<span class=\"truncate\">{{ multipleDisplayInfo.firstItem }}</span>\n\t\t\t\t\t@if (multipleDisplayInfo.additionalCount > 0) {\n\t\t\t\t\t\t<st-badge [size]=\"badgeClass()\">+{{ multipleDisplayInfo.additionalCount }}</st-badge>\n\t\t\t\t\t}\n\t\t\t\t</span>\n\t\t\t} @else {\n\t\t\t\t{{ displayValue }}\n\t\t\t}\n\t\t</span>\n\t\t<div class=\"inline-flex items-center justify-items-end gap-2\">\n\t\t\t<!-- Clear button for both single and multiple selection -->\n\t\t\t@if (showClearButton()) {\n\t\t\t\t<div class=\"grid translate-x-full place-content-center\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"btn font-body btn-sm btn-secondary h-[1.5em] w-[1.5em] items-center p-0 align-top [font-size:inherit] leading-normal font-medium text-pretty\"\n\t\t\t\t\t\t(click)=\"handleClearSelection($event)\"\n\t\t\t\t\t\t(keydown.enter)=\"handleClearSelection($event)\"\n\t\t\t\t\t\t(keydown.space)=\"handleClearSelection($event)\"\n\t\t\t\t\t\t[attr.aria-label]=\"'sdk.combobox.clearSelection' | translate\"\n\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<st-icon [icon]=\"iconClose\"></st-icon>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t<st-icon\n\t\t\t\tclass=\"h-[1.5em] w-[1.5em] flex-0 translate-x-full transition-transform duration-200\"\n\t\t\t\t[class.rotate-180]=\"isAnimatingOpen\"\n\t\t\t\t[icon]=\"iconChevronDown\"\n\t\t\t></st-icon>\n\t\t</div>\n\t</button>\n\n\t<ng-template\n\t\tcdkConnectedOverlay\n\t\t[cdkConnectedOverlayOrigin]=\"trigger\"\n\t\t[cdkConnectedOverlayOpen]=\"isOpen\"\n\t\t[cdkConnectedOverlayWidth]=\"triggerWidth\"\n\t\t[cdkConnectedOverlayMinWidth]=\"triggerWidth\"\n\t\t(detach)=\"handleDetach()\"\n\t\t(overlayOutsideClick)=\"handleClickOutside($event)\"\n\t>\n\t\t<div\n\t\t\tclass=\"bg-base-200 rounded-box shadow-primary border-neutral data-[state=closing]:animate-fade-out-down data-[state=opening]:animate-fade-in-up data-[state=open]:animate-fade-in-up data-[state=closing]:animate-duration-150 data-[state=opening]:animate-duration-150 data-[state=open]:animate-duration-150 mt-1 w-full overflow-hidden border border-solid\"\n\t\t\t(animationend)=\"onAnimationEnd($event)\"\n\t\t\t[attr.data-state]=\"dataState\"\n\t\t>\n\t\t\t<!-- Search Input -->\n\t\t\t<div class=\"border-neutral border-b\">\n\t\t\t\t@let searchInputClass = this.searchInputClass();\n\t\t\t\t<div [class]=\"searchInputClass\">\n\t\t\t\t\t<st-icon class=\"text-base-placeholder\" [icon]=\"iconSearch\"></st-icon>\n\t\t\t\t\t<input\n\t\t\t\t\t\t#searchInput\n\t\t\t\t\t\tclass=\"placeholder:text-base-placeholder w-full border-transparent py-0 focus-within:outline-none focus:border-transparent focus:outline-none\"\n\t\t\t\t\t\t[value]=\"currentSearchValue\"\n\t\t\t\t\t\t(input)=\"handleSearchInput($event)\"\n\t\t\t\t\t\t(keydown)=\"handleSearchKeydown($event)\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t[placeholder]=\"translatedSearchPlaceholder()\"\n\t\t\t\t\t\t[attr.aria-label]=\"'sdk.combobox.searchOptions' | translate\"\n\t\t\t\t\t\t[disabled]=\"!isOpen\"\n\t\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Options List -->\n\t\t\t@let menuClass = this.menuClass();\n\t\t\t<ul\n\t\t\t\t[class]=\"menuClass\"\n\t\t\t\tcdkListbox\n\t\t\t\tcdkListboxUseActiveDescendant\n\t\t\t\t[cdkListboxMultiple]=\"selectionType() === 'multiple'\"\n\t\t\t\taria-labelledby=\"Combobox options\"\n\t\t\t\trole=\"listbox\"\n\t\t\t\t#optionsList\n\t\t\t\t[tabindex]=\"-1\"\n\t\t\t\t(keydown)=\"handleListboxKeydown($event)\"\n\t\t\t\t(selectionChange)=\"handleSelectionChange($event)\"\n\t\t\t>\n\t\t\t\t@for (option of filteredOptions; track $index) {\n\t\t\t\t\t<li [cdkOption]=\"option\" class=\"group\" [attr.data-option-index]=\"$index\" [class.cdk-option-active]=\"isActiveOption($index)\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"group-[.cdk-option-active]:bg-base-300 font-body flex w-full items-center justify-between rounded-none px-4 py-2 text-left leading-normal font-normal text-pretty transition-colors duration-150 ease-in-out\"\n\t\t\t\t\t\t\t(click)=\"handleOptionSelect(option)\"\n\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"flex-1 truncate\">{{ getDisplayValue(option) }}</span>\n\n\t\t\t\t\t\t\t@if (isOptionSelected(option)) {\n\t\t\t\t\t\t\t\t<st-icon class=\"h-4 w-4 shrink-0\" [icon]=\"iconCheck\"></st-icon>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\n\t\t\t\t<!-- Empty state -->\n\t\t\t\t@if (filteredOptions.length === 0) {\n\t\t\t\t\t<li class=\"text-base-content/60 px-4 py-2 text-center\">\n\t\t\t\t\t\t<span>{{ translatedNoResultsText() }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\n\t\t\t<!-- Clear All button for multiple selection -->\n\t\t\t@if (selectionType() === 'multiple' && allowClearAll() && value() && isArray(value()) && value().length > 0) {\n\t\t\t\t<div class=\"border-base-300 border-t p-2\">\n\t\t\t\t\t<button type=\"button\" class=\"btn btn-outline btn-sm w-full\" (click)=\"clearSelection()\" (keydown)=\"handleClearButtonKeydown($event)\">\n\t\t\t\t\t\t{{ 'sdk.combobox.clearAll' | translate }}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</div>\n\t</ng-template>\n\n\t<!-- Hidden label for accessibility -->\n\t<label class=\"sr-only\" [for]=\"name()\">{{ label }}</label>\n\n\t<!-- Additional content -->\n\t<ng-content></ng-content>\n</fieldset>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CdkListbox, selector: "[cdkListbox]", inputs: ["id", "tabindex", "cdkListboxValue", "cdkListboxMultiple", "cdkListboxDisabled", "cdkListboxUseActiveDescendant", "cdkListboxOrientation", "cdkListboxCompareWith", "cdkListboxNavigationWrapDisabled", "cdkListboxNavigatesDisabledOptions"], outputs: ["cdkListboxValueChange"], exportAs: ["cdkListbox"] }, { kind: "directive", type: CdkOption, selector: "[cdkOption]", inputs: ["id", "cdkOption", "cdkOptionTypeaheadLabel", "cdkOptionDisabled", "tabindex"], exportAs: ["cdkOption"] }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "component", type: BadgeComponent, selector: "st-badge", inputs: ["variant", "size", "outline", "soft", "dash", "ghost", "icon", "iconPosition"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: ComboboxComponent, decorators: [{
            type: Component,
            args: [{ selector: 'st-combobox', imports: [FormsModule, ReactiveFormsModule, PortalModule, OverlayModule, CdkListbox, CdkOption, IconComponent, BadgeComponent, TranslatePipe], providers: [
                        {
                            provide: NG_VALUE_ACCESSOR,
                            useExisting: ComboboxComponent,
                            multi: true,
                        },
                    ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let isOpen = this.isOpen(); @let isAnimatingOpen = this.isAnimatingOpen(); @let triggerWidth = this.triggerWidth();\n@let dataState = this.dataState(); @let filteredOptions = this.filteredOptions(); @let displayValue = this.displayValue();\n@let multipleDisplayInfo = this.multipleDisplayInfo(); @let label = this.label();\n\n<fieldset class=\"fieldset p-0\">\n\t@if (label) {\n\t\t<legend class=\"fieldset-legend font-body pt-0 text-pretty\">{{ label }}</legend>\n\t}\n\n\t<button\n\t\t#trigger\n\t\t[class]=\"validationClass()\"\n\t\t(click)=\"toggleOverlay()\"\n\t\t(keydown)=\"handleTriggerKeydown($event)\"\n\t\ttype=\"button\"\n\t\tcdkOverlayOrigin\n\t\taria-haspopup=\"true\"\n\t\trole=\"combobox\"\n\t\t[attr.aria-controls]=\"'combobox-list'\"\n\t\t[attr.aria-expanded]=\"isOpen\"\n\t\t[disabled]=\"disabled()\"\n\t>\n\t\t<span class=\"flex-1 truncate text-left\">\n\t\t\t@if (selectionType() === 'multiple' && multipleDisplayInfo) {\n\t\t\t\t<span class=\"inline-flex items-center gap-2\">\n\t\t\t\t\t<span class=\"truncate\">{{ multipleDisplayInfo.firstItem }}</span>\n\t\t\t\t\t@if (multipleDisplayInfo.additionalCount > 0) {\n\t\t\t\t\t\t<st-badge [size]=\"badgeClass()\">+{{ multipleDisplayInfo.additionalCount }}</st-badge>\n\t\t\t\t\t}\n\t\t\t\t</span>\n\t\t\t} @else {\n\t\t\t\t{{ displayValue }}\n\t\t\t}\n\t\t</span>\n\t\t<div class=\"inline-flex items-center justify-items-end gap-2\">\n\t\t\t<!-- Clear button for both single and multiple selection -->\n\t\t\t@if (showClearButton()) {\n\t\t\t\t<div class=\"grid translate-x-full place-content-center\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"btn font-body btn-sm btn-secondary h-[1.5em] w-[1.5em] items-center p-0 align-top [font-size:inherit] leading-normal font-medium text-pretty\"\n\t\t\t\t\t\t(click)=\"handleClearSelection($event)\"\n\t\t\t\t\t\t(keydown.enter)=\"handleClearSelection($event)\"\n\t\t\t\t\t\t(keydown.space)=\"handleClearSelection($event)\"\n\t\t\t\t\t\t[attr.aria-label]=\"'sdk.combobox.clearSelection' | translate\"\n\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<st-icon [icon]=\"iconClose\"></st-icon>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t<st-icon\n\t\t\t\tclass=\"h-[1.5em] w-[1.5em] flex-0 translate-x-full transition-transform duration-200\"\n\t\t\t\t[class.rotate-180]=\"isAnimatingOpen\"\n\t\t\t\t[icon]=\"iconChevronDown\"\n\t\t\t></st-icon>\n\t\t</div>\n\t</button>\n\n\t<ng-template\n\t\tcdkConnectedOverlay\n\t\t[cdkConnectedOverlayOrigin]=\"trigger\"\n\t\t[cdkConnectedOverlayOpen]=\"isOpen\"\n\t\t[cdkConnectedOverlayWidth]=\"triggerWidth\"\n\t\t[cdkConnectedOverlayMinWidth]=\"triggerWidth\"\n\t\t(detach)=\"handleDetach()\"\n\t\t(overlayOutsideClick)=\"handleClickOutside($event)\"\n\t>\n\t\t<div\n\t\t\tclass=\"bg-base-200 rounded-box shadow-primary border-neutral data-[state=closing]:animate-fade-out-down data-[state=opening]:animate-fade-in-up data-[state=open]:animate-fade-in-up data-[state=closing]:animate-duration-150 data-[state=opening]:animate-duration-150 data-[state=open]:animate-duration-150 mt-1 w-full overflow-hidden border border-solid\"\n\t\t\t(animationend)=\"onAnimationEnd($event)\"\n\t\t\t[attr.data-state]=\"dataState\"\n\t\t>\n\t\t\t<!-- Search Input -->\n\t\t\t<div class=\"border-neutral border-b\">\n\t\t\t\t@let searchInputClass = this.searchInputClass();\n\t\t\t\t<div [class]=\"searchInputClass\">\n\t\t\t\t\t<st-icon class=\"text-base-placeholder\" [icon]=\"iconSearch\"></st-icon>\n\t\t\t\t\t<input\n\t\t\t\t\t\t#searchInput\n\t\t\t\t\t\tclass=\"placeholder:text-base-placeholder w-full border-transparent py-0 focus-within:outline-none focus:border-transparent focus:outline-none\"\n\t\t\t\t\t\t[value]=\"currentSearchValue\"\n\t\t\t\t\t\t(input)=\"handleSearchInput($event)\"\n\t\t\t\t\t\t(keydown)=\"handleSearchKeydown($event)\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t[placeholder]=\"translatedSearchPlaceholder()\"\n\t\t\t\t\t\t[attr.aria-label]=\"'sdk.combobox.searchOptions' | translate\"\n\t\t\t\t\t\t[disabled]=\"!isOpen\"\n\t\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Options List -->\n\t\t\t@let menuClass = this.menuClass();\n\t\t\t<ul\n\t\t\t\t[class]=\"menuClass\"\n\t\t\t\tcdkListbox\n\t\t\t\tcdkListboxUseActiveDescendant\n\t\t\t\t[cdkListboxMultiple]=\"selectionType() === 'multiple'\"\n\t\t\t\taria-labelledby=\"Combobox options\"\n\t\t\t\trole=\"listbox\"\n\t\t\t\t#optionsList\n\t\t\t\t[tabindex]=\"-1\"\n\t\t\t\t(keydown)=\"handleListboxKeydown($event)\"\n\t\t\t\t(selectionChange)=\"handleSelectionChange($event)\"\n\t\t\t>\n\t\t\t\t@for (option of filteredOptions; track $index) {\n\t\t\t\t\t<li [cdkOption]=\"option\" class=\"group\" [attr.data-option-index]=\"$index\" [class.cdk-option-active]=\"isActiveOption($index)\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"group-[.cdk-option-active]:bg-base-300 font-body flex w-full items-center justify-between rounded-none px-4 py-2 text-left leading-normal font-normal text-pretty transition-colors duration-150 ease-in-out\"\n\t\t\t\t\t\t\t(click)=\"handleOptionSelect(option)\"\n\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"flex-1 truncate\">{{ getDisplayValue(option) }}</span>\n\n\t\t\t\t\t\t\t@if (isOptionSelected(option)) {\n\t\t\t\t\t\t\t\t<st-icon class=\"h-4 w-4 shrink-0\" [icon]=\"iconCheck\"></st-icon>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\n\t\t\t\t<!-- Empty state -->\n\t\t\t\t@if (filteredOptions.length === 0) {\n\t\t\t\t\t<li class=\"text-base-content/60 px-4 py-2 text-center\">\n\t\t\t\t\t\t<span>{{ translatedNoResultsText() }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\n\t\t\t<!-- Clear All button for multiple selection -->\n\t\t\t@if (selectionType() === 'multiple' && allowClearAll() && value() && isArray(value()) && value().length > 0) {\n\t\t\t\t<div class=\"border-base-300 border-t p-2\">\n\t\t\t\t\t<button type=\"button\" class=\"btn btn-outline btn-sm w-full\" (click)=\"clearSelection()\" (keydown)=\"handleClearButtonKeydown($event)\">\n\t\t\t\t\t\t{{ 'sdk.combobox.clearAll' | translate }}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</div>\n\t</ng-template>\n\n\t<!-- Hidden label for accessibility -->\n\t<label class=\"sr-only\" [for]=\"name()\">{{ label }}</label>\n\n\t<!-- Additional content -->\n\t<ng-content></ng-content>\n</fieldset>\n" }]
        }], ctorParameters: () => [] });

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

export { ComboboxComponent };
//# sourceMappingURL=sixbell-telco-sdk-components-forms-combobox.mjs.map