Skip to content

Commit

Permalink
ha-form-multi_select accessibility improvements (#21023)
Browse files Browse the repository at this point in the history
  • Loading branch information
karwosts authored Dec 23, 2024
1 parent d4cbfd9 commit 147098f
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 34 deletions.
85 changes: 51 additions & 34 deletions src/components/ha-form/ha-form-multi_select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import "../ha-button-menu";
import "../ha-check-list-item";
import type { HaCheckListItem } from "../ha-check-list-item";
import "../ha-checkbox";
import type { HaCheckbox } from "../ha-checkbox";
import "../ha-formfield";
import "../ha-svg-icon";
import "../ha-icon-button";
import "../ha-textfield";
import "../ha-md-button-menu";
import "../ha-md-menu-item";

import type {
HaFormElement,
HaFormMultiSelectData,
Expand Down Expand Up @@ -73,13 +75,10 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
}

return html`
<ha-button-menu
<ha-md-button-menu
.disabled=${this.disabled}
fixed
@opened=${this._handleOpen}
@closed=${this._handleClose}
multi
activatable
@opening=${this._handleOpen}
@closing=${this._handleClose}
>
<ha-textfield
slot="trigger"
Expand All @@ -94,28 +93,56 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
.disabled=${this.disabled}
tabindex="-1"
></ha-textfield>
<ha-svg-icon
<ha-icon-button
slot="trigger"
.label=${this.label}
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
></ha-svg-icon>
></ha-icon-button>
${options.map((item: string | [string, string]) => {
const value = optionValue(item);
const selected = data.includes(value);
return html`<ha-check-list-item
left
.selected=${selected}
.activated=${selected}
@request-selected=${this._selectedChanged}
return html`<ha-md-menu-item
type="option"
aria-checked=${selected}
.value=${value}
.disabled=${this.disabled}
.action=${selected ? "remove" : "add"}
.activated=${selected}
@click=${this._toggleItem}
@keydown=${this._keydown}
keep-open
>
<ha-checkbox
slot="start"
tabindex="-1"
.checked=${selected}
></ha-checkbox>
${optionLabel(item)}
</ha-check-list-item>`;
</ha-md-menu-item>`;
})}
</ha-button-menu>
</ha-md-button-menu>
`;
}

protected _keydown(ev) {
if (ev.code === "Space" || ev.code === "Enter") {
ev.preventDefault();
this._toggleItem(ev);
}
}

protected _toggleItem(ev) {
const oldData = this.data || [];
let newData: string[];
if (ev.currentTarget.action === "add") {
newData = [...oldData, ev.currentTarget.value];
} else {
newData = oldData.filter((d) => d !== ev.currentTarget.value);
}
fireEvent(this, "value-changed", {
value: newData,
});
}

protected firstUpdated() {
this.updateComplete.then(() => {
const { formElement, mdcRoot } =
Expand All @@ -139,17 +166,6 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
}
}

private _selectedChanged(ev: CustomEvent): void {
ev.stopPropagation();
if (ev.detail.source === "property") {
return;
}
this._handleValueChanged(
(ev.target as HaCheckListItem).value,
ev.detail.selected
);
}

private _valueChanged(ev: CustomEvent): void {
const { value, checked } = ev.target as HaCheckbox;
this._handleValueChanged(value, checked);
Expand Down Expand Up @@ -195,7 +211,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
:host([own-margin]) {
margin-bottom: 5px;
}
ha-button-menu {
ha-md-button-menu {
display: block;
cursor: pointer;
}
Expand All @@ -208,22 +224,23 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
}
ha-textfield {
display: block;
width: 100%;
pointer-events: none;
}
ha-svg-icon {
ha-icon-button {
color: var(--input-dropdown-icon-color);
position: absolute;
right: 1em;
top: 1em;
top: 4px;
cursor: pointer;
inset-inline-end: 1em;
inset-inline-start: initial;
direction: var(--direction);
}
:host([opened]) ha-svg-icon {
:host([opened]) ha-icon-button {
color: var(--primary-color);
}
:host([opened]) ha-button-menu {
:host([opened]) ha-md-button-menu {
--mdc-text-field-idle-line-color: var(--input-hover-line-color);
--mdc-text-field-label-ink-color: var(--primary-color);
}
Expand Down
18 changes: 18 additions & 0 deletions src/components/ha-md-button-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
import { fireEvent } from "../common/dom/fire_event";
import type { HaIconButton } from "./ha-icon-button";
import "./ha-menu";
import type { HaMenu } from "./ha-menu";
Expand Down Expand Up @@ -40,12 +41,22 @@ export class HaMdButtonMenu extends LitElement {
<ha-menu
.positioning=${this.positioning}
.hasOverflow=${this.hasOverflow}
@opening=${this._handleOpening}
@closing=${this._handleClosing}
>
<slot></slot>
</ha-menu>
`;
}

private _handleOpening(): void {
fireEvent(this, "opening", undefined, { composed: false });
}

private _handleClosing(): void {
fireEvent(this, "closing", undefined, { composed: false });
}

private _handleClick(): void {
if (this.disabled) {
return;
Expand Down Expand Up @@ -88,3 +99,10 @@ declare global {
"ha-md-button-menu": HaMdButtonMenu;
}
}

declare global {
interface HASSDomEvents {
opening: undefined;
closing: undefined;
}
}

0 comments on commit 147098f

Please sign in to comment.