Fix pointer/more-info inconsistencies for entity rows (#10025)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Philip Allgaier 2021-12-02 10:48:30 +01:00 committed by GitHub
parent acf4d59fde
commit cf062bf0f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 203 additions and 255 deletions

View File

@ -188,8 +188,9 @@ export const DOMAINS_WITH_MORE_INFO = [
"weather",
];
/** Domains that show no more info dialog. */
export const DOMAINS_HIDE_MORE_INFO = [
/** Domains that do not show the default more info dialog content (e.g. the attribute section)
* and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */
export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"input_number",
"input_select",
"input_text",
@ -198,6 +199,30 @@ export const DOMAINS_HIDE_MORE_INFO = [
"select",
];
/** Domains that render an input element instead of a text value when rendered in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
* should not act as a click target to open the more info dialog (the row name and state icon
* still do of course) as the click might instead e.g. activate the input field that this row shows.
*/
export const DOMAINS_INPUT_ROW = [
"cover",
"fan",
"humidifier",
"input_boolean",
"input_datetime",
"input_number",
"input_select",
"input_text",
"light",
"lock",
"media_player",
"number",
"scene",
"script",
"select",
];
/** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"];

View File

@ -1,6 +1,6 @@
import type { HassEntity } from "home-assistant-js-websocket";
import {
DOMAINS_HIDE_MORE_INFO,
DOMAINS_HIDE_DEFAULT_MORE_INFO,
DOMAINS_WITH_MORE_INFO,
} from "../../common/const";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
@ -40,7 +40,7 @@ export const domainMoreInfoType = (domain: string): string => {
if (DOMAINS_WITH_MORE_INFO.includes(domain)) {
return domain;
}
if (DOMAINS_HIDE_MORE_INFO.includes(domain)) {
if (DOMAINS_HIDE_DEFAULT_MORE_INFO.includes(domain)) {
return "hidden";
}
return "default";

View File

@ -9,7 +9,7 @@ import {
import { property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { DOMAINS_INPUT_ROW } from "../../../common/const";
import { toggleAttribute } from "../../../common/dom/toggle_attribute";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
@ -31,6 +31,8 @@ class HuiGenericEntityRow extends LitElement {
@property() public secondaryText?: string;
@property({ type: Boolean }) public hideName = false;
protected render(): TemplateResult {
if (!this.hass || !this.config) {
return html``;
@ -47,10 +49,10 @@ class HuiGenericEntityRow extends LitElement {
`;
}
const pointer =
(this.config.tap_action && this.config.tap_action.action !== "none") ||
(this.config.entity &&
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this.config.entity)));
const domain = computeDomain(this.config.entity);
const pointer = !(
this.config.tap_action && this.config.tap_action.action !== "none"
);
const hasSecondary = this.secondaryText || this.config.secondary_info;
const name = this.config.name ?? computeStateName(stateObj);
@ -72,75 +74,90 @@ class HuiGenericEntityRow extends LitElement {
})}
tabindex=${ifDefined(pointer ? "0" : undefined)}
></state-badge>
<div
class="info ${classMap({
pointer,
"text-content": !hasSecondary,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this.config!.hold_action),
hasDoubleClick: hasAction(this.config!.double_tap_action),
})}
.title=${name}
>
${name}
${hasSecondary
? html`
<div class="secondary">
${this.secondaryText ||
(this.config.secondary_info === "entity-id"
? stateObj.entity_id
: this.config.secondary_info === "last-changed"
? html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_changed}
capitalize
></ha-relative-time>
`
: this.config.secondary_info === "last-updated"
? html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_updated}
capitalize
></ha-relative-time>
`
: this.config.secondary_info === "last-triggered"
? stateObj.attributes.last_triggered
? html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.attributes.last_triggered}
capitalize
></ha-relative-time>
`
: this.hass.localize(
"ui.panel.lovelace.cards.entities.never_triggered"
)
: this.config.secondary_info === "position" &&
stateObj.attributes.current_position !== undefined
? `${this.hass.localize("ui.card.cover.position")}: ${
stateObj.attributes.current_position
}`
: this.config.secondary_info === "tilt-position" &&
stateObj.attributes.current_tilt_position !== undefined
? `${this.hass.localize("ui.card.cover.tilt_position")}: ${
stateObj.attributes.current_tilt_position
}`
: this.config.secondary_info === "brightness" &&
stateObj.attributes.brightness
? html`${Math.round(
(stateObj.attributes.brightness / 255) * 100
)}
%`
: "")}
</div>
`
: ""}
</div>
<slot></slot>
${!this.hideName
? html` <div
class="info ${classMap({
pointer,
"text-content": !hasSecondary,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this.config!.hold_action),
hasDoubleClick: hasAction(this.config!.double_tap_action),
})}
.title=${name}
>
${this.config.name || computeStateName(stateObj)}
${hasSecondary
? html`
<div class="secondary">
${this.secondaryText ||
(this.config.secondary_info === "entity-id"
? stateObj.entity_id
: this.config.secondary_info === "last-changed"
? html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_changed}
capitalize
></ha-relative-time>
`
: this.config.secondary_info === "last-updated"
? html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_updated}
capitalize
></ha-relative-time>
`
: this.config.secondary_info === "last-triggered"
? stateObj.attributes.last_triggered
? html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.attributes.last_triggered}
capitalize
></ha-relative-time>
`
: this.hass.localize(
"ui.panel.lovelace.cards.entities.never_triggered"
)
: this.config.secondary_info === "position" &&
stateObj.attributes.current_position !== undefined
? `${this.hass.localize("ui.card.cover.position")}: ${
stateObj.attributes.current_position
}`
: this.config.secondary_info === "tilt-position" &&
stateObj.attributes.current_tilt_position !== undefined
? `${this.hass.localize(
"ui.card.cover.tilt_position"
)}: ${stateObj.attributes.current_tilt_position}`
: this.config.secondary_info === "brightness" &&
stateObj.attributes.brightness
? html`${Math.round(
(stateObj.attributes.brightness / 255) * 100
)}
%`
: "")}
</div>
`
: ""}
</div>`
: html``}
${!DOMAINS_INPUT_ROW.includes(domain)
? html` <div
class="text-content ${classMap({
pointer,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this.config!.hold_action),
hasDoubleClick: hasAction(this.config!.double_tap_action),
})}
>
<slot></slot>
</div>`
: html`<slot></slot>`}
`;
}

View File

@ -49,10 +49,8 @@ class HuiClimateEntityRow extends LitElement implements LovelaceRow {
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
<ha-climate-state
.hass=${this.hass}
.stateObj=${stateObj}
></ha-climate-state>
<ha-climate-state .hass=${this.hass} .stateObj=${stateObj}>
</ha-climate-state>
</hui-generic-entity-row>
`;
}

View File

@ -132,7 +132,6 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
return css`
:host {
display: block;
cursor: pointer;
}
.flex {
display: flex;

View File

@ -9,11 +9,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/entity/state-badge";
import "../../../components/ha-paper-dropdown-menu";
@ -23,13 +19,10 @@ import {
InputSelectEntity,
setInputSelectOption,
} from "../../../data/input_select";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { LovelaceRow } from "./types";
@ -68,42 +61,28 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
`;
}
const pointer =
(this._config.tap_action && this._config.tap_action.action !== "none") ||
(this._config.entity &&
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
return html`
<state-badge
.stateObj=${stateObj}
.stateColor=${this._config.state_color}
.overrideIcon=${this._config.icon}
.overrideImage=${this._config.image}
class=${classMap({
pointer,
})}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex=${ifDefined(pointer ? "0" : undefined)}
></state-badge>
<ha-paper-dropdown-menu
.label=${this._config.name || computeStateName(stateObj)}
.value=${stateObj.state}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
@iron-select=${this._selectedChanged}
@click=${stopPropagation}
<hui-generic-entity-row
.hass=${this.hass}
.config=${this._config}
hideName
>
<paper-listbox slot="dropdown-content">
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) => html` <paper-item>${option}</paper-item> `
)
: ""}
</paper-listbox>
</ha-paper-dropdown-menu>
<ha-paper-dropdown-menu
.label=${this._config.name || computeStateName(stateObj)}
.value=${stateObj.state}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
@iron-select=${this._selectedChanged}
@click=${stopPropagation}
>
<paper-listbox slot="dropdown-content">
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) => html` <paper-item>${option}</paper-item> `
)
: ""}
</paper-listbox>
</ha-paper-dropdown-menu>
</hui-generic-entity-row>
`;
}
@ -129,10 +108,6 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
}
}
private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action!);
}
static get styles(): CSSResultGroup {
return css`
:host {

View File

@ -1,12 +1,5 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
@ -80,14 +73,6 @@ class HuiInputTextEntityRow extends LitElement implements LovelaceRow {
ev.target.blur();
}
static get styles(): CSSResultGroup {
return css`
:host {
cursor: pointer;
}
`;
}
}
declare global {

View File

@ -9,24 +9,17 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/entity/state-badge";
import "../../../components/ha-paper-dropdown-menu";
import { UNAVAILABLE } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import { SelectEntity, setSelectOption } from "../../../data/select";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { LovelaceRow } from "./types";
@ -65,52 +58,39 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
`;
}
const pointer =
(this._config.tap_action && this._config.tap_action.action !== "none") ||
(this._config.entity &&
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
return html`
<state-badge
.stateObj=${stateObj}
.overrideIcon=${this._config.icon}
.overrideImage=${this._config.image}
class=${classMap({
pointer,
})}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex=${ifDefined(pointer ? "0" : undefined)}
></state-badge>
<ha-paper-dropdown-menu
.label=${this._config.name || computeStateName(stateObj)}
.disabled=${stateObj.state === UNAVAILABLE}
@iron-select=${this._selectedChanged}
@click=${stopPropagation}
<hui-generic-entity-row
.hass=${this.hass}
.config=${this._config}
hideName
>
<paper-listbox slot="dropdown-content">
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) =>
html`
<paper-item .option=${option}
>${(stateObj.attributes.device_class &&
<ha-paper-dropdown-menu
.label=${this._config.name || computeStateName(stateObj)}
.disabled=${stateObj.state === UNAVAILABLE}
@iron-select=${this._selectedChanged}
@click=${stopPropagation}
>
<paper-listbox slot="dropdown-content">
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) =>
html`
<paper-item .option=${option}
>${(stateObj.attributes.device_class &&
this.hass!.localize(
`component.select.state.${stateObj.attributes.device_class}.${option}`
)) ||
this.hass!.localize(
`component.select.state.${stateObj.attributes.device_class}.${option}`
)) ||
this.hass!.localize(
`component.select.state._.${option}`
) ||
option}</paper-item
>
`
)
: ""}
</paper-listbox>
</ha-paper-dropdown-menu>
`component.select.state._.${option}`
) ||
option}</paper-item
>
`
)
: ""}
</paper-listbox>
</ha-paper-dropdown-menu>
</hui-generic-entity-row>
`;
}
@ -136,10 +116,6 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
}
}
private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action!);
}
static get styles(): CSSResultGroup {
return css`
:host {

View File

@ -7,16 +7,9 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
@ -54,37 +47,13 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
`;
}
const pointer =
(this._config.tap_action && this._config.tap_action.action !== "none") ||
(this._config.entity &&
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
<div
class="text-content ${classMap({
pointer,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config.hold_action),
hasDoubleClick: hasAction(this._config.double_tap_action),
})}
>
${computeStateDisplay(
this.hass!.localize,
stateObj,
this.hass.locale
)}
</div>
${computeStateDisplay(this.hass!.localize, stateObj, this.hass.locale)}
</hui-generic-entity-row>
`;
}
private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action);
}
static get styles(): CSSResultGroup {
return css`
div {

View File

@ -9,8 +9,6 @@ import {
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { formatNumber } from "../../../common/number/format_number";
@ -67,10 +65,9 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
`;
}
const pointer =
(this._config.tap_action && this._config.tap_action.action !== "none") ||
(this._config.entity &&
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
const pointer = !(
this._config.tap_action && this._config.tap_action.action !== "none"
);
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
@ -106,7 +103,16 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
>
${this._config.name || computeStateName(stateObj)}
</div>
<div class="attributes">
<div
class="attributes ${classMap({
pointer,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
>
<div>
${UNAVAILABLE_STATES.includes(stateObj.state)
? computeStateDisplay(

View File

@ -63,22 +63,20 @@ class HuiAttributeRow extends LitElement implements LovelaceRow {
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
<div>
${this._config.prefix}
${this._config.format && checkValidDate(date)
? html` <hui-timestamp-display
.hass=${this.hass}
.ts=${date}
.format=${this._config.format}
capitalize
></hui-timestamp-display>`
: typeof attribute === "number"
? formatNumber(attribute, this.hass.locale)
: attribute !== undefined
? formatAttributeValue(this.hass, attribute)
: "-"}
${this._config.suffix}
</div>
${this._config.prefix}
${this._config.format && checkValidDate(date)
? html` <hui-timestamp-display
.hass=${this.hass}
.ts=${date}
.format=${this._config.format}
capitalize
></hui-timestamp-display>`
: typeof attribute === "number"
? formatNumber(attribute, this.hass.locale)
: attribute !== undefined
? formatAttributeValue(this.hass, attribute)
: "-"}
${this._config.suffix}
</hui-generic-entity-row>
`;
}