mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-26 06:17:20 +00:00
Convert ha-climate-control
ot Lit and add tooltips to buttons (#10921)
Co-authored-by: Zack Barett <zackbarett@hey.com>
This commit is contained in:
parent
7c194d8910
commit
7d335d7d85
@ -1,2 +1,10 @@
|
|||||||
export const clamp = (value: number, min: number, max: number) =>
|
export const clamp = (value: number, min: number, max: number) =>
|
||||||
Math.min(Math.max(value, min), max);
|
Math.min(Math.max(value, min), max);
|
||||||
|
|
||||||
|
// Variant that only applies the clamping to a border if the border is defined
|
||||||
|
export const conditionalClamp = (value: number, min?: number, max?: number) => {
|
||||||
|
let result: number;
|
||||||
|
result = min ? Math.max(value, min) : value;
|
||||||
|
result = max ? Math.min(value, max) : value;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { EventsMixin } from "../mixins/events-mixin";
|
|
||||||
import "./ha-icon";
|
|
||||||
import "./ha-icon-button";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class HaClimateControl extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex iron-flex-alignment"></style>
|
|
||||||
<style>
|
|
||||||
/* local DOM styles go here */
|
|
||||||
:host {
|
|
||||||
@apply --layout-flex;
|
|
||||||
@apply --layout-horizontal;
|
|
||||||
@apply --layout-justified;
|
|
||||||
}
|
|
||||||
.in-flux#target_temperature {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
#target_temperature {
|
|
||||||
@apply --layout-self-center;
|
|
||||||
font-size: 200%;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
.control-buttons {
|
|
||||||
font-size: 200%;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-size: 32px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- local DOM goes here -->
|
|
||||||
<div id="target_temperature">[[value]] [[units]]</div>
|
|
||||||
<div class="control-buttons">
|
|
||||||
<div>
|
|
||||||
<ha-icon-button on-click="incrementValue">
|
|
||||||
<ha-icon icon="hass:chevron-up"></ha-icon>
|
|
||||||
</ha-icon-button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ha-icon-button on-click="decrementValue">
|
|
||||||
<ha-icon icon="hass:chevron-down"></ha-icon>
|
|
||||||
</ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
value: {
|
|
||||||
type: Number,
|
|
||||||
observer: "valueChanged",
|
|
||||||
},
|
|
||||||
units: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
min: {
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
max: {
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
step: {
|
|
||||||
type: Number,
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
temperatureStateInFlux(inFlux) {
|
|
||||||
this.$.target_temperature.classList.toggle("in-flux", inFlux);
|
|
||||||
}
|
|
||||||
|
|
||||||
_round(val) {
|
|
||||||
// round value to precision derived from step
|
|
||||||
// insired by https://github.com/soundar24/roundSlider/blob/master/src/roundslider.js
|
|
||||||
const s = this.step.toString().split(".");
|
|
||||||
return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
incrementValue() {
|
|
||||||
const newval = this._round(this.value + this.step);
|
|
||||||
if (this.value < this.max) {
|
|
||||||
this.last_changed = Date.now();
|
|
||||||
this.temperatureStateInFlux(true);
|
|
||||||
}
|
|
||||||
if (newval <= this.max) {
|
|
||||||
// If no initial target_temp
|
|
||||||
// this forces control to start
|
|
||||||
// from the min configured instead of 0
|
|
||||||
if (newval <= this.min) {
|
|
||||||
this.value = this.min;
|
|
||||||
} else {
|
|
||||||
this.value = newval;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.value = this.max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
decrementValue() {
|
|
||||||
const newval = this._round(this.value - this.step);
|
|
||||||
if (this.value > this.min) {
|
|
||||||
this.last_changed = Date.now();
|
|
||||||
this.temperatureStateInFlux(true);
|
|
||||||
}
|
|
||||||
if (newval >= this.min) {
|
|
||||||
this.value = newval;
|
|
||||||
} else {
|
|
||||||
this.value = this.min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueChanged() {
|
|
||||||
// when the last_changed timestamp is changed,
|
|
||||||
// trigger a potential event fire in
|
|
||||||
// the future, as long as last changed is far enough in the
|
|
||||||
// past.
|
|
||||||
if (this.last_changed) {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - this.last_changed >= 2000) {
|
|
||||||
this.fire("change");
|
|
||||||
this.temperatureStateInFlux(false);
|
|
||||||
this.last_changed = null;
|
|
||||||
}
|
|
||||||
}, 2010);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-climate-control", HaClimateControl);
|
|
138
src/components/ha-climate-control.ts
Normal file
138
src/components/ha-climate-control.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { mdiChevronDown, mdiChevronUp } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { conditionalClamp } from "../common/number/clamp";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import "./ha-icon";
|
||||||
|
import "./ha-icon-button";
|
||||||
|
|
||||||
|
@customElement("ha-climate-control")
|
||||||
|
class HaClimateControl extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public value!: number;
|
||||||
|
|
||||||
|
@property() public unit = "";
|
||||||
|
|
||||||
|
@property() public min?: number;
|
||||||
|
|
||||||
|
@property() public max?: number;
|
||||||
|
|
||||||
|
@property() public step = 1;
|
||||||
|
|
||||||
|
private _lastChanged?: number;
|
||||||
|
|
||||||
|
@query("#target_temperature") private _targetTemperature!: HTMLElement;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div id="target_temperature">${this.value} ${this.unit}</div>
|
||||||
|
<div class="control-buttons">
|
||||||
|
<div>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiChevronUp}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.climate-control.temperature_up"
|
||||||
|
)}
|
||||||
|
@click=${this._incrementValue}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiChevronDown}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.climate-control.temperature_down"
|
||||||
|
)}
|
||||||
|
@click=${this._decrementValue}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties) {
|
||||||
|
if (changedProperties.has("value")) {
|
||||||
|
this._valueChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _temperatureStateInFlux(inFlux) {
|
||||||
|
this._targetTemperature.classList.toggle("in-flux", inFlux);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _round(value) {
|
||||||
|
// Round value to precision derived from step.
|
||||||
|
// Inspired by https://github.com/soundar24/roundSlider/blob/master/src/roundslider.js
|
||||||
|
const s = this.step.toString().split(".");
|
||||||
|
return s[1] ? parseFloat(value.toFixed(s[1].length)) : Math.round(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _incrementValue() {
|
||||||
|
const newValue = this._round(this.value + this.step);
|
||||||
|
this._processNewValue(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _decrementValue() {
|
||||||
|
const newValue = this._round(this.value - this.step);
|
||||||
|
this._processNewValue(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _processNewValue(value) {
|
||||||
|
const newValue = conditionalClamp(value, this.min, this.max);
|
||||||
|
|
||||||
|
if (this.value !== newValue) {
|
||||||
|
this.value = newValue;
|
||||||
|
this._lastChanged = Date.now();
|
||||||
|
this._temperatureStateInFlux(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged() {
|
||||||
|
// When the last_changed timestamp is changed,
|
||||||
|
// trigger a potential event fire in the future,
|
||||||
|
// as long as last_changed is far enough in the past.
|
||||||
|
if (this._lastChanged) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - this._lastChanged! >= 2000) {
|
||||||
|
fireEvent(this, "change");
|
||||||
|
this._temperatureStateInFlux(false);
|
||||||
|
this._lastChanged = undefined;
|
||||||
|
}
|
||||||
|
}, 2010);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.in-flux {
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
#target_temperature {
|
||||||
|
align-self: center;
|
||||||
|
font-size: 28px;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
.control-buttons {
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
--mdc-icon-size: 32px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-climate-control": HaClimateControl;
|
||||||
|
}
|
||||||
|
}
|
@ -103,8 +103,9 @@ class MoreInfoClimate extends LitElement {
|
|||||||
stateObj.attributes.temperature !== null
|
stateObj.attributes.temperature !== null
|
||||||
? html`
|
? html`
|
||||||
<ha-climate-control
|
<ha-climate-control
|
||||||
|
.hass=${this.hass}
|
||||||
.value=${stateObj.attributes.temperature}
|
.value=${stateObj.attributes.temperature}
|
||||||
.units=${hass.config.unit_system.temperature}
|
.unit=${hass.config.unit_system.temperature}
|
||||||
.step=${temperatureStepSize}
|
.step=${temperatureStepSize}
|
||||||
.min=${stateObj.attributes.min_temp}
|
.min=${stateObj.attributes.min_temp}
|
||||||
.max=${stateObj.attributes.max_temp}
|
.max=${stateObj.attributes.max_temp}
|
||||||
@ -118,8 +119,9 @@ class MoreInfoClimate extends LitElement {
|
|||||||
stateObj.attributes.target_temp_high !== null)
|
stateObj.attributes.target_temp_high !== null)
|
||||||
? html`
|
? html`
|
||||||
<ha-climate-control
|
<ha-climate-control
|
||||||
|
.hass=${this.hass}
|
||||||
.value=${stateObj.attributes.target_temp_low}
|
.value=${stateObj.attributes.target_temp_low}
|
||||||
.units=${hass.config.unit_system.temperature}
|
.unit=${hass.config.unit_system.temperature}
|
||||||
.step=${temperatureStepSize}
|
.step=${temperatureStepSize}
|
||||||
.min=${stateObj.attributes.min_temp}
|
.min=${stateObj.attributes.min_temp}
|
||||||
.max=${stateObj.attributes.target_temp_high}
|
.max=${stateObj.attributes.target_temp_high}
|
||||||
@ -127,8 +129,9 @@ class MoreInfoClimate extends LitElement {
|
|||||||
@change=${this._targetTemperatureLowChanged}
|
@change=${this._targetTemperatureLowChanged}
|
||||||
></ha-climate-control>
|
></ha-climate-control>
|
||||||
<ha-climate-control
|
<ha-climate-control
|
||||||
|
.hass=${this.hass}
|
||||||
.value=${stateObj.attributes.target_temp_high}
|
.value=${stateObj.attributes.target_temp_high}
|
||||||
.units=${hass.config.unit_system.temperature}
|
.unit=${hass.config.unit_system.temperature}
|
||||||
.step=${temperatureStepSize}
|
.step=${temperatureStepSize}
|
||||||
.min=${stateObj.attributes.target_temp_low}
|
.min=${stateObj.attributes.target_temp_low}
|
||||||
.max=${stateObj.attributes.max_temp}
|
.max=${stateObj.attributes.max_temp}
|
||||||
|
@ -558,6 +558,10 @@
|
|||||||
"not_supported": "Your browser doesn't support QR scanning.",
|
"not_supported": "Your browser doesn't support QR scanning.",
|
||||||
"manual_input": "You can scan the QR code with another QR scanner and paste the code in the input below",
|
"manual_input": "You can scan the QR code with another QR scanner and paste the code in the input below",
|
||||||
"enter_qr_code": "Enter QR code value"
|
"enter_qr_code": "Enter QR code value"
|
||||||
|
},
|
||||||
|
"climate-control": {
|
||||||
|
"temperature_up": "Increase temperature",
|
||||||
|
"temperature_down": "Decrease temperature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dialogs": {
|
"dialogs": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user