Restore screen readers support on pickers (#25553)

This commit is contained in:
Paul Bottein 2025-05-22 11:51:12 +02:00 committed by GitHub
parent 9b7db191a6
commit 3355986585
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 62 additions and 11 deletions

View File

@ -317,6 +317,7 @@ export class HaEntityPicker extends LitElement {
const secondary = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(isRTL ? " ◂ " : " ▸ ");
const a11yLabel = [deviceName, entityName].filter(Boolean).join(" - ");
return {
id: entityId,
@ -332,6 +333,7 @@ export class HaEntityPicker extends LitElement {
friendlyName,
entityId,
].filter(Boolean) as string[],
a11y_label: a11yLabel,
stateObj: stateObj,
};
});

View File

@ -267,6 +267,7 @@ export class HaStatisticPicker extends LitElement {
const secondary = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(isRTL ? " ◂ " : " ▸ ");
const a11yLabel = [deviceName, entityName].filter(Boolean).join(" - ");
const sortingPrefix = `${TYPE_ORDER.indexOf("entity")}`;
output.push({
@ -274,6 +275,7 @@ export class HaStatisticPicker extends LitElement {
statistic_id: id,
primary,
secondary,
a11y_label: a11yLabel,
stateObj: stateObj,
type: "entity",
sorting_label: [sortingPrefix, deviceName, entityName].join("_"),

View File

@ -0,0 +1,24 @@
import type { PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { HaTextField } from "./ha-textfield";
@customElement("ha-combo-box-textfield")
export class HaComboBoxTextField extends HaTextField {
@property({ type: Boolean, attribute: "disable-set-value" })
public disableSetValue = false;
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (changedProps.has("value")) {
if (this.disableSetValue) {
this.value = changedProps.get("value") as string;
}
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-combo-box-textfield": HaComboBoxTextField;
}
}

View File

@ -12,11 +12,12 @@ import type {
import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles";
import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import type { HomeAssistant } from "../types";
import "./ha-combo-box-item";
import "./ha-combo-box-textfield";
import "./ha-icon-button";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
@ -108,9 +109,14 @@ export class HaComboBox extends LitElement {
@property({ type: Boolean, attribute: "hide-clear-icon" })
public hideClearIcon = false;
@property({ type: Boolean, attribute: "clear-initial-value" })
public clearInitialValue = false;
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
@query("ha-textfield", true) private _inputElement!: HaTextField;
@query("ha-combo-box-textfield", true) private _inputElement!: HaTextField;
@state({ type: Boolean }) private _disableSetValue = false;
private _overlayMutationObserver?: MutationObserver;
@ -171,7 +177,7 @@ export class HaComboBox extends LitElement {
@value-changed=${this._valueChanged}
attr-for-value="value"
>
<ha-textfield
<ha-combo-box-textfield
label=${ifDefined(this.label)}
placeholder=${ifDefined(this.placeholder)}
?disabled=${this.disabled}
@ -191,9 +197,10 @@ export class HaComboBox extends LitElement {
.invalid=${this.invalid}
.helper=${this.helper}
helperPersistent
.disableSetValue=${this._disableSetValue}
>
<slot name="icon" slot="leadingIcon"></slot>
</ha-textfield>
</ha-combo-box-textfield>
${this.value && !this.hideClearIcon
? html`<ha-svg-icon
role="button"
@ -249,6 +256,18 @@ export class HaComboBox extends LitElement {
fireEvent(this, "opened-changed", { value: ev.detail.value });
}, 0);
if (this.clearInitialValue) {
this.setTextFieldValue("");
if (opened) {
// Wait 100ms to be sure vaddin-combo-box-light already tried to set the value
setTimeout(() => {
this._disableSetValue = false;
}, 100);
} else {
this._disableSetValue = true;
}
}
if (opened) {
const overlay = document.querySelector<HTMLElement>(
"vaadin-combo-box-overlay"
@ -342,10 +361,10 @@ export class HaComboBox extends LitElement {
position: relative;
--vaadin-combo-box-overlay-max-height: calc(45vh - 56px);
}
ha-textfield {
ha-combo-box-textfield {
width: 100%;
}
ha-textfield > ha-icon-button {
ha-combo-box-textfield > ha-icon-button {
--mdc-icon-button-size: 24px;
padding: 2px;
color: var(--secondary-text-color);

View File

@ -2,6 +2,7 @@ import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import type { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import type { HomeAssistant } from "../types";
import "./ha-combo-box-item";
@ -74,6 +75,7 @@ export class HaGenericPicker extends LitElement {
<ha-picker-field
type="button"
compact
aria-label=${ifDefined(this.label)}
@click=${this.open}
@clear=${this._clear}
.placeholder=${this.placeholder}

View File

@ -18,6 +18,7 @@ import "./ha-icon";
export interface PickerComboBoxItem {
id: string;
primary: string;
a11y_label?: string;
secondary?: string;
search_labels?: string[];
sorting_label?: string;
@ -27,7 +28,7 @@ export interface PickerComboBoxItem {
// Hack to force empty label to always display empty value by default in the search field
export interface PickerComboBoxItemWithLabel extends PickerComboBoxItem {
label: "";
a11y_label: string;
}
const NO_MATCHING_ITEMS_FOUND_ID = "___no_matching_items_found___";
@ -109,7 +110,7 @@ export class HaPickerComboBox extends LitElement {
id: NO_MATCHING_ITEMS_FOUND_ID,
primary: label || localize("ui.components.combo-box.no_match"),
icon_path: mdiMagnify,
label: "",
a11y_label: label || localize("ui.components.combo-box.no_match"),
})
);
@ -118,7 +119,7 @@ export class HaPickerComboBox extends LitElement {
return items.map<PickerComboBoxItemWithLabel>((item) => ({
...item,
label: "",
a11y_label: item.a11y_label || item.primary,
}));
};
@ -128,7 +129,7 @@ export class HaPickerComboBox extends LitElement {
const sortedItems = items
.map<PickerComboBoxItemWithLabel>((item) => ({
...item,
label: "",
a11y_label: item.a11y_label || item.primary,
}))
.sort((entityA, entityB) =>
caseInsensitiveStringCompare(
@ -175,7 +176,8 @@ export class HaPickerComboBox extends LitElement {
<ha-combo-box
item-id-path="id"
item-value-path="id"
item-label-path="label"
item-label-path="a11y_label"
clear-initial-value
.hass=${this.hass}
.value=${this._value}
.label=${this.label}