/** * Copyright Aquera Inc 2025 * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ import '../nile-icon'; import '../nile-popup/nile-popup'; import '../nile-tag/nile-tag'; import '../nile-option/nile-option'; import '../nile-checkbox/nile-checkbox'; import '../nile-loader/nile-loader'; import NileElement from '../internal/nile-element'; import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; import type { NileFormControl } from '../internal/nile-element'; import type NilePopup from '../nile-popup/nile-popup'; import type { ComboboxOption, ComboboxRenderItemConfig, ComboboxTagLayout, ComboboxSize, ComboboxPlacement } from './types.js'; /** * @summary A data-driven combobox with virtualized options, inline search, multi-select tags, * custom value creation, and full WAI-ARIA Combobox keyboard navigation. * * @tag nile-combobox * @status stable * @since 2.0 * * @dependency nile-icon * @dependency nile-popup * @dependency nile-tag * @dependency nile-checkbox * @dependency nile-loader * * @slot label - The input's label. * @slot prefix - Prepend a presentational icon or element before the input. * @slot clear-icon - An icon to use in lieu of the default clear icon. * @slot expand-icon - The icon to show when the control is expanded/collapsed. * @slot help-text - Text that describes how to use the input. * @slot footer - Custom footer content (overrides default footer in multi-select mode). * @slot no-results - Custom no-results content. * * @event nile-change - Emitted when the control's value changes. * @event nile-clear - Emitted when the control's value is cleared. * @event nile-input - Emitted when the control receives input. * @event nile-focus - Emitted when the control gains focus. * @event nile-blur - Emitted when the control loses focus. * @event nile-show - Emitted when the dropdown opens. * @event nile-after-show - Emitted after the dropdown opens and all animations complete. * @event nile-hide - Emitted when the dropdown closes. * @event nile-after-hide - Emitted after the dropdown closes and all animations complete. * @event nile-search - Emitted (debounced) when the user types. Useful for API-driven filtering. * @event nile-tag-remove - Emitted when a tag is removed in multi-select mode. * @event nile-tag-add - Emitted when a custom value is added via allowCustomValue. * @event nile-scroll-end - Emitted when scrolled to the bottom (for infinite loading). * @event nile-invalid - Emitted when form validation constraints aren't satisfied. * @event nile-select-all - Emitted when the Select all / Deselect all control toggles. Detail: { value, name, action: 'select-all' | 'deselect-all' }. * * @csspart form-control - The form control wrapper. * @csspart form-control-label - The label wrapper. * @csspart form-control-input - The input area wrapper. * @csspart combobox - The combobox trigger (input + tags + icons). * @csspart input - The text input element. * @csspart listbox - The dropdown listbox. * @csspart tags - The tags container in multi-select mode. * @csspart tag - Each individual tag. * @csspart clear-button - The clear button. * @csspart expand-icon - The expand/collapse icon. * @csspart top-actions - The sticky row above the option list that contains the Select all checkbox and the Selected / Show all filter toggle. Rendered only when multiple && selectAllEnabled. * @csspart select-all - The Select all / Deselect all checkbox wrapper inside top-actions. * @csspart show-toggle - The "Selected / Show all" filter button inside top-actions. * @csspart no-results - The empty-state container shown when a search/filter returns no items. Contains no-results-title and no-results-subtitle. * @csspart no-results-title - The title row of the no-results empty state. * @csspart no-results-subtitle - The subtitle row of the no-results empty state. * @csspart no-data - The empty-state container shown when the dataset is empty (no active search/filter). * @csspart footer - The footer with "Show Selected" / "Clear All". * @csspart no-results - The no-results message. */ export declare class NileCombobox extends NileElement implements NileFormControl { static styles: CSSResultGroup; private readonly formControlController; private readonly hasSlotController; private readonly portalManager; private readonly searchManager; private scrollElementRef; private virtualizerCtrl; private hScrollElementRef; private hVirtualizerCtrl; private scrollTimeout; private scrolling; private visibilityManager?; private keyboardActiveIndex; popup: NilePopup; combobox: HTMLElement; inputElement: HTMLInputElement; valueInput: HTMLInputElement; private hasFocus; displayLabel: string; selectedOptions: ComboboxOption[]; /** The items displayed after filtering. Renderer reads from this. */ filteredData: any[]; /** * Mixed (header + option) row list, only populated when `data` contains * group entries (`type: 'group'`). When non-empty, the listbox renders from * this instead of `filteredData`. `filteredData` stays in sync as the * option-only projection so existing select-all / strict-match / etc. logic * keeps working unchanged. */ private filteredRows; /** The complete unfiltered dataset (preserved for re-filtering). */ private originalData; showNoResults: boolean; showListbox: boolean; searchValue: string; private showSelectedOnly; private selectAllChecked; private selectAllIndeterminate; /** * Index into `filteredRows` of the group header that should be pinned at * the top of the (virtualized) listbox right now. -1 means none. * Recomputed on scroll. */ private stickyHeaderIndex; name: string; data: any[]; value: string | string[]; defaultValue: string | string[]; size: ComboboxSize; placeholder: string; multiple: boolean; label: string; required: boolean; disabled: boolean; open: boolean; clearable: boolean; loading: boolean; optionsLoading: boolean; /** When true, skip local filtering and rely solely on the `nile-search` event for API-driven results. */ disableLocalSearch: boolean; /** When true, displays a "+ Add [value]" option for values not in the data. */ allowCustomValue: boolean; /** When true, typing free text and pressing Enter/Tab adds it as a tag (like nile-chip's acceptUserInput). */ acceptUserInput: boolean; /** When true, custom values added via allowCustomValue or acceptUserInput are also appended to the suggestions list. */ addToSuggestions: boolean; /** When true, value must match an option. On blur, reverts to the last valid value if text doesn't match. */ strict: boolean; /** Max tags visible before showing "+N more" (0 = no limit). */ maxTagsVisible: number; /** Controls how tags wrap in multi-select mode. */ tagLayout: ComboboxTagLayout; /** * Show footer with "Show Selected" and "Clear All" in multi-select mode. * Automatically suppressed when `selectAllEnabled` is true, since the top * actions row already provides the same controls. */ showFooter: boolean; /** * When true (and `multiple` is true), renders a "Select all" / "Deselect all" * checkbox at the top of the listbox. Operates on the currently visible, * non-disabled options (respects the active search filter). */ selectAllEnabled: boolean; /** * When true (default), data-driven group headers stick to the top of the * listbox while scrolling through that group's options (Atlassian-style). * Works in both plain and virtualized rendering modes. Set to false for * inline-only headers that scroll away with their options. */ stickyGroupHeader: boolean; portal: boolean; hoist: boolean; placement: ComboboxPlacement; form: string; helpText: string; errorMessage: string; warning: boolean; error: boolean; success: boolean; filled: boolean; pill: boolean; noResultsMessage: string; noResultsSubtitle: string; noDataMessage: string; /** * Pre-defined autocomplete suggestions. Accepts a JSON array string attribute * or a JS array property (like nile-chip). When `addToSuggestions` is true, * custom values added by the user are appended to this list and persisted. */ autoCompleteOptions: any[]; /** Debounce interval (ms) for the nile-search event. */ debounceMs: number; renderItemConfig?: ComboboxRenderItemConfig; allowHtmlLabel: boolean; enableVisibilityEffect: boolean; enableTabClose: boolean; noWidthSync: boolean; /** Number of columns in the dropdown grid (vertical scroll). When > 1, options render in a multi-column grid layout. */ gridColumns: number; /** Number of visible rows in horizontal grid mode. When > 0, enables horizontal virtual scroll with columns scrolling left/right. */ gridRows: number; /** Width of each column in horizontal grid mode (px). */ gridColumnWidth: number; get validity(): ValidityState; get validationMessage(): string; connectedCallback(): void; disconnectedCallback(): void; protected firstUpdated(_changed: PropertyValues): void; protected updated(changedProperties: PropertyValues): void; private get isHorizontalGrid(); /** True when the source data contains at least one group entry. */ private get hasGroupedData(); /** * Walk filteredRows and find the index of the deepest group header whose * virtual position is at or above `scrollTop`. That's the header that * should be pinned at the top of the listbox right now. */ private updateStickyHeader; /** Recursively keep only options whose value is in `selectedSet`; drop empty groups. */ private pruneTreeBySelection; private get hasActiveFilter(); private renderEmptyState; private get isBidirectionalGrid(); private get virtualRowCount(); private get virtualColumnCount(); private updateVirtualizerCount; private lastVirtualizerGrouped; private getDisplayText; private getItemValue; private getSearchText; private getItemDescription; private getItemPrefix; private getItemSuffix; private syncSelection; /** * Returns the options eligible for Select All — the currently filtered data * (so the active search/filter is respected) minus any `disabled` items. * Mirrors nile-select's `getSelectableOptions()` contract. */ private getSelectableData; /** * Derives `selectAllChecked` + `selectAllIndeterminate` from the current * selection vs. the selectable set. No-op when `multiple` is false. * * options=0 → both false (nothing to select, e.g. empty search) * selected=0 → checked=false, indeterminate=false * selected=options → checked=true, indeterminate=false * otherwise (partial) → checked=false, indeterminate=true */ private updateSelectAllState; /** * Toggles all selectable, visible options. Indeterminate → clears selection * (matches the "Deselect All" label flip), empty → selects all, * fully-selected → clears. */ private handleSelectAllToggle; private handleDocumentFocusIn; private handleDocumentKeyDown; private handleDocumentMouseDown; private handleWindowError; private handleWindowResize; private handleWindowScroll; private setupBoundHandlers; private addOpenListeners; private removeOpenListeners; private onInputKeyDown; private onGlobalKeyDown; private getVisibleOptions; private moveActiveOption; private setActiveOption; private selectActiveOption; private onInputChange; private onInputHandler; private filterOptions; private onFocusIn; private onFocusOut; private onTriggerMouseDown; private onOptionClick; private handleOptionSelection; private addCustomValue; private onClearClick; private removeTag; private onFooterClick; private toggleShowSelected; private clearAll; private onScroll; show(): Promise; hide(): Promise; handleOpenChange(): Promise; private doOpen; private doClose; handleDisabledChange(): void; handleValueChange(): void; handleDataChange(): void; handleAutoCompleteOptionsChange(): void; handleSelectAllConfigChange(): void; handlePortalChange(): void; checkValidity(): boolean; getForm(): HTMLFormElement | null; reportValidity(): boolean; setCustomValidity(message: string): void; focus(options?: FocusOptions): void; blur(): void; private handleInvalid; private updateValidity; private resetScrollPosition; render(): TemplateResult; private renderTrigger; private renderInlineTags; private renderClearButton; private renderStickyHeaderOverlay; private renderListbox; private renderHorizontalListbox; private renderLoader; private renderSelectAll; private renderFooter; } export default NileCombobox; declare global { interface HTMLElementTagNameMap { 'nile-combobox': NileCombobox; } }