Add tabindex to lovelace elements (#4160)

* tabindex

* use action handler

* circular focus test

* address comment

* add focus styling to other elements

* add focus styling to cards

* style glance card entities

* Add back light/thermo changes that were lost in rebase

* Remove unused import

* lint

* lint

* 💄 tweak focus style for glance entities

* 💄 apply styling to focused state-label-badges
This commit is contained in:
Ian Richardson 2020-01-11 04:50:43 -06:00 committed by Bram Kragten
parent 2848e3a63b
commit 0f487ae4bf
22 changed files with 124 additions and 10 deletions

View File

@ -15,6 +15,7 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
id="progress" id="progress"
progress="[[progress]]" progress="[[progress]]"
on-click="buttonTapped" on-click="buttonTapped"
tabindex="0"
><slot></slot ><slot></slot
></ha-progress-button> ></ha-progress-button>
`; `;

View File

@ -4,6 +4,8 @@ import {
TemplateResult, TemplateResult,
customElement, customElement,
property, property,
CSSResult,
css,
} from "lit-element"; } from "lit-element";
import "../../../components/entity/ha-state-label-badge"; import "../../../components/entity/ha-state-label-badge";
@ -45,6 +47,7 @@ export class HuiStateLabelBadge extends LitElement implements LovelaceBadge {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
></ha-state-label-badge> ></ha-state-label-badge>
`; `;
} }
@ -52,6 +55,21 @@ export class HuiStateLabelBadge extends LitElement implements LovelaceBadge {
private _handleAction(ev: ActionHandlerEvent) { private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action!); handleAction(this, this.hass!, this._config!, ev.detail.action!);
} }
static get styles(): CSSResult {
return css`
ha-state-label-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 4px;
}
ha-state-label-badge {
display: inline-block;
padding: 4px;
margin: -4px 0 -4px 0;
}
`;
}
} }
declare global { declare global {

View File

@ -134,6 +134,7 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
> >
${this._config.show_icon ${this._config.show_icon
? html` ? html`
@ -195,6 +196,11 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
font-size: 1.2rem; font-size: 1.2rem;
} }
ha-card:focus {
outline: none;
background: var(--divider-color);
}
ha-icon { ha-icon {
width: 40%; width: 40%;
height: auto; height: auto;

View File

@ -104,6 +104,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return html` return html`
<ha-card <ha-card
@click="${this._handleClick}" @click="${this._handleClick}"
tabindex="0"
style=${styleMap({ style=${styleMap({
"--base-unit": this._baseUnit, "--base-unit": this._baseUnit,
})} })}
@ -228,6 +229,10 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
} }
ha-card:focus {
outline: none;
background: var(--divider-color);
}
.container { .container {
width: calc(var(--base-unit) * 4); width: calc(var(--base-unit) * 4);
height: calc(var(--base-unit) * 2); height: calc(var(--base-unit) * 2);

View File

@ -167,6 +167,13 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
margin-bottom: 12px; margin-bottom: 12px;
width: var(--glance-column-width, 20%); width: var(--glance-column-width, 20%);
} }
.entity:focus {
outline: none;
background: var(--divider-color);
border-radius: 14px;
padding: 4px;
margin: -4px 0;
}
.entity div { .entity div {
width: 100%; width: 100%;
text-align: center; text-align: center;
@ -207,6 +214,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
hasHold: hasAction(entityConf.hold_action), hasHold: hasAction(entityConf.hold_action),
hasDoubleClick: hasAction(entityConf.double_tap_action), hasDoubleClick: hasAction(entityConf.double_tap_action),
})} })}
tabindex="0"
> >
${this._config!.show_name !== false ${this._config!.show_name !== false
? html` ? html`

View File

@ -94,7 +94,8 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
<paper-icon-button <paper-icon-button
icon="hass:dots-vertical" icon="hass:dots-vertical"
class="more-info" class="more-info"
@click="${this._handleMoreInfo}" @click=${this._handleMoreInfo}
tabindex="0"
></paper-icon-button> ></paper-icon-button>
<div id="controls"> <div id="controls">
@ -121,6 +122,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
color: this._computeColor(stateObj), color: this._computeColor(stateObj),
})} })}
@click=${this._handleClick} @click=${this._handleClick}
tabindex="0"
></paper-icon-button> ></paper-icon-button>
</div> </div>
</div> </div>

View File

@ -147,6 +147,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
></div> ></div>
<paper-icon-button <paper-icon-button
@click=${this._fitMap} @click=${this._fitMap}
tabindex="0"
icon="hass:image-filter-center-focus" icon="hass:image-filter-center-focus"
title="Reset focus" title="Reset focus"
></paper-icon-button> ></paper-icon-button>

