diff --git a/src/components/entity/ha-entity-toggle.js b/src/components/entity/ha-entity-toggle.js
deleted file mode 100644
index 3bb7e511f3..0000000000
--- a/src/components/entity/ha-entity-toggle.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import "@polymer/paper-icon-button/paper-icon-button";
-import "@polymer/paper-toggle-button/paper-toggle-button";
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-
-import { STATES_OFF } from "../../common/const";
-import computeStateDomain from "../../common/entity/compute_state_domain";
-
-class HaEntityToggle extends PolymerElement {
- static get template() {
- return html`
-
-
-
-
-
-
-
-
-
- `;
- }
-
- static get properties() {
- return {
- hass: Object,
- stateObj: {
- type: Object,
- observer: "stateObjObserver",
- },
-
- toggleChecked: {
- type: Boolean,
- value: false,
- },
-
- isOn: {
- type: Boolean,
- computed: "computeIsOn(stateObj)",
- observer: "isOnChanged",
- },
- };
- }
-
- ready() {
- super.ready();
- this.addEventListener("click", (ev) => this.onTap(ev));
- this.forceStateChange();
- }
-
- onTap(ev) {
- ev.stopPropagation();
- }
-
- toggleChanged(ev) {
- const newVal = ev.target.checked;
-
- if (newVal && !this.isOn) {
- this.callService(true);
- } else if (!newVal && this.isOn) {
- this.callService(false);
- }
- }
-
- isOnChanged(newVal) {
- this.toggleChecked = newVal;
- }
-
- forceStateChange() {
- if (this.toggleChecked === this.isOn) {
- this.toggleChecked = !this.toggleChecked;
- }
- this.toggleChecked = this.isOn;
- }
-
- turnOn() {
- this.callService(true);
- }
-
- turnOff() {
- this.callService(false);
- }
-
- computeIsOn(stateObj) {
- return stateObj && !STATES_OFF.includes(stateObj.state);
- }
-
- stateObjObserver(newVal, oldVal) {
- if (!oldVal || !newVal) return;
- if (this.computeIsOn(newVal) === this.computeIsOn(oldVal)) {
- // stateObj changed but isOn is the same. Make sure toggle is in the right position.
- this.forceStateChange();
- }
- }
-
- // We call updateToggle after a successful call to re-sync the toggle
- // with the state. It will be out of sync if our service call did not
- // result in the entity to be turned on. Since the state is not changing,
- // the resync is not called automatic.
- callService(turnOn) {
- const stateDomain = computeStateDomain(this.stateObj);
- let serviceDomain;
- let service;
-
- if (stateDomain === "lock") {
- serviceDomain = "lock";
- service = turnOn ? "unlock" : "lock";
- } else if (stateDomain === "cover") {
- serviceDomain = "cover";
- service = turnOn ? "open_cover" : "close_cover";
- } else if (stateDomain === "group") {
- serviceDomain = "homeassistant";
- service = turnOn ? "turn_on" : "turn_off";
- } else {
- serviceDomain = stateDomain;
- service = turnOn ? "turn_on" : "turn_off";
- }
-
- const currentState = this.stateObj;
- this.hass
- .callService(serviceDomain, service, {
- entity_id: this.stateObj.entity_id,
- })
- .then(() => {
- setTimeout(() => {
- // If after 2 seconds we have not received a state update
- // reset the switch to it's original state.
- if (this.stateObj === currentState) {
- this.forceStateChange();
- }
- }, 2000);
- });
- }
-}
-
-customElements.define("ha-entity-toggle", HaEntityToggle);
diff --git a/src/components/entity/ha-entity-toggle.ts b/src/components/entity/ha-entity-toggle.ts
new file mode 100644
index 0000000000..32d7140ac5
--- /dev/null
+++ b/src/components/entity/ha-entity-toggle.ts
@@ -0,0 +1,154 @@
+import "@polymer/paper-icon-button/paper-icon-button";
+import "@polymer/paper-toggle-button/paper-toggle-button";
+
+import { STATES_OFF } from "../../common/const";
+import computeStateDomain from "../../common/entity/compute_state_domain";
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ CSSResult,
+ css,
+ PropertyDeclarations,
+} from "lit-element";
+import { HomeAssistant } from "../../types";
+import { HassEntity } from "home-assistant-js-websocket";
+
+class HaEntityToggle extends LitElement {
+ // hass is not a property so that we only re-render on stateObj changes
+ public hass?: HomeAssistant;
+ public stateObj?: HassEntity;
+
+ protected render(): TemplateResult | void {
+ if (!this.stateObj) {
+ return html`
+
+ `;
+ }
+
+ const isOn = this._isOn;
+
+ if (this.stateObj.attributes.assumed_state) {
+ return html`
+
+
+ `;
+ }
+
+ return html`
+
+ `;
+ }
+
+ static get properties(): PropertyDeclarations {
+ return {
+ stateObj: {},
+ };
+ }
+
+ protected firstUpdated(changedProps) {
+ super.firstUpdated(changedProps);
+ this.addEventListener("click", (ev) => ev.stopPropagation());
+ }
+
+ private get _isOn(): boolean {
+ return (
+ this.stateObj !== undefined && !STATES_OFF.includes(this.stateObj.state)
+ );
+ }
+
+ private _toggleChanged(ev) {
+ const newVal = ev.target.checked;
+
+ if (newVal !== this._isOn) {
+ this._callService(newVal);
+ }
+ }
+
+ private _turnOn() {
+ this._callService(true);
+ }
+
+ private _turnOff() {
+ this._callService(false);
+ }
+
+ // We will force a re-render after a successful call to re-sync the toggle
+ // with the state. It will be out of sync if our service call did not
+ // result in the entity to be turned on. Since the state is not changing,
+ // the resync is not called automatic.
+ private async _callService(turnOn): Promise {
+ if (!this.hass || !this.stateObj) {
+ return;
+ }
+ const stateDomain = computeStateDomain(this.stateObj);
+ let serviceDomain;
+ let service;
+
+ if (stateDomain === "lock") {
+ serviceDomain = "lock";
+ service = turnOn ? "unlock" : "lock";
+ } else if (stateDomain === "cover") {
+ serviceDomain = "cover";
+ service = turnOn ? "open_cover" : "close_cover";
+ } else if (stateDomain === "group") {
+ serviceDomain = "homeassistant";
+ service = turnOn ? "turn_on" : "turn_off";
+ } else {
+ serviceDomain = stateDomain;
+ service = turnOn ? "turn_on" : "turn_off";
+ }
+
+ const currentState = this.stateObj;
+
+ await this.hass.callService(serviceDomain, service, {
+ entity_id: this.stateObj.entity_id,
+ });
+
+ setTimeout(() => {
+ // If after 2 seconds we have not received a state update
+ // reset the switch to it's original state.
+ if (this.stateObj === currentState) {
+ this.requestUpdate();
+ }
+ }, 2000);
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ :host {
+ white-space: nowrap;
+ min-width: 38px;
+ }
+ paper-icon-button {
+ color: var(
+ --paper-icon-button-inactive-color,
+ var(--primary-text-color)
+ );
+ transition: color 0.5s;
+ }
+ paper-icon-button[state-active] {
+ color: var(--paper-icon-button-active-color, var(--primary-color));
+ }
+ paper-toggle-button {
+ cursor: pointer;
+ --paper-toggle-button-label-spacing: 0;
+ padding: 13px 5px;
+ margin: -4px -5px;
+ }
+ `;
+ }
+}
+
+customElements.define("ha-entity-toggle", HaEntityToggle);