diff --git a/gallery/src/pages/more-info/climate.ts b/gallery/src/pages/more-info/climate.ts
index 95cc65321e..daa3554c50 100644
--- a/gallery/src/pages/more-info/climate.ts
+++ b/gallery/src/pages/more-info/climate.ts
@@ -43,6 +43,28 @@ const ENTITIES = [
target_temp_low: 20,
target_temp_high: 25,
}),
+ getEntity("climate", "advanced", "auto", {
+ friendly_name: "Advanced hvac",
+ supported_features:
+ // eslint-disable-next-line no-bitwise
+ ClimateEntityFeature.TARGET_TEMPERATURE_RANGE |
+ ClimateEntityFeature.TARGET_HUMIDITY |
+ ClimateEntityFeature.PRESET_MODE,
+ hvac_modes: ["auto", "off"],
+ hvac_mode: "auto",
+ preset_modes: ["eco", "comfort", "boost"],
+ preset_mode: "eco",
+ current_temperature: 18,
+ min_temp: 10,
+ max_temp: 30,
+ target_temp_step: 1,
+ target_temp_low: 20,
+ target_temp_high: 25,
+ current_humidity: 40,
+ min_humidity: 0,
+ max_humidity: 100,
+ humidity: 50,
+ }),
getEntity("climate", "unavailable", "unavailable", {
friendly_name: "Unavailable heater",
hvac_modes: ["heat", "off"],
diff --git a/gallery/src/pages/more-info/water-heater.markdown b/gallery/src/pages/more-info/water-heater.markdown
new file mode 100644
index 0000000000..52dd5b3be1
--- /dev/null
+++ b/gallery/src/pages/more-info/water-heater.markdown
@@ -0,0 +1,3 @@
+---
+title: Water Heater
+---
diff --git a/gallery/src/pages/more-info/water-heater.ts b/gallery/src/pages/more-info/water-heater.ts
new file mode 100644
index 0000000000..8210dcac55
--- /dev/null
+++ b/gallery/src/pages/more-info/water-heater.ts
@@ -0,0 +1,70 @@
+import { html, LitElement, PropertyValues, TemplateResult } from "lit";
+import { customElement, property, query } from "lit/decorators";
+import "../../../../src/components/ha-card";
+import { WaterHeaterEntityFeature } from "../../../../src/data/water_heater";
+import "../../../../src/dialogs/more-info/more-info-content";
+import { getEntity } from "../../../../src/fake_data/entity";
+import {
+ MockHomeAssistant,
+ provideHass,
+} from "../../../../src/fake_data/provide_hass";
+import "../../components/demo-more-infos";
+
+const ENTITIES = [
+ getEntity("water_heater", "basic", "eco", {
+ friendly_name: "Basic heater",
+ operation_list: ["heat_pump", "eco", "performance", "off"],
+ operation_mode: "eco",
+ away_mode: "off",
+ target_temp_step: 1,
+ current_temperature: 55,
+ temperature: 60,
+ min_temp: 20,
+ max_temp: 70,
+ supported_features:
+ // eslint-disable-next-line no-bitwise
+ WaterHeaterEntityFeature.TARGET_TEMPERATURE |
+ WaterHeaterEntityFeature.OPERATION_MODE |
+ WaterHeaterEntityFeature.AWAY_MODE,
+ }),
+ getEntity("water_heater", "unavailable", "unavailable", {
+ friendly_name: "Unavailable heater",
+ operation_list: ["heat_pump", "eco", "performance", "off"],
+ operation_mode: "off",
+ min_temp: 20,
+ max_temp: 70,
+ supported_features:
+ // eslint-disable-next-line no-bitwise
+ WaterHeaterEntityFeature.TARGET_TEMPERATURE |
+ WaterHeaterEntityFeature.OPERATION_MODE,
+ }),
+];
+
+@customElement("demo-more-info-water-heater")
+class DemoMoreInfoWaterHeater extends LitElement {
+ @property() public hass!: MockHomeAssistant;
+
+ @query("demo-more-infos") private _demoRoot!: HTMLElement;
+
+ protected render(): TemplateResult {
+ return html`
+ ent.entityId)}
+ >
+ `;
+ }
+
+ protected firstUpdated(changedProperties: PropertyValues) {
+ super.firstUpdated(changedProperties);
+ const hass = provideHass(this._demoRoot);
+ hass.updateTranslations(null, "en");
+ hass.addEntities(ENTITIES);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "demo-more-info-water-heater": DemoMoreInfoWaterHeater;
+ }
+}
diff --git a/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts b/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts
index e9ddb81c11..1d7306f439 100644
--- a/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts
+++ b/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts
@@ -416,11 +416,12 @@ export class HaMoreInfoClimateTemperature extends LitElement {
font-size: 20px;
line-height: 24px;
align-self: flex-start;
- margin-left: -20px;
width: 20px;
margin-top: 4px;
}
-
+ .decimal + .unit {
+ margin-left: -20px;
+ }
.dual {
display: flex;
flex-direction: row;
diff --git a/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts b/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts
index a23d89fc79..3d2319688c 100644
--- a/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts
+++ b/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts
@@ -243,10 +243,12 @@ export class HaMoreInfoWaterHeaterTemperature extends LitElement {
font-size: 20px;
line-height: 24px;
align-self: flex-start;
- margin-left: -20px;
width: 20px;
margin-top: 4px;
}
+ .decimal + .unit {
+ margin-left: -20px;
+ }
`,
];
}
diff --git a/src/fake_data/entity.ts b/src/fake_data/entity.ts
index de4b0ca5a5..f6e6912dfd 100644
--- a/src/fake_data/entity.ts
+++ b/src/fake_data/entity.ts
@@ -356,6 +356,26 @@ class ClimateEntity extends Entity {
}
}
+class WaterHeaterEntity extends Entity {
+ public async handleService(domain, service, data) {
+ if (domain !== this.domain) {
+ return;
+ }
+
+ if (service === "set_operation_mode") {
+ this.update(data.operation_mode, this.attributes);
+ } else if (["set_temperature"].includes(service)) {
+ const { entity_id, ...toSet } = data;
+ this.update(this.state, {
+ ...this.attributes,
+ ...toSet,
+ });
+ } else {
+ super.handleService(domain, service, data);
+ }
+ }
+}
+
class GroupEntity extends Entity {
public async handleService(domain, service, data) {
if (!["homeassistant", this.domain].includes(domain)) {
@@ -386,6 +406,7 @@ const TYPES = {
lock: LockEntity,
media_player: MediaPlayerEntity,
switch: ToggleEntity,
+ water_heater: WaterHeaterEntity,
};
export const getEntity = (
diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts
index 3da61ed4fe..297f4d4f8c 100644
--- a/src/fake_data/provide_hass.ts
+++ b/src/fake_data/provide_hass.ts
@@ -1,15 +1,16 @@
-import { HassEntities } from "home-assistant-js-websocket";
+import { HassEntities, HassEntity } from "home-assistant-js-websocket";
import {
applyThemesOnElement,
invalidateThemeCache,
} from "../common/dom/apply_themes_on_element";
import { fireEvent } from "../common/dom/fire_event";
+import { computeFormatFunctions } from "../common/translations/entity-state";
import { computeLocalize } from "../common/translations/localize";
import { DEFAULT_PANEL } from "../data/panel";
import {
+ DateFormat,
FirstWeekday,
NumberFormat,
- DateFormat,
TimeFormat,
TimeZone,
} from "../data/translation";
@@ -49,6 +50,13 @@ export interface MockHomeAssistant extends HomeAssistant {
mockAPI(path: string | RegExp, callback: MockRestCallback);
mockEvent(event);
mockTheme(theme: Record | null);
+ formatEntityState(stateObj: HassEntity, state?: string): string;
+ formatEntityAttributeValue(
+ stateObj: HassEntity,
+ attribute: string,
+ value?: any
+ ): string;
+ formatEntityAttributeName(stateObj: HassEntity, attribute: string): string;
}
export const provideHass = (
@@ -73,6 +81,7 @@ export const provideHass = (
const lang = language || getLocalLanguage();
const translation = await getTranslation(fragment, lang);
await addTranslations(translation.data, lang);
+ updateFormatFunctions();
}
async function addTranslations(
@@ -101,6 +110,24 @@ export const provideHass = (
});
}
+ async function updateFormatFunctions() {
+ const {
+ formatEntityState,
+ formatEntityAttributeName,
+ formatEntityAttributeValue,
+ } = await computeFormatFunctions(
+ hass().localize,
+ hass().locale,
+ hass().config,
+ hass().entities
+ );
+ hass().updateHass({
+ formatEntityState,
+ formatEntityAttributeName,
+ formatEntityAttributeValue,
+ });
+ }
+
function addEntities(newEntities, replace = false) {
const states = {};
ensureArray(newEntities).forEach((ent) => {
@@ -115,6 +142,7 @@ export const provideHass = (
} else {
updateStates(states);
}
+ updateFormatFunctions();
}
function mockAPI(path, callback) {
@@ -318,6 +346,11 @@ export const provideHass = (
areas: {},
devices: {},
entities: {},
+ formatEntityState: (stateObj, state) =>
+ (state !== null ? state : stateObj.state) ?? "",
+ formatEntityAttributeName: (_stateObj, attribute) => attribute,
+ formatEntityAttributeValue: (stateObj, attribute, value) =>
+ value !== null ? value : stateObj.attributes[attribute] ?? "",
...overrideData,
};