ha-frontend/src/panels/lovelace/entity-rows/hui-number-entity-row.ts

195 lines
5.4 KiB
TypeScript

import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-slider";
import "../../../components/ha-textfield";
import { UNAVAILABLE } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { loadPolyfillIfNeeded } from "../../../resources/resize-observer.polyfill";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { EntityConfig, LovelaceRow } from "./types";
@customElement("hui-number-entity-row")
class HuiNumberEntityRow extends LitElement implements LovelaceRow {
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: EntityConfig;
private _loaded?: boolean;
private _updated?: boolean;
private _resizeObserver?: ResizeObserver;
public setConfig(config: EntityConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
public connectedCallback(): void {
super.connectedCallback();
if (this._updated && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._resizeObserver?.disconnect();
}
protected firstUpdated(): void {
this._updated = true;
if (this.isConnected && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render() {
if (!this._config || !this.hass) {
return nothing;
}
const stateObj = this.hass.states[this._config.entity];
if (!stateObj) {
return html`
<hui-warning>
${createEntityNotFoundWarning(this.hass, this._config.entity)}
</hui-warning>
`;
}
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
${stateObj.attributes.mode === "slider" ||
(stateObj.attributes.mode === "auto" &&
(Number(stateObj.attributes.max) - Number(stateObj.attributes.min)) /
Number(stateObj.attributes.step) <=
256)
? html`
<div class="flex">
<ha-slider
labeled
.disabled=${stateObj.state === UNAVAILABLE}
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}
.max=${Number(stateObj.attributes.max)}
.value=${Number(stateObj.state)}
@change=${this._selectedValueChanged}
></ha-slider>
<span class="state">
${this.hass.formatEntityState(stateObj)}
</span>
</div>
`
: html`
<div class="flex state">
<ha-textfield
autoValidate
.disabled=${stateObj.state === UNAVAILABLE}
pattern="[0-9]+([\\.][0-9]+)?"
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}
.max=${Number(stateObj.attributes.max)}
.value=${stateObj.state}
.suffix=${stateObj.attributes.unit_of_measurement}
type="number"
@change=${this._selectedValueChanged}
></ha-textfield>
</div>
`}
</hui-generic-entity-row>
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
cursor: pointer;
display: block;
}
.flex {
display: flex;
align-items: center;
justify-content: flex-end;
flex-grow: 2;
}
.state {
min-width: 45px;
text-align: end;
}
ha-textfield {
text-align: end;
direction: ltr !important;
}
ha-slider {
width: 100%;
max-width: 200px;
}
`;
}
private async _initialLoad(): Promise<void> {
this._loaded = true;
await this.updateComplete;
this._measureCard();
}
private _measureCard() {
if (!this.isConnected) {
return;
}
const element = this.shadowRoot!.querySelector(".state") as HTMLElement;
if (!element) {
return;
}
element.hidden = this.clientWidth <= 300;
}
private async _attachObserver(): Promise<void> {
if (!this._resizeObserver) {
await loadPolyfillIfNeeded();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
}
if (this.isConnected) {
this._resizeObserver.observe(this);
}
}
private _selectedValueChanged(ev: Event): void {
const stateObj = this.hass!.states[this._config!.entity];
const target = ev.target as HTMLInputElement;
if (target.value !== stateObj.state && !target.validity.stepMismatch) {
setValue(this.hass!, stateObj.entity_id, target.value);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-number-entity-row": HuiNumberEntityRow;
}
}