/**
* PXM Select Component
*
* A flexible, accessible select component that provides dropdown functionality with single
* and multiple selection support. Bring your own styling and animation.
* This component provides structure and behavior only - all styling is controlled by your CSS.
*
* Features:
* - Single and multiple selection modes
* - Keyboard navigation with arrow keys, home/end, enter/space, escape
* - Type-ahead search functionality
* - Dynamic content support (items can be added/removed after initialization)
* - Event-driven animation system (bring your own animation library)
* - Accessibility-first design with proper ARIA attributes
* - Scroll lock option for modal-like behavior
* - Grouping and labeling support
* - Custom value display formatting
*
* Keyboard Navigation:
* - `Enter` or `Space` - Open dropdown / Select focused item
* - `ArrowDown` - Open dropdown / Focus next item
* - `ArrowUp` - Open dropdown / Focus previous item
* - `Home` - Focus first item
* - `End` - Focus last item
* - `Escape` - Close dropdown
* - `Tab` - Close dropdown and move to next focusable element
* - `A-Z` - Type-ahead search
*
* Basic Usage:
* ```html
*
*
* Choose an option...
* ▼
*
*
*
*
* Apple
*
*
* Banana
*
*
* Cherry
*
*
*
* ```
*
* With Custom Icon Rotation:
* ```html
*
*
*
* Choose an option...
* ▶
*
*
*
*
*
* ```
*
* With Search/Filter Functionality:
* ```html
*
*
* Choose an option...
* ▼
*
*
*
*
*
*
*
*
*
*
* Apple
* Banana
* Cherry
* Grape
*
*
*
* ```
*
* Multiple Selection:
* ```html
*
*
* Select multiple...
*
*
* Red
* Green
* Blue
*
*
* ```
*
* Multiple Selection with Custom Separator and Wrapped Values:
* ```html
*
*
*
* Select colors...
*
*
* Red
* Green
*
*
*
*
*
*
* Select tags...
*
*
* Tag 1
* Tag 2
*
*
*
*
*
*
* Select options...
*
*
* Option 1
* Option 2
*
*
* ```
*
* With Groups and Labels:
* ```html
*
*
* Select a fruit...
*
*
*
*
* Citrus
* Orange
* Lemon
*
*
*
*
*
* Berries
* Strawberry
* Blueberry
*
*
*
* ```
*
* Dynamic Content:
* ```javascript
* // Items can be added/removed dynamically
* const select = document.querySelector('pxm-select');
* const content = select.querySelector('pxm-select-content');
*
* const newItem = document.createElement('pxm-select-item');
* newItem.setAttribute('value', 'grape');
* newItem.innerHTML = 'Grape';
* content.appendChild(newItem); // Automatically initialized
*
* // Listen for changes
* select.addEventListener('pxm:select:items-changed', (e) => {
* console.log(`Select now has ${e.detail.itemCount} items`);
* });
*
* // Get/set values programmatically
* select.setValue('apple');
* console.log(select.getValue()); // 'apple'
*
* select.setValues(['apple', 'banana']); // For multiple selection
* console.log(select.getValues()); // ['apple', 'banana']
*
* // Interact with individual wrapped values (when wrap-values="true")
* const valueElement = select.querySelector('pxm-select-value');
* const valueSpans = valueElement.querySelectorAll('.pxm-select-value-item');
*
* valueSpans.forEach(span => {
* span.addEventListener('click', (e) => {
* e.stopPropagation(); // Prevent select from opening
* const value = span.getAttribute('data-value');
* console.log(`Clicked value: ${value}`);
*
* // Example: Remove value on click
* const currentValues = select.getValues();
* const newValues = currentValues.filter(v => v !== value);
* select.setValues(newValues);
* });
* });
* ```
*
* With Animation Library (via events - recommended for CDN):
* ```javascript
* const select = document.querySelector('pxm-select');
*
* select.addEventListener('pxm:select:before-open', (e) => {
* const { contentElement } = e.detail;
* e.preventDefault(); // Take over the animation
*
* gsap.fromTo(contentElement,
* { opacity: 0, y: -10, scaleY: 0.8 },
* {
* opacity: 1,
* y: 0,
* scaleY: 1,
* duration: 0.2,
* transformOrigin: 'top',
* onComplete: () => {
* e.detail.complete(); // Signal animation complete
* }
* }
* );
* });
*
* select.addEventListener('pxm:select:before-close', (e) => {
* const { contentElement } = e.detail;
* e.preventDefault(); // Take over the animation
*
* gsap.to(contentElement, {
* opacity: 0,
* y: -10,
* scaleY: 0.8,
* duration: 0.15,
* transformOrigin: 'top',
* onComplete: () => {
* e.detail.complete(); // Signal animation complete
* }
* });
* });
* ```
*
* With CSS Transitions (default):
* ```css
* pxm-select-content {
* opacity: 0;
* transform: translateY(-8px) scaleY(0.9);
* transform-origin: top;
* transition: opacity 0.2s ease, transform 0.2s ease;
* }
*
* pxm-select-content[data-open="true"] {
* opacity: 1;
* transform: translateY(0) scaleY(1);
* }
* ```
*
* Consumer Styling Examples:
* ```css
* /* Style the component structure *\/
* pxm-select {
* /* Your layout styles *\/
* position: relative;
* display: inline-block;
* }
*
* pxm-select-trigger {
* /* Your trigger button styles *\/
* display: flex;
* align-items: center;
* padding: 8px 12px;
* border: 1px solid #ccc;
* border-radius: 4px;
* cursor: pointer;
* }
*
* pxm-select-content {
* /* Your dropdown styles *\/
* position: absolute;
* top: 100%;
* left: 0;
* right: 0;
* background: white;
* border: 1px solid #ccc;
* border-radius: 4px;
* box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
* z-index: 1000;
* }
*
* /* ✅ DO: Style based on data attributes (for CSS/JS targeting) *\/
* pxm-select[data-open="true"] pxm-select-trigger {
* /* Open state styling *\/
* border-color: #007bff;
* }
*
* pxm-select-item[data-selected="true"] {
* /* Selected item styling *\/
* background: #007bff;
* color: white;
* }
*
* pxm-select-item[data-focused="true"] {
* /* Focused item styling *\/
* background: #f0f0f0;
* }
*
* pxm-select-item[data-disabled="true"] {
* /* Disabled item styling *\/
* opacity: 0.5;
* cursor: not-allowed;
* }
*
* pxm-select[data-disabled="true"] {
* /* Disabled select styling *\/
* opacity: 0.6;
* }
*
* /* Multiple value styling (when wrap-values="true") *\/
* .pxm-select-value-item {
* /* Style individual selected values *\/
* background: #e3f2fd;
* border-radius: 4px;
* padding: 2px 6px;
* display: inline-block;
* font-size: 0.875em;
* }
*
* .pxm-select-value-item[data-value="important"] {
* /* Style specific values by their data-value attribute *\/
* background: #ffebee;
* color: #c62828;
* }
*
* /* ❌ DON'T: Style based on ARIA attributes (bad practice) *\/
* pxm-select-item[aria-selected="true"] {
* /* Don't use ARIA for styling *\/
* }
* ```
*
* With Tailwind CSS:
* ```html
*
*
*
* Choose your option...
* ▼
*
*
*
*
* Apple
*
*
*
*
*
*
* ```
*
* SSR / Hydration Support:
* ```css
* /* Recommended: Set initial styles in CSS to prevent hydration flash *\/
* pxm-select-content:not([data-open="true"]) {
* display: none;
* }
*
* pxm-select-content[data-open="true"] {
* display: block;
* }
*
* /* Optional: Hide content during hydration *\/
* pxm-select:not(:defined) pxm-select-content {
* display: none;
* }
* ```
*
* Accessibility:
* This component manages both ARIA attributes (for accessibility) and data attributes (for styling/JS).
* - ARIA attributes (aria-expanded, aria-selected, etc.) are automatically managed for screen readers
* - Data attributes (data-open, data-selected, etc.) are provided for CSS styling and JavaScript hooks
* - Additional ARIA attributes, labels, and roles should be added by the consumer as needed
*
* Events:
* - `pxm:select:before-open` - Cancelable. Fired before dropdown opens.
* - `pxm:select:after-open` - Fired after dropdown opens.
* - `pxm:select:before-close` - Cancelable. Fired before dropdown closes.
* - `pxm:select:after-close` - Fired after dropdown closes.
* - `pxm:select:before-select` - Cancelable. Fired before item selection.
* - `pxm:select:after-select` - Fired after item selection.
* - `pxm:select:value-change` - Fired when the selected value(s) change.
* - `pxm:select:state-change` - Fired when open/closed state changes.
* - `pxm:select:items-changed` - Fired when items are dynamically added/removed.
* - `pxm:select:icon-rotate` - Fired when the icon should rotate (open/close states).
* - `pxm:select:items-filtered` - Fired when items are filtered by search query.
*/
import type { SelectConfig, SelectState, SelectEventDetail, SelectItemEventDetail, SelectStateChangeDetail, SelectItemData } from './types';
import { PxmSelectTrigger } from './components/pxm-select-trigger';
import { PxmSelectValue } from './components/pxm-select-value';
import { PxmSelectContent } from './components/pxm-select-content';
import { PxmSelectItem } from './components/pxm-select-item';
import { PxmSelectGroup } from './components/pxm-select-group';
import { PxmSelectLabel } from './components/pxm-select-label';
import { PxmSelectSeparator } from './components/pxm-select-separator';
import { PxmSelectSearch } from './components/pxm-select-search';
declare class PxmSelect extends HTMLElement {
private config;
private state;
private triggerElement?;
private valueElement?;
private contentElement?;
private _items?;
private mutationObserver?;
private outsideClickListener?;
private animationPromises;
private hasCustomAnimations;
private defaultListeners;
private typeAheadString;
private typeAheadTimeout?;
private get items();
static get observedAttributes(): string[];
constructor();
connectedCallback(): void;
disconnectedCallback(): void;
attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
private setupComponents;
private findComponents;
private setupEventListeners;
private initializeItems;
private observeChildChanges;
private handleDynamicChanges;
private setupDefaultAnimations;
private hasEventListener;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeDefaultAnimations(): void;
open(): Promise;
close(): Promise;
toggle(): Promise;
selectItem(item: PxmSelectItem): Promise;
focusItem(item: PxmSelectItem): void;
focusItemByIndex(index: number): void;
focusNextItem(): void;
focusPreviousItem(): void;
focusFirstItem(): void;
focusLastItem(): void;
focusFirstVisibleItem(): void;
focusLastVisibleItem(): void;
selectFocusedOrFirstVisibleItem(): void;
private selectFirstVisibleItem;
private focusInitialItem;
filterItems(query: string): void;
private handleFilteredFocus;
getValue(): string | string[];
getValues(): string[];
setValue(value: string): void;
setValues(values: string[]): void;
clearSelection(): void;
private handleOutsideClick;
private createAnimationPromise;
private resolveAnimation;
private updateAllAttributes;
private updateValueDisplay;
private dispatchValueChange;
private dispatchStateChange;
private cleanup;
getSelectedItems(): PxmSelectItem[];
getAllItems(): PxmSelectItem[];
isOpen(): boolean;
isMultiple(): boolean;
/**
* Update icon rotation when icon-rotation attribute changes
*/
private updateIconRotation;
/**
* Update icon for the select trigger
*/
private updateIcon;
selectFocusedItem(): void;
handleTypeAhead(key: string): boolean;
private focusFirstTypeAheadMatch;
private focusNextTypeAheadMatch;
private itemMatchesTypeAhead;
clearTypeAhead(): void;
}
export { PxmSelect };
export { PxmSelectTrigger, PxmSelectValue, PxmSelectContent, PxmSelectItem, PxmSelectGroup, PxmSelectLabel, PxmSelectSeparator, PxmSelectSearch };
export type { SelectEventDetail, SelectItemEventDetail, SelectStateChangeDetail, SelectConfig, SelectState, SelectItemData };