+
+
+
+ `;
+ }
+
+ protected shouldUpdate(changedProps) {
+ if (changedProps.get("hass")) {
+ return changedProps.get("hass").states[this.config!.entity] !==
+ this.hass!.states[this.config!.entity]
+ ? true
+ : false;
+ }
+ return changedProps;
+ }
+
+ protected firstUpdated() {
+ const stateObj = this.hass!.states[this.config!.entity];
+
+ const _sliderType =
+ stateObj.attributes.target_temp_low &&
+ stateObj.attributes.target_temp_high
+ ? "range"
+ : "min-range";
+
+ jQuery("#thermostat", this.shadowRoot).roundSlider({
+ ...thermostatConfig,
+ radius: this.clientWidth / 3,
+ min: stateObj.attributes.min_temp,
+ max: stateObj.attributes.max_temp,
+ sliderType: _sliderType,
+ change: (value) => this._setTemperature(value),
+ drag: (value) => this._dragEvent(value),
+ });
+ }
+
+ protected updated() {
+ const attrs = this.hass!.states[this.config!.entity].attributes;
+
+ let sliderValue;
+ let uiValue;
+
+ if (attrs.target_temp_low && attrs.target_temp_high) {
+ sliderValue = `${attrs.target_temp_low}, ${attrs.target_temp_high}`;
+ uiValue = formatTemp([attrs.target_temp_low, attrs.target_temp_high]);
+ } else {
+ sliderValue = uiValue = attrs.temperature;
+ }
+
+ jQuery("#thermostat", this.shadowRoot).roundSlider({
+ value: sliderValue,
+ });
+ this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = uiValue;
+ }
+
+ private renderStyle() {
+ return html`
+ ${roundSliderStyle}
+
+ `;
+ }
+
+ private _dragEvent(e) {
+ this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = formatTemp(
+ String(e.value).split(",")
+ );
+ }
+
+ private _setTemperature(e) {
+ const stateObj = this.hass!.states[this.config!.entity];
+ if (
+ stateObj.attributes.target_temp_low &&
+ stateObj.attributes.target_temp_high
+ ) {
+ if (e.handle.index === 1) {
+ this.hass!.callService("climate", "set_temperature", {
+ entity_id: this.config!.entity,
+ target_temp_low: e.handle.value,
+ target_temp_high: stateObj.attributes.target_temp_high,
+ });
+ } else {
+ this.hass!.callService("climate", "set_temperature", {
+ entity_id: this.config!.entity,
+ target_temp_low: stateObj.attributes.target_temp_low,
+ target_temp_high: e.handle.value,
+ });
+ }
+ } else {
+ this.hass!.callService("climate", "set_temperature", {
+ entity_id: this.config!.entity,
+ temperature: e.value,
+ });
+ }
+ }
+
+ private _renderIcon(mode, currentMode) {
+ return html`
`;
+ }
+
+ private _handleModeClick(e: MouseEvent) {
+ this.hass!.callService("climate", "set_operation_mode", {
+ entity_id: this.config!.entity,
+ operation_mode: (e.currentTarget as any).mode,
+ });
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-thermostat-card": HuiThermostatCard;
+ }
+}
+
+customElements.define("hui-thermostat-card", HuiThermostatCard);
diff --git a/src/panels/lovelace/common/create-card-element.js b/src/panels/lovelace/common/create-card-element.js
index f6465cb8a5..3cadc4b052 100644
--- a/src/panels/lovelace/common/create-card-element.js
+++ b/src/panels/lovelace/common/create-card-element.js
@@ -20,6 +20,7 @@ import "../cards/hui-picture-glance-card";
import "../cards/hui-plant-status-card.js";
import "../cards/hui-sensor-card.js";
import "../cards/hui-vertical-stack-card.ts";
+import "../cards/hui-thermostat-card.ts";
import "../cards/hui-weather-forecast-card";
import "../cards/hui-gauge-card.js";
@@ -46,6 +47,7 @@ const CARD_TYPES = new Set([
"picture-glance",
"plant-status",
"sensor",
+ "thermostat",
"vertical-stack",
"weather-forecast",
]);
diff --git a/src/resources/jquery.roundslider.js b/src/resources/jquery.roundslider.js
new file mode 100644
index 0000000000..16ada36315
--- /dev/null
+++ b/src/resources/jquery.roundslider.js
@@ -0,0 +1,6 @@
+import { html } from "@polymer/lit-element";
+import "./jquery";
+import "round-slider";
+import roundSliderCSS from "round-slider/dist/roundslider.min.css";
+
+export const roundSliderStyle = html``;
diff --git a/src/resources/jquery.ts b/src/resources/jquery.ts
new file mode 100644
index 0000000000..650365b431
--- /dev/null
+++ b/src/resources/jquery.ts
@@ -0,0 +1,5 @@
+import jQuery_ from "jquery";
+
+(window as any).jQuery = jQuery_;
+
+export const jQuery = jQuery_;
diff --git a/webpack.config.js b/webpack.config.js
index 3ece128c29..639d9db55e 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -63,6 +63,10 @@ function createConfig(isProdBuild, latestBuild) {
module: {
rules: [
babelLoaderConfig({ latestBuild }),
+ {
+ test: /\.css$/,
+ use: "raw-loader",
+ },
{
test: /\.(html)$/,
use: {
diff --git a/yarn.lock b/yarn.lock
index 05917754b3..a2c350890c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9080,6 +9080,11 @@ joi@^11.1.1:
isemail "3.x.x"
topo "2.x.x"
+"jquery@>= 1.4.1", jquery@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
+ integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
+
js-levenshtein@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.4.tgz#3a56e3cbf589ca0081eb22cd9ba0b1290a16d26e"
@@ -12838,6 +12843,13 @@ rollup@^0.58.2:
"@types/estree" "0.0.38"
"@types/node" "*"
+round-slider@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/round-slider/-/round-slider-1.3.2.tgz#8fb363f4fe2ab653b8160a13aa4d493634bac050"
+ integrity sha512-JAUSXwuxiLv/kliHNP2GbnXID87hFqoxac38UIcvkpyYTwSzpTKlqvMKLB7xWnDOgX/9MCD7B2Ab41pk6cGiWQ==
+ dependencies:
+ jquery ">= 1.4.1"
+
run-async@^2.0.0, run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"