Fix broken numeric text entry in state-card-input_number (#14812)

fixes undefined
This commit is contained in:
karwosts 2022-12-27 12:57:47 -08:00 committed by GitHub
parent e175c7ba3c
commit 419f23879a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 169 additions and 200 deletions

View File

@ -1,200 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior";
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import "../components/entity/state-info";
import "../components/ha-slider";
import "../components/ha-textfield";
class StateCardInputNumber extends mixinBehaviors(
[IronResizableBehavior],
PolymerElement
) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
ha-slider {
margin-left: auto;
}
.state {
@apply --paper-font-body1;
color: var(--primary-text-color);
display: flex;
align-items: center;
justify-content: end;
}
.sliderstate {
min-width: 45px;
}
[hidden] {
display: none !important;
}
ha-textfield {
text-align: right;
margin-left: auto;
}
</style>
<div class="horizontal justified layout" id="input_number_card">
${this.stateInfoTemplate}
<ha-slider
min="[[min]]"
max="[[max]]"
value="{{value}}"
step="[[step]]"
hidden="[[hiddenslider]]"
pin
on-change="selectedValueChanged"
on-click="stopPropagation"
id="slider"
ignore-bar-touch=""
>
</ha-slider>
<ha-textfield
no-label-float=""
auto-validate=""
pattern="[0-9]+([\\.][0-9]+)?"
step="[[step]]"
min="[[min]]"
max="[[max]]"
value="[[value]]"
type="number"
on-change="selectedValueChanged"
on-click="stopPropagation"
on-input="onInput"
hidden="[[hiddenbox]]"
suffix="[[stateObj.attributes.unit_of_measurement]]"
>
</ha-textfield>
<div
id="sliderstate"
class="state sliderstate"
hidden="[[hiddenslider]]"
>
[[formattedState]]
</div>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
ready() {
super.ready();
if (typeof ResizeObserver === "function") {
const ro = new ResizeObserver((entries) => {
entries.forEach(() => {
this.hiddenState();
});
});
ro.observe(this.$.input_number_card);
} else {
this.addEventListener("iron-resize", this.hiddenState);
}
}
static get properties() {
return {
hass: Object,
hiddenbox: {
type: Boolean,
value: true,
},
hiddenslider: {
type: Boolean,
value: true,
},
inDialog: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
observer: "stateObjectChanged",
},
min: {
type: Number,
value: 0,
},
max: {
type: Number,
value: 100,
},
maxlength: {
type: Number,
value: 3,
},
step: Number,
value: Number,
formattedState: String,
mode: String,
};
}
hiddenState() {
if (this.mode !== "slider") return;
const sliderwidth = this.$.slider.offsetWidth;
if (sliderwidth < 100) {
this.$.sliderstate.hidden = true;
} else if (sliderwidth >= 145) {
this.$.sliderstate.hidden = false;
}
}
stateObjectChanged(newVal) {
const prevMode = this.mode;
this.setProperties({
min: Number(newVal.attributes.min),
max: Number(newVal.attributes.max),
step: Number(newVal.attributes.step),
value: Number(newVal.state),
formattedState: computeStateDisplay(
this.hass.localize,
newVal,
this.hass.locale,
this.hass.entities,
newVal.state
),
mode: String(newVal.attributes.mode),
maxlength: String(newVal.attributes.max).length,
hiddenbox: newVal.attributes.mode !== "box",
hiddenslider: newVal.attributes.mode !== "slider",
});
if (this.mode === "slider" && prevMode !== "slider") {
this.hiddenState();
}
}
onInput(ev) {
this.value = ev.target.value;
}
selectedValueChanged() {
if (this.value === Number(this.stateObj.state)) {
return;
}
this.hass.callService("input_number", "set_value", {
value: this.value,
entity_id: this.stateObj.entity_id,
});
}
stopPropagation(ev) {
ev.stopPropagation();
}
}
customElements.define("state-card-input_number", StateCardInputNumber);

View File

@ -0,0 +1,169 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { HassEntity } from "home-assistant-js-websocket";
import { customElement, property } from "lit/decorators";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import { computeRTLDirection } from "../common/util/compute_rtl";
import { debounce } from "../common/util/debounce";
import "../components/ha-slider";
import "../components/ha-textfield";
import "../components/entity/state-info";
import { isUnavailableState } from "../data/entity";
import { setValue } from "../data/input_text";
import { HomeAssistant } from "../types";
import { installResizeObserver } from "../panels/lovelace/common/install-resize-observer";
@customElement("state-card-input_number")
class StateCardInputNumber extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
private _loaded?: boolean;
private _updated?: boolean;
private _resizeObserver?: ResizeObserver;
public connectedCallback(): void {
super.connectedCallback();
if (this._updated && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
public disconnectedCallback(): void {
this._resizeObserver?.disconnect();
}
protected firstUpdated(): void {
this._updated = true;
if (this.isConnected && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
protected render(): TemplateResult {
return html`
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
${this.stateObj.attributes.mode === "slider"
? html`
<div class="flex">
<ha-slider
.disabled=${isUnavailableState(this.stateObj.state)}
.dir=${computeRTLDirection(this.hass)}
.step=${Number(this.stateObj.attributes.step)}
.min=${Number(this.stateObj.attributes.min)}
.max=${Number(this.stateObj.attributes.max)}
.value=${this.stateObj.state}
pin
@change=${this._selectedValueChanged}
ignore-bar-touch
></ha-slider>
<span class="state">
${computeStateDisplay(
this.hass.localize,
this.stateObj,
this.hass.locale,
this.hass.entities,
this.stateObj.state
)}
</span>
</div>
`
: html`
<div class="flex state">
<ha-textfield
.disabled=${isUnavailableState(this.stateObj.state)}
pattern="[0-9]+([\\.][0-9]+)?"
.step=${Number(this.stateObj.attributes.step)}
.min=${Number(this.stateObj.attributes.min)}
.max=${Number(this.stateObj.attributes.max)}
.value=${Number(this.stateObj.state).toString()}
.suffix=${this.stateObj.attributes.unit_of_measurement || ""}
type="number"
@change=${this._selectedValueChanged}
>
</ha-textfield>
</div>
`}
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
display: flex;
}
.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;
}
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 installResizeObserver();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
}
if (this.isConnected) {
this._resizeObserver.observe(this);
}
}
private _selectedValueChanged(ev: Event): void {
if ((ev.target as HTMLInputElement).value !== this.stateObj.state) {
setValue(
this.hass!,
this.stateObj.entity_id,
(ev.target as HTMLInputElement).value
);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-input_number": StateCardInputNumber;
}
}