View File

@ -86,6 +86,7 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
class="${classMap({ class="${classMap({
clickable: Boolean( clickable: Boolean(
this._config.tap_action || this._config.hold_action this._config.tap_action || this._config.hold_action

View File

@ -156,6 +156,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
class=${classMap({ class=${classMap({
clickable: stateObj.state !== UNAVAILABLE, clickable: stateObj.state !== UNAVAILABLE,
})} })}

View File

@ -168,6 +168,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
.config=${this._config} .config=${this._config}
.hass=${this.hass} .hass=${this.hass}
.image=${this._config.image} .image=${this._config.image}
@ -230,6 +231,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
hasHold: hasAction(entityConf.hold_action), hasHold: hasAction(entityConf.hold_action),
hasDoubleClick: hasAction(entityConf.double_tap_action), hasDoubleClick: hasAction(entityConf.double_tap_action),
})} })}
tabindex="0"
.config=${entityConf} .config=${entityConf}
class="${classMap({ class="${classMap({
"state-on": !STATES_OFF.has(stateObj.state), "state-on": !STATES_OFF.has(stateObj.state),
@ -318,6 +320,11 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
padding-bottom: 4px; padding-bottom: 4px;
padding-top: 4px; padding-top: 4px;
} }
ha-icon:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
.state { .state {
display: block; display: block;
font-size: 12px; font-size: 12px;

View File

@ -21,6 +21,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import { PlantStatusCardConfig, PlantAttributeTarget } from "./types"; import { PlantStatusCardConfig, PlantAttributeTarget } from "./types";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive";
const SENSORS = { const SENSORS = {
moisture: "hass:water", moisture: "hass:water",
@ -119,7 +120,9 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
(item) => html` (item) => html`
<div <div
class="attributes" class="attributes"
@click="${this._handleMoreInfo}" @action=${this._handleMoreInfo}
.actionHandler=${actionHandler()}
tabindex="0"
.value="${item}" .value="${item}"
> >
<div> <div>
@ -206,6 +209,12 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
cursor: pointer; cursor: pointer;
} }
.attributes:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
.attributes div { .attributes div {
text-align: center; text-align: center;
} }

View File

@ -25,6 +25,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { fetchRecent } from "../../../data/history"; import { fetchRecent } from "../../../data/history";
import { SensorCardConfig } from "./types"; import { SensorCardConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import { actionHandler } from "../common/directives/action-handler-directive";
const midPoint = ( const midPoint = (
_Ax: number, _Ax: number,
@ -241,7 +242,11 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
graph = ""; graph = "";
} }
return html` return html`
<ha-card @click="${this._handleClick}"> <ha-card
@action=${this._handleClick}
.actionHandler=${actionHandler()}
tabindex="0"
>
<div class="flex"> <div class="flex">
<div class="icon"> <div class="icon">
<ha-icon <ha-icon
@ -353,6 +358,11 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
cursor: pointer; cursor: pointer;
} }
ha-card:focus {
outline: none;
background: var(--divider-color);
}
.flex { .flex {
display: flex; display: flex;
} }

View File

@ -26,6 +26,7 @@ import {
} from "../../../data/shopping-list"; } from "../../../data/shopping-list";
import { ShoppingListCardConfig, SensorCardConfig } from "./types"; import { ShoppingListCardConfig, SensorCardConfig } from "./types";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive";
@customElement("hui-shopping-list-card") @customElement("hui-shopping-list-card")
class HuiShoppingListCard extends LitElement implements LovelaceCard { class HuiShoppingListCard extends LitElement implements LovelaceCard {
@ -165,7 +166,9 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
</span> </span>
<ha-icon <ha-icon
class="clearall" class="clearall"
@click="${this._clearItems}" @action=${this._clearItems}
.actionHandler=${actionHandler()}
tabindex="0"
icon="hass:notification-clear-all" icon="hass:notification-clear-all"
.title="${this.hass!.localize( .title="${this.hass!.localize(
"ui.panel.lovelace.cards.shopping-list.clear_items" "ui.panel.lovelace.cards.shopping-list.clear_items"

View File

@ -33,6 +33,7 @@ import {
CLIMATE_PRESET_NONE, CLIMATE_PRESET_NONE,
} from "../../../data/climate"; } from "../../../data/climate";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { actionHandler } from "../common/directives/action-handler-directive";
const modeIcons: { [mode in HvacMode]: string } = { const modeIcons: { [mode in HvacMode]: string } = {
auto: "hass:calendar-repeat", auto: "hass:calendar-repeat",
@ -218,6 +219,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
icon="hass:dots-vertical" icon="hass:dots-vertical"
class="more-info" class="more-info"
@click=${this._handleMoreInfo} @click=${this._handleMoreInfo}
tabindex="0"
></paper-icon-button> ></paper-icon-button>
<div id="controls"> <div id="controls">
@ -364,9 +366,10 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
class="${classMap({ "selected-icon": currentMode === mode })}" class="${classMap({ "selected-icon": currentMode === mode })}"
.mode="${mode}" .mode="${mode}"
.icon="${modeIcons[mode]}" .icon="${modeIcons[mode]}"
@click="${this._handleModeClick}" @action=${this._handleAction}
.actionHandler=${actionHandler()}
tabindex="0" tabindex="0"
></paper-icon-button> ></ha-icon>
`; `;
} }
@ -376,7 +379,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
}); });
} }
private _handleModeClick(e: MouseEvent): void { private _handleAction(e: MouseEvent): void {
this.hass!.callService("climate", "set_hvac_mode", { this.hass!.callService("climate", "set_hvac_mode", {
entity_id: this._config!.entity, entity_id: this._config!.entity,
hvac_mode: (e.currentTarget as any).mode, hvac_mode: (e.currentTarget as any).mode,

View File

@ -23,6 +23,7 @@ import { computeRTL } from "../../../common/util/compute_rtl";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { toggleAttribute } from "../../../common/dom/toggle_attribute"; import { toggleAttribute } from "../../../common/dom/toggle_attribute";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive";
const cardinalDirections = [ const cardinalDirections = [
"N", "N",
@ -141,7 +142,11 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
: undefined; : undefined;
return html` return html`
<ha-card @click="${this.handleClick}"> <ha-card
@action=${this._handleAction}
.actionHandler=${actionHandler()}
tabindex="0"
>
<div class="header"> <div class="header">
${this.hass.localize(`state.weather.${stateObj.state}`) || ${this.hass.localize(`state.weather.${stateObj.state}`) ||
stateObj.state} stateObj.state}
@ -274,7 +279,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
return hasConfigOrEntityChanged(this, changedProps); return hasConfigOrEntityChanged(this, changedProps);
} }
private handleClick(): void { private _handleAction(): void {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
} }

View File

@ -171,6 +171,11 @@ class HuiGenericEntityRow extends LitElement {
state-badge { state-badge {
flex: 0 0 40px; flex: 0 0 40px;
} }
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
:host([rtl]) .flex { :host([rtl]) .flex {
margin-left: 0; margin-left: 0;
margin-right: 16px; margin-right: 16px;

View File

@ -45,6 +45,7 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
></ha-icon> ></ha-icon>
`; `;
} }
@ -58,6 +59,11 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
:host { :host {
cursor: pointer; cursor: pointer;
} }
ha-icon:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`; `;
} }
} }

View File

@ -56,6 +56,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
></hui-image> ></hui-image>
`; `;
} }
@ -70,6 +71,11 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
hui-image { hui-image {
-webkit-user-select: none !important; -webkit-user-select: none !important;
} }
hui-image:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`; `;
} }

View File

@ -70,6 +70,7 @@ export class HuiStateBadgeElement extends LitElement
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
></ha-state-label-badge> ></ha-state-label-badge>
`; `;
} }

View File

@ -66,6 +66,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
.overrideIcon=${this._config.icon} .overrideIcon=${this._config.icon}
></state-badge> ></state-badge>
`; `;
@ -76,6 +77,11 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
:host { :host {
cursor: pointer; cursor: pointer;
} }
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`; `;
} }

View File

@ -65,6 +65,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
})} })}
tabindex="0"
> >
${this._config.prefix}${stateObj ${this._config.prefix}${stateObj
? computeStateDisplay( ? computeStateDisplay(
@ -90,6 +91,11 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
padding: 8px; padding: 8px;
white-space: nowrap; white-space: nowrap;
} }
div:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`; `;
} }
} }

View File

@ -141,7 +141,6 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
margin-left: 16px; margin-left: 16px;
flex: 1; flex: 1;
} }
paper-item { paper-item {
cursor: pointer; cursor: pointer;
min-width: 200px; min-width: 200px;
@ -149,6 +148,11 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
.pointer { .pointer {
cursor: pointer; cursor: pointer;
} }
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`; `;
} }