@rcarls/rc-select 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-select-BumY2uQl.js","sources":["../src/rc-select.styles.ts","../src/rc-select.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const selectStyles = css`\n :host {\n display: inline-block;\n }\n\n #anchor {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--rc-select-gap, var(--rc-control-gap, 0.25em));\n }\n\n #trigger {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--rc-select-gap, var(--rc-control-gap, 0.25em));\n min-block-size: var(--rc-select-control-block-size, var(--rc-control-block-size, auto));\n padding: var(--rc-select-padding-block, var(--rc-control-padding-block, 0.25em))\n var(--rc-select-padding-inline, var(--rc-control-padding-inline, 0.5em));\n min-width: 8em;\n cursor: default;\n user-select: none;\n border: var(--rc-select-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));\n border-radius: var(--rc-select-radius, var(--rc-control-radius, var(--rc-radius-sm, 0.125em)));\n background: var(--rc-field, Field);\n color: var(--rc-field-text, FieldText);\n font-family: var(--rc-font-family, inherit);\n font-size: var(--rc-font-size, inherit);\n line-height: var(--rc-line-height, normal);\n transition:\n background-color var(--rc-motion-duration, 120ms),\n border-color var(--rc-motion-duration, 120ms),\n box-shadow var(--rc-motion-duration, 120ms);\n\n /* Keyboard focus indicator */\n outline: none;\n &:focus-visible {\n outline: var(--rc-focus-ring, auto);\n outline-offset: var(--rc-focus-ring-offset, 0);\n }\n }\n\n [part='value-display'] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n [part='toggle-icon'] {\n flex-shrink: 0;\n font-size: 0.75em;\n }\n\n /* Chips — the whole chip is the remove button for a larger touch target */\n [part='chip'] {\n display: inline-flex;\n align-items: center;\n gap: var(--rc-select-chip-gap, calc(var(--rc-control-gap, 0.25em) * 0.8));\n padding: var(--rc-select-chip-padding-block, 0.1em)\n var(--rc-select-chip-padding-inline, 0.3em);\n border: var(--rc-select-chip-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));\n border-radius: var(--rc-select-chip-radius, var(--rc-radius-md, 0.25em));\n background: var(--rc-button-bg, ButtonFace);\n color: var(--rc-button-text, ButtonText);\n font: inherit;\n font-size: 0.875em;\n cursor: pointer;\n\n &:hover {\n background: var(--rc-highlight, Highlight);\n color: var(--rc-highlight-text, HighlightText);\n }\n\n &:focus-visible {\n outline: var(--rc-focus-ring, auto);\n outline-offset: var(--rc-focus-ring-offset, 0);\n }\n }\n\n /* Decorative × icon inside the chip — no interaction, aria-hidden */\n [part='chip-remove'] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n font-size: 0.85em;\n pointer-events: none;\n }\n\n /* Hidden native select slot */\n slot[name='select'] {\n display: none;\n }\n\n /* Listbox popup — positioned by AnchorController via adoptedStyleSheets */\n rc-listbox {\n max-height: var(--rc-select-max-height, 20em);\n overflow-y: auto;\n background: var(--rc-surface, Canvas);\n border: var(--rc-select-listbox-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));\n border-radius: var(--rc-select-listbox-radius, var(--rc-control-radius, 0));\n box-shadow: var(--rc-select-shadow, var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)));\n color: var(--rc-field-text, FieldText);\n padding-block: var(--rc-select-listbox-padding-block, var(--rc-control-padding-block, 0.25em));\n\n &:not(:popover-open) {\n display: none;\n }\n }\n\n /* Options within the listbox */\n rc-listbox [part~='option'] {\n display: flex;\n align-items: center;\n gap: var(--rc-item-gap, 0.4em);\n padding: var(--rc-item-padding-block, 0.3em) var(--rc-item-padding-inline, 0.75em);\n cursor: default;\n\n /* display: flex overrides [hidden]'s browser-default display:none — restore it explicitly */\n &[hidden] { display: none; }\n\n &:not([hidden]):not([aria-disabled='true']):hover {\n background: var(--rc-highlight, Highlight);\n color: var(--rc-highlight-text, HighlightText);\n }\n\n &[data-active]:not([aria-disabled='true']) {\n background: var(--rc-highlight, Highlight);\n color: var(--rc-highlight-text, HighlightText);\n outline: var(--rc-focus-ring, 2px solid var(--rc-accent, Highlight));\n outline-offset: -2px;\n }\n\n &[aria-disabled='true'] {\n opacity: var(--rc-disabled-opacity, 0.5);\n cursor: not-allowed;\n }\n }\n\n /* Selection checkmark — reserves space whether visible or not */\n rc-listbox [part~='option-checkmark'] {\n flex-shrink: 0;\n width: 1em;\n text-align: center;\n font-size: 0.85em;\n visibility: hidden;\n }\n\n rc-listbox [part~='option'][aria-selected='true'] [part~='option-checkmark'] {\n visibility: visible;\n }\n\n rc-listbox [part~='create-option'] {\n font-style: italic;\n border-top: 1px solid var(--rc-border-color, ButtonBorder);\n margin-top: 0.25em;\n padding-top: 0.3em;\n }\n`;\n\nexport default selectStyles;\n","import { LitElement, html, nothing } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport {\n ActiveDescendantController,\n AnchorController,\n} from '@rcarls/rc-common';\nimport type { RCListbox, ListboxOption } from '@rcarls/rc-listbox';\nimport { selectStyles } from './rc-select.styles.js';\n\nexport type RCSelectValue = string | string[];\n\nexport interface RCSelectChangeEvent {\n value: RCSelectValue;\n selectedValues: string[];\n selectedOptions: ListboxOption[];\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-select': RCSelect;\n }\n}\n\n/**\n * A fully-accessible, form-associated select/combobox component.\n *\n * Wraps a native `<select slot=\"select\">` as the form value reflector while\n * rendering a custom trigger and popup listbox. Follows the WAI-ARIA APG\n * Combobox pattern (select-only variant) with JS-computed popup placement\n * and `aria-activedescendant` for virtual keyboard navigation.\n *\n * @slot select - Required. A native `<select>` element used for form submission\n * and as the source of truth for options, multiple, and disabled state.\n * @slot display - Optional. Replaces the default value label in the trigger.\n * @slot toggle-icon - Optional. Replaces the default chevron icon.\n *\n * @fires rc-select-change - When user interaction changes selection.\n * `detail: { value, selectedValues, selectedOptions }`\n * @fires rc-select-open - When the popup opens.\n * @fires rc-select-close - When the popup closes.\n *\n * @csspart anchor - The flex container wrapping chips and trigger.\n * @csspart trigger - The combobox trigger element (div).\n * @csspart chips - The chips group container (when multiple).\n * @csspart chip - Individual chip (when multiple).\n * @csspart chip-label - Text label inside a chip.\n * @csspart chip-remove - Remove button inside a chip.\n * @csspart value-display - The text label showing selected value(s).\n * @csspart toggle-icon - The chevron/arrow icon container.\n *\n * @cssprop [--rc-select-max-height=20em] - Maximum popup height.\n * @cssprop [--rc-select-control-block-size=var(--rc-control-block-size)] - Trigger block size.\n * @cssprop [--rc-select-padding-block=var(--rc-control-padding-block)] - Trigger block-axis padding.\n * @cssprop [--rc-select-padding-inline=var(--rc-control-padding-inline)] - Trigger inline-axis padding.\n * @cssprop [--rc-select-gap=var(--rc-control-gap)] - Gap between trigger content, chips, and icon.\n * @cssprop [--rc-select-radius=var(--rc-control-radius)] - Trigger border radius.\n * @cssprop [--rc-select-border=var(--rc-border)] - Trigger border.\n * @cssprop [--rc-select-listbox-radius=var(--rc-control-radius)] - Popup listbox border radius.\n * @cssprop [--rc-select-listbox-padding-block=var(--rc-control-padding-block)] - Popup listbox block padding.\n * @cssprop [--rc-select-chip-radius=var(--rc-radius-md)] - Multi-select chip border radius.\n * @cssprop [--rc-select-chip-padding-block=0.1em] - Multi-select chip block-axis padding.\n * @cssprop [--rc-select-chip-padding-inline=0.3em] - Multi-select chip inline-axis padding.\n */\nexport class RCSelect extends LitElement {\n static override styles = selectStyles;\n\n @property({ type: Boolean, reflect: true }) open = false;\n @property({ type: Boolean, reflect: true }) multiple = false;\n @property({ type: Boolean, reflect: true }) disabled = false;\n @property() placeholder = '';\n @property() display: 'auto' | 'chips' | 'compact' = 'auto';\n\n @query('#anchor') protected _$anchor!: HTMLElement;\n @query('#trigger') protected _$trigger!: HTMLElement;\n @query('#listbox') protected _$listbox!: RCListbox;\n\n @state() protected _selectedValues: Set<string> = new Set();\n @state() private _chipNavIndex = -1;\n @state() private _options: ListboxOption[] = [];\n\n protected _selectRef: WeakRef<HTMLSelectElement> | null = null;\n private _mutationObserver: MutationObserver | null = null;\n private _defaultValue: RCSelectValue | undefined;\n private _propertyOptions: ListboxOption[] | undefined;\n private _value: RCSelectValue | undefined;\n private _selectionInitialized = false;\n private _typeAheadBuffer = '';\n private _typeAheadTimer = 0;\n\n protected _adc = new ActiveDescendantController(this, {\n host: () => this._$trigger ?? null,\n items: () => this._$listbox?.navigableItems ?? [],\n });\n\n protected _anchorCtrl = new AnchorController(this, {\n anchor: () => this._$anchor ?? null,\n floating: () => this._$listbox ?? null,\n shadowHost: () => this,\n placement: 'bottom-start',\n offset: 2,\n });\n\n override connectedCallback() {\n super.connectedCallback();\n document.addEventListener('click', this._onDocClick, { capture: true });\n document.addEventListener('keydown', this._onDocKeyDown, { capture: true });\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('click', this._onDocClick, { capture: true });\n document.removeEventListener('keydown', this._onDocKeyDown, {\n capture: true,\n });\n this._mutationObserver?.disconnect();\n }\n\n // ── Popup ────────────────────────────────────────────────────────────────────\n\n openPopup() {\n if (this.open || this.disabled) return;\n this.open = true;\n this._$listbox.showPopover();\n this._anchorCtrl.update();\n this.dispatchEvent(\n new CustomEvent('rc-select-open', { bubbles: true, composed: true }),\n );\n this.requestUpdate();\n }\n\n closePopup(returnFocus = true) {\n if (!this.open) return;\n this.open = false;\n this._$listbox.hidePopover();\n this._adc.clear();\n if (returnFocus) this._$trigger?.focus();\n this.dispatchEvent(\n new CustomEvent('rc-select-close', { bubbles: true, composed: true }),\n );\n }\n\n /**\n * Current selection. Host writes update selection silently; user interaction\n * emits `rc-select-change`.\n */\n get value(): RCSelectValue {\n return this.multiple\n ? this.selectedValues\n : (this.selectedValues[0] ?? '');\n }\n\n set value(value: RCSelectValue | undefined) {\n const oldValue = this._value;\n\n this._value = value;\n\n if (value === undefined) {\n this.requestUpdate('value', oldValue);\n return;\n }\n\n this._selectionInitialized = true;\n this._applySelection(this._normalizeValue(value));\n this.requestUpdate('value', oldValue);\n }\n\n /** Initial uncontrolled selection, applied before user or native state owns the value. */\n get defaultValue(): RCSelectValue | undefined {\n return this._defaultValue;\n }\n\n set defaultValue(value: RCSelectValue | undefined) {\n const oldValue = this._defaultValue;\n\n this._defaultValue = value;\n\n if (!this._selectionInitialized && this._value === undefined && value !== undefined) {\n this._applySelection(this._normalizeValue(value));\n }\n\n this.requestUpdate('defaultValue', oldValue);\n }\n\n /** Programmatic option source. Omit to derive options from the slotted `<select>`. */\n get options(): ListboxOption[] | undefined {\n return this._propertyOptions ? [...this._propertyOptions] : undefined;\n }\n\n set options(options: ListboxOption[] | undefined) {\n const oldValue = this._propertyOptions;\n\n this._propertyOptions = options ? [...options] : undefined;\n this._syncOptions(this._currentOptions());\n this._mirrorOptionsToNativeSelect();\n this.requestUpdate('options', oldValue);\n }\n\n /** Selected values as a consistently-array-shaped convenience getter. */\n get selectedValues(): string[] {\n return [...this._selectedValues];\n }\n\n protected get selectedOptions(): ListboxOption[] {\n return this._selectedOptionsFor(this.selectedValues);\n }\n\n protected _applySelection(values: string[]): void {\n const selectedValues = this.multiple ? values : values.slice(0, 1);\n\n this._selectedValues = new Set(selectedValues);\n this._$listbox?.setSelectedValues(selectedValues);\n this._syncNativeSelect();\n this.requestUpdate();\n }\n\n // ── Document-level listeners ─────────────────────────────────────────────────\n\n private _onDocClick = (e: MouseEvent) => {\n if (!this.open) return;\n if (!e.composedPath().includes(this)) this.closePopup(false);\n };\n\n private _onDocKeyDown = (e: KeyboardEvent) => {\n if (!this.open) return;\n if (e.key === 'Escape') {\n e.preventDefault();\n this.closePopup();\n }\n };\n\n // ── Slot: native <select> ────────────────────────────────────────────────────\n\n protected _handleSelectSlotChange(e: Event) {\n const slot = e.target as HTMLSlotElement;\n const sel =\n slot\n .assignedElements()\n .find(\n (el): el is HTMLSelectElement => el instanceof HTMLSelectElement,\n ) ?? null;\n\n // Disconnect synchronously so the old observer stops immediately.\n this._mutationObserver?.disconnect();\n this._mutationObserver = null;\n this._selectRef = sel ? new WeakRef(sel) : null;\n\n if (!sel) return;\n\n // Defer all DOM reads/mutations so this handler is instantaneous when\n // slotchange fires synchronously inside a framework reactive update pass\n // (e.g. SolidJS runUpdates on second+ mount, when shadow DOM already exists).\n queueMicrotask(() => {\n if (!sel.isConnected) return;\n this.multiple = sel.multiple;\n this.disabled = sel.disabled;\n this._syncOptionsFromSelect(sel);\n this._mutationObserver = new MutationObserver(() => {\n const s = this._selectRef?.deref();\n if (!s) return;\n\n this.multiple = s.multiple;\n this.disabled = s.disabled;\n\n if (this._propertyOptions !== undefined) {\n this._applySelectionFromCurrentSource();\n return;\n }\n\n this._syncOptionsFromSelect(s);\n });\n this._mutationObserver.observe(sel, {\n childList: true,\n subtree: true,\n attributes: true,\n });\n this._syncAccessibleName(sel);\n });\n }\n\n protected _syncOptionsFromSelect(sel: HTMLSelectElement) {\n if (this._propertyOptions !== undefined) {\n this._syncOptions(this._propertyOptions);\n this._mirrorOptionsToNativeSelect();\n this._applySelectionFromCurrentSource();\n return;\n }\n\n const options = this._optionsFromSelect(sel);\n\n this._syncOptions(options);\n this._applySelectionFromCurrentSource();\n this._syncAccessibleName(sel);\n }\n\n private _applySelectionFromCurrentSource(): void {\n if (this._value !== undefined) {\n this._applySelection(this._normalizeValue(this._value));\n return;\n }\n\n if (!this._selectionInitialized && this._defaultValue !== undefined) {\n this._selectionInitialized = true;\n this._applySelection(this._normalizeValue(this._defaultValue));\n return;\n }\n\n const selected = this._selectionInitialized\n ? this._selectedValuesFromNativeSelect()\n : this._defaultSelectedValuesFromNativeSelect();\n\n this._selectionInitialized = true;\n this._applySelection(selected);\n }\n\n private _syncOptions(options: ListboxOption[]): void {\n this._options = [...options];\n if (this._$listbox) this._$listbox.options = this._options;\n }\n\n private _currentOptions(): ListboxOption[] {\n if (this._propertyOptions !== undefined) return this._propertyOptions;\n\n const sel = this._selectRef?.deref();\n\n return sel ? this._optionsFromSelect(sel) : [];\n }\n\n private _optionsFromSelect(sel: HTMLSelectElement): ListboxOption[] {\n const options: ListboxOption[] = [];\n\n for (const opt of sel.options) {\n if (!opt.value) continue;\n\n options.push({\n value: opt.value,\n label: opt.text,\n disabled: opt.disabled,\n });\n }\n\n return options;\n }\n\n private _selectedValuesFromNativeSelect(): string[] {\n const sel = this._selectRef?.deref();\n if (!sel) return this.selectedValues;\n\n return Array.from(sel.selectedOptions)\n .map((opt) => opt.value)\n .filter(Boolean);\n }\n\n private _defaultSelectedValuesFromNativeSelect(): string[] {\n const sel = this._selectRef?.deref();\n if (!sel) return this.selectedValues;\n\n return Array.from(sel.options)\n .filter((opt) => opt.defaultSelected)\n .map((opt) => opt.value)\n .filter(Boolean);\n }\n\n private _mirrorOptionsToNativeSelect(): void {\n const sel = this._selectRef?.deref();\n if (!sel || this._propertyOptions === undefined) return;\n\n this._mutationObserver?.disconnect();\n sel.replaceChildren();\n\n for (const opt of this._propertyOptions) {\n const optionEl = document.createElement('option');\n\n optionEl.value = opt.value;\n optionEl.text = opt.label;\n optionEl.disabled = opt.disabled ?? false;\n optionEl.selected = this._selectedValues.has(opt.value);\n\n sel.add(optionEl);\n }\n\n this._mutationObserver?.observe(sel, {\n childList: true,\n subtree: true,\n attributes: true,\n });\n }\n\n protected _addOption(option: ListboxOption): void {\n const options = this._options.some((opt) => opt.value === option.value)\n ? this._options.map((opt) => (opt.value === option.value ? option : opt))\n : [...this._options, option];\n\n if (this._propertyOptions !== undefined) {\n this._propertyOptions = options;\n }\n\n this._syncOptions(options);\n\n const sel = this._selectRef?.deref();\n if (!sel || Array.from(sel.options).some((opt) => opt.value === option.value)) return;\n\n const optionEl = document.createElement('option');\n\n optionEl.value = option.value;\n optionEl.text = option.label;\n optionEl.disabled = option.disabled ?? false;\n optionEl.selected = this._selectedValues.has(option.value);\n\n sel.add(optionEl);\n }\n\n private _normalizeValue(value: RCSelectValue): string[] {\n return Array.isArray(value) ? value : value ? [value] : [];\n }\n\n private _syncAccessibleName(sel: HTMLSelectElement): void {\n if (!this._$trigger) return;\n if (this._$trigger.hasAttribute('aria-label')) return; // don't clobber explicit\n\n const name =\n sel.getAttribute('aria-label') ??\n sel.labels?.[0]?.textContent?.trim() ??\n null;\n\n if (name) this._$trigger.setAttribute('aria-label', name);\n else this._$trigger.removeAttribute('aria-label');\n }\n\n // ── Selection ────────────────────────────────────────────────────────────────\n\n protected _handleListboxChange(e: CustomEvent) {\n const { optionValue, value, selected } = e.detail as {\n optionValue?: string;\n value: string | string[];\n selected: boolean;\n };\n const activatedValue = optionValue ?? (Array.isArray(value) ? value.at(-1) : value);\n if (!activatedValue) return;\n\n e.stopPropagation();\n\n if (this.multiple) {\n const next = new Set(this._selectedValues);\n if (selected) next.add(activatedValue);\n else next.delete(activatedValue);\n this._selectedValues = next;\n } else {\n this._selectedValues = selected ? new Set([activatedValue]) : new Set();\n if (selected) this.closePopup();\n }\n\n this._selectionInitialized = true;\n this._syncNativeSelect();\n this._$listbox.setSelectedValues(this.selectedValues);\n this._dispatchChange();\n }\n\n protected _removeValue(value: string) {\n const next = new Set(this._selectedValues);\n\n next.delete(value);\n\n this._selectedValues = next;\n this._$listbox.setSelectedValues([...next]);\n this._selectionInitialized = true;\n this._syncNativeSelect();\n this._dispatchChange();\n }\n\n protected _syncNativeSelect() {\n const sel = this._selectRef?.deref();\n if (!sel) return;\n for (const opt of sel.options) {\n opt.selected = this._selectedValues.has(opt.value);\n }\n }\n\n protected _dispatchChange(): void {\n this.dispatchEvent(\n new CustomEvent<RCSelectChangeEvent>('rc-select-change', {\n bubbles: true,\n composed: true,\n detail: {\n value: this.value,\n selectedValues: this.selectedValues,\n selectedOptions: this.selectedOptions,\n },\n }),\n );\n }\n\n private _selectedOptionsFor(values: string[]): ListboxOption[] {\n return values.map((value) => {\n return (\n this._options.find((opt) => opt.value === value) ?? {\n value,\n label: value,\n }\n );\n });\n }\n\n // ── Display helpers ──────────────────────────────────────────────────────────\n\n protected get _effectiveDisplay(): 'chips' | 'compact' {\n if (this.display === 'chips') return 'chips';\n if (this.display === 'compact') return 'compact';\n return window.matchMedia('(pointer: coarse)').matches ? 'compact' : 'chips';\n }\n\n protected _labelFor(value: string): string {\n const sel = this._selectRef?.deref();\n if (sel) {\n for (const opt of sel.options) {\n if (opt.value === value) return opt.text;\n }\n }\n return value;\n }\n\n protected get _displayLabel(): string {\n if (this._selectedValues.size === 0) return this.placeholder;\n if (this._effectiveDisplay === 'chips' && this.multiple)\n return this.placeholder;\n if (this._selectedValues.size === 1) {\n return this._labelFor([...this._selectedValues][0]);\n }\n // Compact multi: \"First, +N more\"\n const values = [...this._selectedValues];\n const first = this._labelFor(values[0]);\n return `${first}, +${values.length - 1} more`;\n }\n\n // ── Keyboard: trigger ────────────────────────────────────────────────────────\n\n protected _handleTriggerKeyDown(e: KeyboardEvent) {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (!this.open) {\n this.openPopup();\n this._adc.navigateToFirst();\n } else this._adc.navigate(1);\n break;\n case 'ArrowUp':\n e.preventDefault();\n if (!this.open) {\n this.openPopup();\n this._adc.navigateToLast();\n } else this._adc.navigate(-1);\n break;\n case 'Home':\n if (this.open) {\n e.preventDefault();\n this._adc.navigateToFirst();\n }\n break;\n case 'End':\n if (this.open) {\n e.preventDefault();\n this._adc.navigateToLast();\n }\n break;\n case ' ':\n case 'Enter':\n e.preventDefault();\n if (!this.open) {\n this.openPopup();\n this._adc.navigateToFirst();\n } else this._activateActive();\n break;\n case 'Tab':\n if (this.open) this.closePopup(false);\n break;\n case 'ArrowLeft':\n if (!this.open && this.multiple && this._selectedValues.size > 0) {\n e.preventDefault();\n this._enterChipNav();\n }\n break;\n default:\n if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {\n this._handleTypeAhead(e.key);\n }\n }\n }\n\n private _activateActive() {\n const item = this._adc.activeItem;\n if (item) {\n item.dispatchEvent(\n new PointerEvent('pointerdown', { bubbles: true, cancelable: true }),\n );\n }\n }\n\n // ── Type-ahead ───────────────────────────────────────────────────────────────\n\n private _handleTypeAhead(char: string) {\n clearTimeout(this._typeAheadTimer);\n this._typeAheadBuffer += char.toLowerCase();\n this._typeAheadTimer = window.setTimeout(() => {\n this._typeAheadBuffer = '';\n }, 500);\n\n const allOpts = this._$listbox?.allOptions ?? [];\n const match = allOpts.find(\n (o) =>\n !o.disabled && o.label.toLowerCase().startsWith(this._typeAheadBuffer),\n );\n if (!match) return;\n\n if (!this.open) {\n // Select-only type-ahead: immediately select the match\n if (!this.multiple) {\n this._selectionInitialized = true;\n this._applySelection([match.value]);\n this._dispatchChange();\n }\n } else {\n // Open popup: move virtual cursor to match\n const items = this._$listbox.navigableItems;\n const el = items.find(\n (el) => el.getAttribute('data-value') === match.value,\n );\n if (el) this._adc.navigateToItem(el);\n }\n }\n\n // ── Chip keyboard navigation ─────────────────────────────────────────────────\n\n private _enterChipNav() {\n const buttons = this._getChipButtons();\n if (buttons.length === 0) return;\n this._chipNavIndex = buttons.length - 1;\n buttons[this._chipNavIndex].focus();\n }\n\n private _getChipButtons(): HTMLButtonElement[] {\n return Array.from(\n this.renderRoot.querySelectorAll<HTMLButtonElement>(\n 'button[part~=\"chip\"]',\n ),\n );\n }\n\n protected _handleChipKeyDown(e: KeyboardEvent, value: string) {\n const buttons = this._getChipButtons();\n switch (e.key) {\n case 'ArrowLeft':\n e.preventDefault();\n if (this._chipNavIndex > 0) {\n this._chipNavIndex--;\n buttons[this._chipNavIndex]?.focus();\n }\n break;\n case 'ArrowRight':\n e.preventDefault();\n if (this._chipNavIndex < buttons.length - 1) {\n this._chipNavIndex++;\n buttons[this._chipNavIndex]?.focus();\n } else {\n this._chipNavIndex = -1;\n this._$trigger?.focus();\n }\n break;\n case 'Escape':\n e.preventDefault();\n this._chipNavIndex = -1;\n this._$trigger?.focus();\n break;\n case 'Delete':\n case 'Backspace':\n case 'Enter':\n case ' ':\n e.preventDefault();\n this._removeValue(value);\n this._chipNavIndex = -1;\n this._$trigger?.focus();\n break;\n }\n }\n\n // ── Trigger click ────────────────────────────────────────────────────────────\n\n protected _handleTriggerClick() {\n if (this.open) this.closePopup();\n else this.openPopup();\n }\n\n // ── Render ───────────────────────────────────────────────────────────────────\n\n protected override render() {\n const showChips =\n this.multiple &&\n this._effectiveDisplay === 'chips' &&\n this._selectedValues.size > 0;\n\n return html`\n <div id=\"anchor\" part=\"anchor\">\n <div\n id=\"trigger\"\n part=\"trigger\"\n role=\"combobox\"\n tabindex=${this.disabled ? '-1' : '0'}\n aria-haspopup=\"listbox\"\n aria-expanded=${this.open ? 'true' : 'false'}\n aria-controls=\"listbox\"\n aria-disabled=${this.disabled ? 'true' : 'false'}\n aria-label=${this.placeholder || nothing}\n @click=${this._handleTriggerClick}\n @keydown=${this._handleTriggerKeyDown}\n >\n ${showChips ? this._renderChips() : nothing}\n <span part=\"value-display\">${this._displayLabel}</span>\n <span part=\"toggle-icon\" aria-hidden=\"true\">\n <slot name=\"toggle-icon\">&#9660;</slot>\n </span>\n </div>\n </div>\n\n <rc-listbox\n id=\"listbox\"\n part=\"listbox\"\n popover=\"manual\"\n ?multiple=${this.multiple}\n checkmark\n @rc-listbox-change=${this._handleListboxChange}\n ></rc-listbox>\n\n <slot name=\"select\" @slotchange=${this._handleSelectSlotChange}></slot>\n `;\n }\n\n protected _renderChips() {\n return html`\n <span part=\"chips\" role=\"group\" aria-label=\"Selected items\">\n ${[...this._selectedValues].map((value) => {\n const label = this._labelFor(value);\n return html`\n <button\n type=\"button\"\n part=\"chip\"\n data-value=${value}\n tabindex=\"-1\"\n aria-label=${`Remove ${label}`}\n @click=${(e: MouseEvent) => {\n e.stopPropagation();\n this._removeValue(value);\n }}\n @keydown=${(e: KeyboardEvent) =>\n this._handleChipKeyDown(e, value)}\n >\n <span part=\"chip-label\">${label}</span\n ><span part=\"chip-remove\" aria-hidden=\"true\">&#215;</span>\n </button>\n `;\n })}\n </span>\n `;\n }\n}\n\nexport default RCSelect;\n"],"names":["selectStyles","css","_RCSelect","LitElement","ActiveDescendantController","AnchorController","returnFocus","value","oldValue","options","values","selectedValues","sel","el","selected","opt","optionEl","option","name","optionValue","activatedValue","next","item","char","match","o","buttons","showChips","html","nothing","label","e","RCSelect","__decorateClass","property","query","state"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import "@rcarls/rc-listbox/define";
2
+ import { R as e } from "./rc-select-BumY2uQl.js";
3
+ customElements.get("rc-select") || customElements.define("rc-select", e);
4
+ export {
5
+ e as RCSelect
6
+ };
7
+ //# sourceMappingURL=rc-select-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-select-define.js","sources":["../src/define.ts"],"sourcesContent":["import '@rcarls/rc-listbox/define';\nimport { RCSelect } from './index.js';\n\ncustomElements.get('rc-select') || customElements.define('rc-select', RCSelect);\n\nexport * from './index.js';\n"],"names":["RCSelect"],"mappings":";;AAGA,eAAe,IAAI,WAAW,KAAK,eAAe,OAAO,aAAaA,CAAQ;"}
@@ -0,0 +1,5 @@
1
+ import { R as r } from "./rc-select-BumY2uQl.js";
2
+ export {
3
+ r as RCSelect
4
+ };
5
+ //# sourceMappingURL=rc-select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-select.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1 @@
1
+ export * from './rc-select.js';
@@ -0,0 +1,130 @@
1
+ import { LitElement } from 'lit';
2
+ import { ActiveDescendantController, AnchorController } from '@rcarls/rc-common';
3
+ import { RCListbox, ListboxOption } from '@rcarls/rc-listbox';
4
+ export type RCSelectValue = string | string[];
5
+ export interface RCSelectChangeEvent {
6
+ value: RCSelectValue;
7
+ selectedValues: string[];
8
+ selectedOptions: ListboxOption[];
9
+ }
10
+ declare global {
11
+ interface HTMLElementTagNameMap {
12
+ 'rc-select': RCSelect;
13
+ }
14
+ }
15
+ /**
16
+ * A fully-accessible, form-associated select/combobox component.
17
+ *
18
+ * Wraps a native `<select slot="select">` as the form value reflector while
19
+ * rendering a custom trigger and popup listbox. Follows the WAI-ARIA APG
20
+ * Combobox pattern (select-only variant) with JS-computed popup placement
21
+ * and `aria-activedescendant` for virtual keyboard navigation.
22
+ *
23
+ * @slot select - Required. A native `<select>` element used for form submission
24
+ * and as the source of truth for options, multiple, and disabled state.
25
+ * @slot display - Optional. Replaces the default value label in the trigger.
26
+ * @slot toggle-icon - Optional. Replaces the default chevron icon.
27
+ *
28
+ * @fires rc-select-change - When user interaction changes selection.
29
+ * `detail: { value, selectedValues, selectedOptions }`
30
+ * @fires rc-select-open - When the popup opens.
31
+ * @fires rc-select-close - When the popup closes.
32
+ *
33
+ * @csspart anchor - The flex container wrapping chips and trigger.
34
+ * @csspart trigger - The combobox trigger element (div).
35
+ * @csspart chips - The chips group container (when multiple).
36
+ * @csspart chip - Individual chip (when multiple).
37
+ * @csspart chip-label - Text label inside a chip.
38
+ * @csspart chip-remove - Remove button inside a chip.
39
+ * @csspart value-display - The text label showing selected value(s).
40
+ * @csspart toggle-icon - The chevron/arrow icon container.
41
+ *
42
+ * @cssprop [--rc-select-max-height=20em] - Maximum popup height.
43
+ * @cssprop [--rc-select-control-block-size=var(--rc-control-block-size)] - Trigger block size.
44
+ * @cssprop [--rc-select-padding-block=var(--rc-control-padding-block)] - Trigger block-axis padding.
45
+ * @cssprop [--rc-select-padding-inline=var(--rc-control-padding-inline)] - Trigger inline-axis padding.
46
+ * @cssprop [--rc-select-gap=var(--rc-control-gap)] - Gap between trigger content, chips, and icon.
47
+ * @cssprop [--rc-select-radius=var(--rc-control-radius)] - Trigger border radius.
48
+ * @cssprop [--rc-select-border=var(--rc-border)] - Trigger border.
49
+ * @cssprop [--rc-select-listbox-radius=var(--rc-control-radius)] - Popup listbox border radius.
50
+ * @cssprop [--rc-select-listbox-padding-block=var(--rc-control-padding-block)] - Popup listbox block padding.
51
+ * @cssprop [--rc-select-chip-radius=var(--rc-radius-md)] - Multi-select chip border radius.
52
+ * @cssprop [--rc-select-chip-padding-block=0.1em] - Multi-select chip block-axis padding.
53
+ * @cssprop [--rc-select-chip-padding-inline=0.3em] - Multi-select chip inline-axis padding.
54
+ */
55
+ export declare class RCSelect extends LitElement {
56
+ static styles: import('lit').CSSResult;
57
+ open: boolean;
58
+ multiple: boolean;
59
+ disabled: boolean;
60
+ placeholder: string;
61
+ display: 'auto' | 'chips' | 'compact';
62
+ protected _$anchor: HTMLElement;
63
+ protected _$trigger: HTMLElement;
64
+ protected _$listbox: RCListbox;
65
+ protected _selectedValues: Set<string>;
66
+ private _chipNavIndex;
67
+ private _options;
68
+ protected _selectRef: WeakRef<HTMLSelectElement> | null;
69
+ private _mutationObserver;
70
+ private _defaultValue;
71
+ private _propertyOptions;
72
+ private _value;
73
+ private _selectionInitialized;
74
+ private _typeAheadBuffer;
75
+ private _typeAheadTimer;
76
+ protected _adc: ActiveDescendantController;
77
+ protected _anchorCtrl: AnchorController;
78
+ connectedCallback(): void;
79
+ disconnectedCallback(): void;
80
+ openPopup(): void;
81
+ closePopup(returnFocus?: boolean): void;
82
+ /**
83
+ * Current selection. Host writes update selection silently; user interaction
84
+ * emits `rc-select-change`.
85
+ */
86
+ get value(): RCSelectValue;
87
+ set value(value: RCSelectValue | undefined);
88
+ /** Initial uncontrolled selection, applied before user or native state owns the value. */
89
+ get defaultValue(): RCSelectValue | undefined;
90
+ set defaultValue(value: RCSelectValue | undefined);
91
+ /** Programmatic option source. Omit to derive options from the slotted `<select>`. */
92
+ get options(): ListboxOption[] | undefined;
93
+ set options(options: ListboxOption[] | undefined);
94
+ /** Selected values as a consistently-array-shaped convenience getter. */
95
+ get selectedValues(): string[];
96
+ protected get selectedOptions(): ListboxOption[];
97
+ protected _applySelection(values: string[]): void;
98
+ private _onDocClick;
99
+ private _onDocKeyDown;
100
+ protected _handleSelectSlotChange(e: Event): void;
101
+ protected _syncOptionsFromSelect(sel: HTMLSelectElement): void;
102
+ private _applySelectionFromCurrentSource;
103
+ private _syncOptions;
104
+ private _currentOptions;
105
+ private _optionsFromSelect;
106
+ private _selectedValuesFromNativeSelect;
107
+ private _defaultSelectedValuesFromNativeSelect;
108
+ private _mirrorOptionsToNativeSelect;
109
+ protected _addOption(option: ListboxOption): void;
110
+ private _normalizeValue;
111
+ private _syncAccessibleName;
112
+ protected _handleListboxChange(e: CustomEvent): void;
113
+ protected _removeValue(value: string): void;
114
+ protected _syncNativeSelect(): void;
115
+ protected _dispatchChange(): void;
116
+ private _selectedOptionsFor;
117
+ protected get _effectiveDisplay(): 'chips' | 'compact';
118
+ protected _labelFor(value: string): string;
119
+ protected get _displayLabel(): string;
120
+ protected _handleTriggerKeyDown(e: KeyboardEvent): void;
121
+ private _activateActive;
122
+ private _handleTypeAhead;
123
+ private _enterChipNav;
124
+ private _getChipButtons;
125
+ protected _handleChipKeyDown(e: KeyboardEvent, value: string): void;
126
+ protected _handleTriggerClick(): void;
127
+ protected render(): import('lit').TemplateResult<1>;
128
+ protected _renderChips(): import('lit').TemplateResult<1>;
129
+ }
130
+ export default RCSelect;
@@ -0,0 +1,2 @@
1
+ export declare const selectStyles: import('lit').CSSResult;
2
+ export default selectStyles;
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@rcarls/rc-select",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.0",
7
+ "description": "Headless select-only combobox backed by a native select element, built with Lit",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-select"
12
+ },
13
+ "homepage": "https://github.com/richardcarls/rc-webcomponents#readme",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-select.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-select/src/index.d.ts",
24
+ "default": "./dist/rc-select.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-select/src/define.d.ts",
30
+ "default": "./dist/rc-select-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-select/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-select-define.js"
37
+ ],
38
+ "customElements": "dist/custom-elements.json",
39
+ "scripts": {
40
+ "dev": "vite",
41
+ "build": "tsc && vite build && cem analyze",
42
+ "cem:analyze": "cem analyze",
43
+ "preview": "vite preview",
44
+ "test:browser": "vitest",
45
+ "test:browser:chrome": "vitest --project=chromium",
46
+ "test:browser:firefox": "vitest --project=firefox",
47
+ "yalc:publish": "yalc publish --push"
48
+ },
49
+ "dependencies": {
50
+ "@rcarls/rc-common": "workspace:^",
51
+ "@rcarls/rc-listbox": "workspace:^"
52
+ },
53
+ "devDependencies": {
54
+ "@custom-elements-manifest/analyzer": "0.11.0",
55
+ "@guanghechen/rollup-plugin-copy": "^6.0.9",
56
+ "@vitest/browser-playwright": "4.1.5",
57
+ "lit": "^3.0.0",
58
+ "playwright": "^1.56.0",
59
+ "rollup": "^4.60.2",
60
+ "typescript": "~5.9.3",
61
+ "vite": "^7.1.7",
62
+ "vite-plugin-dts": "^4.5.4",
63
+ "vitest": "^4.0.6",
64
+ "vitest-browser-lit": "^1.0.1"
65
+ },
66
+ "peerDependencies": {
67
+ "lit": "^3.0.0"
68
+ }
69
+ }