/** * TyMultiselect Web Component * PORTED FROM: clj/ty/components/multiselect.cljs * * A multiselect dropdown component using ty-tag for selections with: * - Tag-only options (only ty-tag elements supported) * - Multiple selection with visual tags * - Desktop mode with smart positioning * - Mobile mode with full-screen modal * - Search and filtering capabilities * - Keyboard navigation * - Form association for native form submission with multiple values * - Scroll locking when dropdown is open * - Outside click to close * * @example * ```html * * * JavaScript * TypeScript * Python * * * * * JavaScript * TypeScript * Python * * * * * *
* * John Doe *
*
*
* ``` */ import type { Flavor, Size } from '../types/common.js'; import { TyComponent } from '../base/ty-component.js'; import type { PropertyChange } from '../utils/property-manager.js'; /** * Tag data structure */ interface TagData { value: string; text: string; element: HTMLElement; } /** * Component state structure */ interface MultiselectState { open: boolean; search: string; highlightedIndex: number; filteredTags: TagData[]; selectedValues: string[]; mode: 'desktop' | 'mobile'; } /** * Ty Multiselect Component */ export declare class TyMultiselect extends TyComponent { protected static properties: { value: { type: "string"; visual: boolean; formValue: boolean; emitChange: boolean; default: string; coerce: (v: any) => string; }; name: { type: "string"; default: string; }; placeholder: { type: "string"; visual: boolean; default: string; }; label: { type: "string"; visual: boolean; default: string; }; disabled: { type: "boolean"; visual: boolean; default: boolean; }; readonly: { type: "boolean"; visual: boolean; default: boolean; }; required: { type: "boolean"; visual: boolean; default: boolean; }; externalSearch: { type: "boolean"; visual: boolean; default: boolean; aliases: { 'external-search': boolean; }; }; size: { type: "string"; visual: boolean; default: string; validate: (v: any) => boolean; coerce: (v: any) => any; }; flavor: { type: "string"; visual: boolean; default: string; validate: (v: any) => boolean; coerce: (v: any) => any; }; debounce: { type: "number"; default: number; validate: (v: any) => boolean; coerce: (v: any) => number; }; 'selected-label': { type: "string"; visual: boolean; default: string; }; 'available-label': { type: "string"; visual: boolean; default: string; }; 'no-options-message': { type: "string"; visual: boolean; default: string; }; loading: { type: "boolean"; visual: boolean; default: boolean; }; }; private _name; private _placeholder; private _label; private _disabled; private _readonly; private _required; private _externalSearch; private _loading; private _scrollLockId; private _size; private _selectedLabel; private _availableLabel; private _noOptionsMessage; private _state; private _stubClickHandler; private _tagClickHandler; private _tagDismissHandler; private _searchInputHandler; private _blockSearchClick; private _keyboardHandler; private _debounce; private _searchDebounceTimer; private _optionsScrollbar; private _childObserver; constructor(); /** * Called when component is connected to DOM * TyComponent handles property capture automatically */ protected onConnect(): void; /** * Called when component is disconnected from DOM * Clean up event listeners and timers */ protected onDisconnect(): void; /** * Called when properties change * Handle state synchronization BEFORE render */ protected onPropertiesChanged(changes: PropertyChange[]): void; /** * Toggle the loading visual state on the open popup. * Replaces the available-options area with a centered spinner; search input stays usable. * Pulls the latest registered loader SVG on each call so registry changes * take effect on the next loading toggle. */ private applyLoadingState; /** * Get the form value for this component * Returns FormData with multiple entries (HTMX standard) */ protected getFormValue(): FormDataEntryValue | FormData | null; /** * Parse multiselect value (comma-separated string to array) */ private parseValue; /** * Initialize component state from attributes * Reads from both property and attribute (like ClojureScript version) */ private initializeState; /** * Get all ty-tag elements from the component (ALL slots) */ private getTagElements; /** * Extract value and text from a ty-tag element */ private getTagData; /** * Select a tag - set selected state, move to selected slot, make dismissible */ private selectTag; /** * Deselect a tag - remove selected state, remove from selected slot, remove dismissible */ private deselectTag; /** * Get array of currently selected values from tags (ALWAYS reads from DOM) */ private getSelectedValues; /** * Check if all available tags are selected */ private allTagsSelected; /** * Sync tag selection states with desired values */ private syncSelectedTags; /** * Central update function - synchronizes everything * Uses TyComponent's property system for proper lifecycle */ private updateComponentValue; /** * Calculate and set dropdown position with smart direction detection */ private calculatePosition; private _setupOptionsScrollbar; private _destroyOptionsScrollbar; /** * Open dropdown dialog (desktop mode) * * `.showModal()` puts the dialog in the top layer with a backdrop, but * does NOT prevent the page behind it from scrolling. We use the shared scroll * lock utility (overflow:hidden on ) to keep wheel/touch scrolling from * leaking through to the body — same behavior and * implement. */ private openDropdown; /** * Close dropdown dialog (desktop mode) */ private closeDropdown; /** * Open mobile modal (mobile mode) * Now using element for native z-index management */ private openMobileModal; /** * Close mobile modal (mobile mode) * Now using element for native management */ private closeMobileModal; private handleStubClick; private handleTagClick; private handleTagDismiss; private blockSearchClick; private handleSearchInput; private handleKeyboard; /** * Filter tags based on search query */ private filterTags; /** * Update visibility of tags based on filtered list */ private updateTagVisibility; /** * Show/hide the dropdown options area */ private updateOptionsVisibility; /** * Clear all tag highlights */ private clearHighlights; /** * Highlight tag at specific index */ private highlightTag; /** * Dispatch search event for external search handling * With optional debounce support */ private dispatchSearchEvent; /** * Fire the actual search event */ private fireSearchEvent; /** * Dispatch lifecycle events for popup open/close. * On open with external-search, also fire a `search` event with an empty * query so consumers have a clean hook to reset/refetch the option list. */ private fireOpenEvent; private fireCloseEvent; /** * Dispatch custom change event */ private dispatchChangeEvent; /** * Main render method (required by TyComponent) * Delegates to mode-specific renderer */ protected render(): void; /** * Setup event listeners */ private setupEventListeners; /** * Build CSS class list for stub */ private buildStubClasses; /** * Render desktop mode with dialog */ private renderDesktop; /** * Render mobile mode with full-screen modal * Following dropdown.ts mobile structure */ private renderMobile; /** * Setup event listeners for mobile mode * Using element - backdrop clicks handled natively */ private setupMobileEventListeners; /** * Handle mobile stub click - open modal */ private handleMobileStubClick; /** * Handle mobile tag click - select and potentially close */ private handleMobileTagClick; /** * Update mobile selected section state (collapsed view, empty states, etc.) */ private updateMobileSelectedState; /** * Update selection display (show/hide placeholder) * Matches dropdown.ts pattern - uses CSS via has-selection class */ private updateSelectionDisplay; get value(): string; set value(val: string); get name(): string; set name(val: string); get placeholder(): string; set placeholder(val: string); get label(): string; set label(val: string); get disabled(): boolean; set disabled(value: boolean); get loading(): boolean; set loading(value: boolean); get readonly(): boolean; set readonly(value: boolean); get required(): boolean; set required(value: boolean); get externalSearch(): boolean; set externalSearch(value: boolean); get debounce(): number; set debounce(value: number | string); get size(): Size; set size(value: Size); get flavor(): Flavor; set flavor(value: Flavor); get form(): HTMLFormElement | null; } export {}; //# sourceMappingURL=multiselect.d.ts.map