diff --git a/src/components/ha-control-select.ts b/src/components/ha-control-select.ts index 5f706e2d12..da38ed5eb9 100644 --- a/src/components/ha-control-select.ts +++ b/src/components/ha-control-select.ts @@ -34,43 +34,62 @@ export class HaControlSelect extends LitElement { @state() private _activeIndex?: number; - private _handleFocus() { - if (this.disabled) return; - this._activeIndex = - (this.value != null - ? this.options?.findIndex((option) => option.value === this.value) - : undefined) ?? 0; + private _handleFocus(ev: FocusEvent) { + if (this.disabled || !this.options) return; + + // Only handle focus if coming to the container + if (ev.target === ev.currentTarget) { + // Focus the selected radio or the first one + const selectedIndex = + this.value != null + ? this.options.findIndex((option) => option.value === this.value) + : -1; + const focusIndex = selectedIndex !== -1 ? selectedIndex : 0; + this._focusOption(focusIndex); + } } - private _handleBlur() { - this._activeIndex = undefined; + private _focusOption(index: number) { + this._activeIndex = index; + this.requestUpdate(); + this.updateComplete.then(() => { + const option = this.shadowRoot?.querySelector( + `#option-${this.options![index].value}` + ) as HTMLElement; + option?.focus(); + }); + } + + private _handleBlur(ev: FocusEvent) { + // Only reset if focus is leaving the entire component + if (!this.contains(ev.relatedTarget as Node)) { + this._activeIndex = undefined; + } } private _handleKeydown(ev: KeyboardEvent) { - if (!this.options || this._activeIndex == null || this.disabled) return; - const value = this.options[this._activeIndex].value; + if (!this.options || this.disabled) return; + + let newIndex = this._activeIndex ?? 0; + switch (ev.key) { case " ": case "Enter": - this.value = value; - fireEvent(this, "value-changed", { value }); + if (this._activeIndex != null) { + const value = this.options[this._activeIndex].value; + this.value = value; + fireEvent(this, "value-changed", { value }); + } break; case "ArrowUp": case "ArrowLeft": - this._activeIndex = - this._activeIndex <= 0 - ? this.options.length - 1 - : this._activeIndex - 1; + newIndex = newIndex <= 0 ? this.options.length - 1 : newIndex - 1; + this._focusOption(newIndex); break; case "ArrowDown": case "ArrowRight": - this._activeIndex = (this._activeIndex + 1) % this.options.length; - break; - case "Home": - this._activeIndex = 0; - break; - case "End": - this._activeIndex = this.options.length - 1; + newIndex = (newIndex + 1) % this.options.length; + this._focusOption(newIndex); break; default: return; @@ -96,25 +115,22 @@ export class HaControlSelect extends LitElement { private _handleOptionMouseUp(ev: MouseEvent) { ev.preventDefault(); - this._activeIndex = undefined; + } + + private _handleOptionFocus(ev: FocusEvent) { + if (this.disabled) return; + const value = (ev.target as any).value; + this._activeIndex = this.options?.findIndex( + (option) => option.value === value + ); } protected render() { - const activeValue = - this._activeIndex != null - ? this.options?.[this._activeIndex]?.value - : undefined; - const activedescendant = - activeValue != null ? `option-${activeValue}` : undefined; - return html`