diff --git a/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts b/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts
index 0040c44d9d78..00a19db3f306 100644
--- a/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts
+++ b/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts
@@ -1,4 +1,3 @@
-import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { mdiMinus, mdiPlus, mdiWaterPercent } from "@mdi/js";
import { CSSResultGroup, LitElement, PropertyValues, html } from "lit";
import { customElement, property, state } from "lit/decorators";
diff --git a/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts b/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts
index 89983772dffb..50d43b10f316 100644
--- a/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts
+++ b/src/dialogs/more-info/components/ha-more-info-control-circular-slider-style.ts
@@ -93,7 +93,6 @@ export const moreInfoControlCircularSliderStyle = css`
font-size: 32px;
}
.info {
- margin-top: 12px;
font-size: 14px;
gap: 2px;
--mdc-icon-size: 14px;
diff --git a/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts b/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts
index 721d49c8080d..df6461424d75 100644
--- a/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts
+++ b/src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts
@@ -1,4 +1,4 @@
-import { mdiMinus, mdiPlus } from "@mdi/js";
+import { mdiMinus, mdiPlus, mdiWaterPercent } from "@mdi/js";
import { CSSResultGroup, LitElement, PropertyValues, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
@@ -100,13 +100,9 @@ export class HaMoreInfoHumidifierHumidity extends LitElement {
return html`
- ${action && ["drying", "humidifying"].includes(action)
- ? this.hass.localize("ui.card.humidifier.target_label", {
- action: actionLabel,
- })
- : action && action !== "off" && action !== "idle"
- ? actionLabel
- : this.hass.localize("ui.card.humidifier.target")}
+ ${action && action !== "off" && action !== "idle"
+ ? actionLabel
+ : this.hass.localize("ui.card.humidifier.target")}
`;
}
@@ -118,7 +114,7 @@ export class HaMoreInfoHumidifierHumidity extends LitElement {
return html`
- ${this.hass.localize("ui.card.humidifier.currently")}
+
${this.hass.formatEntityAttributeValue(
this.stateObj,
diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts
index eae7103ec318..2d82274547e0 100644
--- a/src/panels/lovelace/cards/hui-humidifier-card.ts
+++ b/src/panels/lovelace/cards/hui-humidifier-card.ts
@@ -1,6 +1,4 @@
-import { mdiDotsVertical, mdiPower, mdiWaterPercent } from "@mdi/js";
-import "@thomasloven/round-slider";
-import { HassEntity } from "home-assistant-js-websocket";
+import { mdiDotsVertical } from "@mdi/js";
import {
CSSResultGroup,
LitElement,
@@ -8,26 +6,19 @@ import {
css,
html,
nothing,
- svg,
} from "lit";
-import { customElement, property, query, state } from "lit/decorators";
-import { classMap } from "lit/directives/class-map";
-import { styleMap } from "lit/directives/style-map";
+import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
-import { stateColorCss } from "../../../common/entity/state_color";
-import { formatNumber } from "../../../common/number/format_number";
-import { computeRTLDirection } from "../../../common/util/compute_rtl";
import "../../../components/ha-card";
-import type { HaCard } from "../../../components/ha-card";
import "../../../components/ha-icon-button";
-import { UNAVAILABLE, isUnavailableState } from "../../../data/entity";
import { HumidifierEntity } from "../../../data/humidifier";
+import "../../../dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity";
import { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
-import { hasConfigOrEntityChanged } from "../common/has-changed";
import { createEntityNotFoundWarning } from "../components/hui-warning";
+import "../tile-features/hui-tile-features";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { HumidifierCardConfig } from "./types";
@@ -53,17 +44,21 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
includeDomains
);
- return { type: "humidifier", entity: foundEntities[0] || "" };
+ return {
+ type: "humidifier",
+ entity: foundEntities[0] || "",
+ features: [
+ {
+ type: "humidifier-modes",
+ },
+ ],
+ };
}
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: HumidifierCardConfig;
- @state() private _setHum?: number;
-
- @query("ha-card") private _card?: HaCard;
-
public getCardSize(): number {
return 7;
}
@@ -76,168 +71,10 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
this._config = config;
}
- protected render() {
- if (!this.hass || !this._config) {
- return nothing;
- }
- const stateObj = this.hass.states[this._config.entity] as HumidifierEntity;
-
- if (!stateObj) {
- return html`
-
- ${createEntityNotFoundWarning(this.hass, this._config.entity)}
-
- `;
- }
-
- const name =
- this._config!.name ||
- computeStateName(this.hass!.states[this._config!.entity]);
-
- const targetHumidity =
- stateObj.attributes.humidity !== null &&
- Number.isFinite(Number(stateObj.attributes.humidity))
- ? stateObj.attributes.humidity
- : null;
-
- const setHumidity = this._setHum ? this._setHum : targetHumidity;
-
- const rtlDirection = computeRTLDirection(this.hass);
-
- const slider = isUnavailableState(stateObj.state)
- ? html` `
- : html`
-
- `;
-
- const currentHumidity = svg`
-
- `;
-
- const setValues = svg`
-
- `;
-
- return html`
-
-
-
-
-
-
- ${slider}
-
-
${currentHumidity} ${setValues}
-
-
-
-
-
-
- `;
- }
-
- protected shouldUpdate(changedProps: PropertyValues): boolean {
- return hasConfigOrEntityChanged(this, changedProps);
+ private _handleMoreInfo() {
+ fireEvent(this, "hass-more-info", {
+ entityId: this._config!.entity,
+ });
}
protected updated(changedProps: PropertyValues): void {
@@ -264,90 +101,49 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
) {
applyThemesOnElement(this, this.hass.themes, this._config.theme);
}
-
- const stateObj = this.hass.states[this._config.entity];
- if (!stateObj) {
- return;
- }
-
- if (!oldHass || oldHass.states[this._config.entity] !== stateObj) {
- this._rescale_svg();
- }
}
- public willUpdate(changedProps: PropertyValues) {
- if (!this.hass || !this._config || !changedProps.has("hass")) {
- return;
+ protected render() {
+ if (!this.hass || !this._config) {
+ return nothing;
}
+ const stateObj = this.hass.states[this._config.entity] as HumidifierEntity;
- const stateObj = this.hass.states[this._config.entity];
if (!stateObj) {
- return;
- }
-
- const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
-
- if (!oldHass || oldHass.states[this._config.entity] !== stateObj) {
- this._setHum = this._getSetHum(stateObj);
- }
- }
-
- private _rescale_svg() {
- // Set the viewbox of the SVG containing the set temperature to perfectly
- // fit the text
- // That way it will auto-scale correctly
- // This is not done to the SVG containing the current temperature, because
- // it should not be centered on the text, but only on the value
- const card = this._card;
- if (card) {
- card.updateComplete.then(() => {
- const svgRoot = this.shadowRoot!.querySelector("#set-values")!;
- const box = svgRoot.querySelector("g")!.getBBox()!;
- svgRoot.setAttribute(
- "viewBox",
- `${box.x} ${box!.y} ${box.width} ${box.height}`
- );
- svgRoot.setAttribute("width", `${box.width}`);
- svgRoot.setAttribute("height", `${box.height}`);
- });
- }
- }
-
- private _getSetHum(stateObj: HassEntity): undefined | number {
- if (isUnavailableState(stateObj.state)) {
- return undefined;
+ return html`
+
+ ${createEntityNotFoundWarning(this.hass, this._config.entity)}
+
+ `;
}
- return stateObj.attributes.humidity;
- }
-
- private _dragEvent(e): void {
- this._setHum = e.detail.value;
- }
+ const name = this._config!.name || computeStateName(stateObj);
- private _setHumidity(e): void {
- this.hass!.callService("humidifier", "set_humidity", {
- entity_id: this._config!.entity,
- humidity: e.detail.value,
- });
- }
-
- private _turnOn(): void {
- this.hass!.callService("humidifier", "turn_on", {
- entity_id: this._config!.entity,
- });
- }
-
- private _turnOff(): void {
- this.hass!.callService("humidifier", "turn_off", {
- entity_id: this._config!.entity,
- });
- }
-
- private _handleMoreInfo() {
- fireEvent(this, "hass-more-info", {
- entityId: this._config!.entity,
- });
+ return html`
+
+ ${name}
+
+
+
+
+ `;
}
static get styles(): CSSResultGroup {
@@ -360,10 +156,27 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
height: 100%;
position: relative;
overflow: hidden;
- --name-font-size: 1.2rem;
- --brightness-font-size: 1.2rem;
- --rail-border-color: transparent;
- --mode-color: var(--state-inactive-color);
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .title {
+ width: 100%;
+ font-size: 18px;
+ line-height: 24px;
+ padding: 12px 36px 16px 36px;
+ margin: 0;
+ text-align: center;
+ box-sizing: border-box;
+ }
+
+ ha-more-info-humidifier-humidity {
+ width: 100%;
+ max-width: 344px; /* 12px + 12px + 320px */
+ padding: 0 12px 12px 12px;
+ box-sizing: border-box;
}
.more-info {
@@ -375,93 +188,11 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
inset-inline-start: initial;
border-radius: 100%;
color: var(--secondary-text-color);
- z-index: 1;
direction: var(--direction);
}
- .content {
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
-
- #controls {
- display: flex;
- justify-content: center;
- padding: 16px;
- position: relative;
- }
-
- #slider {
- height: 100%;
- width: 100%;
- position: relative;
- max-width: 250px;
- min-width: 100px;
- }
-
- round-slider {
- --round-slider-path-color: var(--slider-track-color);
- --round-slider-bar-color: var(--mode-color);
- padding-bottom: 10%;
- }
-
- #slider-center {
- position: absolute;
- width: calc(100% - 40px);
- height: calc(100% - 40px);
- box-sizing: border-box;
- border-radius: 100%;
- left: 20px;
- top: 20px;
- text-align: center;
- overflow-wrap: break-word;
- pointer-events: none;
- }
-
- #humidity {
- position: absolute;
- transform: translate(-50%, -50%);
+ hui-tile-features {
width: 100%;
- height: 50%;
- top: 45%;
- left: 50%;
- direction: ltr;
- }
-
- #set-values {
- max-width: 80%;
- transform: translate(0, -50%);
- font-size: 20px;
- }
-
- #set-mode {
- fill: var(--secondary-text-color);
- font-size: 16px;
- }
-
- #info {
- display: flex-vertical;
- justify-content: center;
- text-align: center;
- padding: 16px;
- margin-top: -60px;
- font-size: var(--name-font-size);
- }
-
- #modes > * {
- color: var(--disabled-text-color);
- cursor: pointer;
- display: inline-block;
- }
-
- #modes .selected-icon {
- color: var(--mode-color);
- }
-
- text {
- fill: var(--primary-text-color);
}
`;
}
diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts
index 0ed4346f82fa..205ca9ea8b98 100644
--- a/src/panels/lovelace/cards/hui-thermostat-card.ts
+++ b/src/panels/lovelace/cards/hui-thermostat-card.ts
@@ -1,5 +1,4 @@
import { mdiDotsVertical } from "@mdi/js";
-import "@thomasloven/round-slider";
import {
CSSResultGroup,
LitElement,
diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts
index c0adf7335c30..0be7d44ac554 100644
--- a/src/panels/lovelace/cards/types.ts
+++ b/src/panels/lovelace/cards/types.ts
@@ -262,6 +262,7 @@ export interface HumidifierCardConfig extends LovelaceCardConfig {
entity: string;
theme?: string;
name?: string;
+ features?: LovelaceTileFeatureConfig[];
}
export interface IframeCardConfig extends LovelaceCardConfig {
diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts
index fdd03510a6a2..82e5a6c43c34 100644
--- a/src/panels/lovelace/common/generate-lovelace-config.ts
+++ b/src/panels/lovelace/common/generate-lovelace-config.ts
@@ -153,6 +153,11 @@ export const computeCards = (
const cardConfig: HumidifierCardConfig = {
type: "humidifier",
entity: entityId,
+ features: [
+ {
+ type: "humidifier-modes",
+ },
+ ],
};
cards.push(cardConfig);
} else if (domain === "media_player") {
diff --git a/src/panels/lovelace/create-element/create-tile-feature-element.ts b/src/panels/lovelace/create-element/create-tile-feature-element.ts
index 0f608494c9c5..cb51578c0448 100644
--- a/src/panels/lovelace/create-element/create-tile-feature-element.ts
+++ b/src/panels/lovelace/create-element/create-tile-feature-element.ts
@@ -6,14 +6,15 @@ import "../tile-features/hui-cover-position-tile-feature";
import "../tile-features/hui-cover-tilt-position-tile-feature";
import "../tile-features/hui-cover-tilt-tile-feature";
import "../tile-features/hui-fan-speed-tile-feature";
+import "../tile-features/hui-humidifier-modes-tile-feature";
import "../tile-features/hui-lawn-mower-commands-tile-feature";
import "../tile-features/hui-light-brightness-tile-feature";
import "../tile-features/hui-light-color-temp-tile-feature";
+import "../tile-features/hui-number-tile-feature";
import "../tile-features/hui-select-options-tile-feature";
import "../tile-features/hui-target-temperature-tile-feature";
import "../tile-features/hui-vacuum-commands-tile-feature";
import "../tile-features/hui-water-heater-operation-modes-tile-feature";
-import "../tile-features/hui-number-tile-feature";
import { LovelaceTileFeatureConfig } from "../tile-features/types";
import {
createLovelaceElement,
@@ -29,6 +30,7 @@ const TYPES: Set = new Set([
"cover-tilt-position",
"cover-tilt",
"fan-speed",
+ "humidifier-modes",
"lawn-mower-commands",
"light-brightness",
"light-color-temp",
diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts
index 5cb79b16216f..697af9f2e8a4 100644
--- a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts
@@ -1,13 +1,28 @@
-import { html, LitElement, nothing } from "lit";
+import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
-import { assert, assign, object, optional, string } from "superstruct";
-import { fireEvent } from "../../../../common/dom/fire_event";
+import memoizeOne from "memoize-one";
+import {
+ any,
+ array,
+ assert,
+ assign,
+ object,
+ optional,
+ string,
+} from "superstruct";
+import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
import type { HumidifierCardConfig } from "../../cards/types";
+import {
+ LovelaceTileFeatureConfig,
+ LovelaceTileFeatureContext,
+} from "../../tile-features/types";
import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
+import { EditSubElementEvent, SubElementEditorConfig } from "../types";
+import "./hui-tile-card-features-editor";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -15,6 +30,7 @@ const cardConfigStruct = assign(
entity: optional(string()),
name: optional(string()),
theme: optional(string()),
+ features: optional(array(any())),
})
);
@@ -43,16 +59,39 @@ export class HuiHumidifierCardEditor
@state() private _config?: HumidifierCardConfig;
+ @state() private _subElementEditorConfig?: SubElementEditorConfig;
+
public setConfig(config: HumidifierCardConfig): void {
assert(config, cardConfigStruct);
this._config = config;
}
+ private _context = memoizeOne(
+ (entity_id?: string): LovelaceTileFeatureContext => ({ entity_id })
+ );
+
protected render() {
if (!this.hass || !this._config) {
return nothing;
}
+ const stateObj = this._config.entity
+ ? this.hass.states[this._config.entity]
+ : undefined;
+
+ if (this._subElementEditorConfig) {
+ return html`
+
+
+ `;
+ }
+
return html`
+
`;
}
@@ -68,6 +114,62 @@ export class HuiHumidifierCardEditor
fireEvent(this, "config-changed", { config: ev.detail.value });
}
+ private _featuresChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+ if (!this._config || !this.hass) {
+ return;
+ }
+
+ const features = ev.detail.features as LovelaceTileFeatureConfig[];
+ const config: HumidifierCardConfig = {
+ ...this._config,
+ features,
+ };
+
+ if (features.length === 0) {
+ delete config.features;
+ }
+
+ fireEvent(this, "config-changed", { config });
+ }
+
+ private subElementChanged(ev: CustomEvent): void {
+ ev.stopPropagation();
+ if (!this._config || !this.hass) {
+ return;
+ }
+
+ const value = ev.detail.config;
+
+ const newConfigFeatures = this._config!.features
+ ? [...this._config!.features]
+ : [];
+
+ if (!value) {
+ newConfigFeatures.splice(this._subElementEditorConfig!.index!, 1);
+ this._goBack();
+ } else {
+ newConfigFeatures[this._subElementEditorConfig!.index!] = value;
+ }
+
+ this._config = { ...this._config!, features: newConfigFeatures };
+
+ this._subElementEditorConfig = {
+ ...this._subElementEditorConfig!,
+ elementConfig: value,
+ };
+
+ fireEvent(this, "config-changed", { config: this._config });
+ }
+
+ private _editDetailElement(ev: HASSDomEvent): void {
+ this._subElementEditorConfig = ev.detail.subElementConfig;
+ }
+
+ private _goBack(): void {
+ this._subElementEditorConfig = undefined;
+ }
+
private _computeLabelCallback = (schema: SchemaUnion) => {
if (schema.name === "entity") {
return this.hass!.localize(
@@ -87,6 +189,15 @@ export class HuiHumidifierCardEditor
`ui.panel.lovelace.editor.card.generic.${schema.name}`
);
};
+
+ static get styles() {
+ return css`
+ ha-form {
+ display: block;
+ margin-bottom: 24px;
+ }
+ `;
+ }
}
declare global {
diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts
index e1e556a0b130..8b80bc8cd989 100644
--- a/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts
@@ -24,21 +24,22 @@ import { HomeAssistant } from "../../../../types";
import { getTileFeatureElementClass } from "../../create-element/create-tile-feature-element";
import { supportsAlarmModesTileFeature } from "../../tile-features/hui-alarm-modes-tile-feature";
import { supportsClimateHvacModesTileFeature } from "../../tile-features/hui-climate-hvac-modes-tile-feature";
+import { supportsClimatePresetModesTileFeature } from "../../tile-features/hui-climate-preset-modes-tile-feature";
import { supportsCoverOpenCloseTileFeature } from "../../tile-features/hui-cover-open-close-tile-feature";
import { supportsCoverPositionTileFeature } from "../../tile-features/hui-cover-position-tile-feature";
import { supportsCoverTiltPositionTileFeature } from "../../tile-features/hui-cover-tilt-position-tile-feature";
import { supportsCoverTiltTileFeature } from "../../tile-features/hui-cover-tilt-tile-feature";
import { supportsFanSpeedTileFeature } from "../../tile-features/hui-fan-speed-tile-feature";
+import { supportsHumidifierModesTileFeature } from "../../tile-features/hui-humidifier-modes-tile-feature";
import { supportsLawnMowerCommandTileFeature } from "../../tile-features/hui-lawn-mower-commands-tile-feature";
import { supportsLightBrightnessTileFeature } from "../../tile-features/hui-light-brightness-tile-feature";
import { supportsLightColorTempTileFeature } from "../../tile-features/hui-light-color-temp-tile-feature";
+import { supportsNumberTileFeature } from "../../tile-features/hui-number-tile-feature";
import { supportsSelectOptionTileFeature } from "../../tile-features/hui-select-options-tile-feature";
import { supportsTargetTemperatureTileFeature } from "../../tile-features/hui-target-temperature-tile-feature";
import { supportsVacuumCommandTileFeature } from "../../tile-features/hui-vacuum-commands-tile-feature";
import { supportsWaterHeaterOperationModesTileFeature } from "../../tile-features/hui-water-heater-operation-modes-tile-feature";
import { LovelaceTileFeatureConfig } from "../../tile-features/types";
-import { supportsClimatePresetModesTileFeature } from "../../tile-features/hui-climate-preset-modes-tile-feature";
-import { supportsNumberTileFeature } from "../../tile-features/hui-number-tile-feature";
export type FeatureType = LovelaceTileFeatureConfig["type"];
type SupportsFeature = (stateObj: HassEntity) => boolean;
@@ -52,6 +53,7 @@ const UI_FEATURE_TYPES = [
"cover-tilt-position",
"cover-tilt",
"fan-speed",
+ "humidifier-modes",
"lawn-mower-commands",
"light-brightness",
"light-color-temp",
@@ -86,14 +88,15 @@ const SUPPORTS_FEATURE_TYPES: Record<
"cover-tilt-position": supportsCoverTiltPositionTileFeature,
"cover-tilt": supportsCoverTiltTileFeature,
"fan-speed": supportsFanSpeedTileFeature,
+ "humidifier-modes": supportsHumidifierModesTileFeature,
"lawn-mower-commands": supportsLawnMowerCommandTileFeature,
"light-brightness": supportsLightBrightnessTileFeature,
"light-color-temp": supportsLightColorTempTileFeature,
+ number: supportsNumberTileFeature,
"target-temperature": supportsTargetTemperatureTileFeature,
"vacuum-commands": supportsVacuumCommandTileFeature,
"water-heater-operation-modes": supportsWaterHeaterOperationModesTileFeature,
"select-options": supportsSelectOptionTileFeature,
- number: supportsNumberTileFeature,
};
const CUSTOM_FEATURE_ENTRIES: Record<
diff --git a/src/panels/lovelace/tile-features/hui-humidifier-modes-tile-feature.ts b/src/panels/lovelace/tile-features/hui-humidifier-modes-tile-feature.ts
new file mode 100644
index 000000000000..2445a1519299
--- /dev/null
+++ b/src/panels/lovelace/tile-features/hui-humidifier-modes-tile-feature.ts
@@ -0,0 +1,136 @@
+import { mdiPower, mdiWaterPercent } from "@mdi/js";
+import { HassEntity } from "home-assistant-js-websocket";
+import { LitElement, PropertyValues, TemplateResult, css, html } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { styleMap } from "lit/directives/style-map";
+import { computeDomain } from "../../../common/entity/compute_domain";
+import { stateColorCss } from "../../../common/entity/state_color";
+import "../../../components/ha-control-select";
+import type { ControlSelectOption } from "../../../components/ha-control-select";
+import { UNAVAILABLE } from "../../../data/entity";
+import { HumidifierEntity, HumidifierState } from "../../../data/humidifier";
+import { HomeAssistant } from "../../../types";
+import { LovelaceTileFeature } from "../types";
+import { HumidifierModesTileFeatureConfig } from "./types";
+
+export const supportsHumidifierModesTileFeature = (stateObj: HassEntity) => {
+ const domain = computeDomain(stateObj.entity_id);
+ return domain === "humidifier";
+};
+
+@customElement("hui-humidifier-modes-tile-feature")
+class HuiHumidifierModeTileFeature
+ extends LitElement
+ implements LovelaceTileFeature
+{
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @property({ attribute: false }) public stateObj?: HumidifierEntity;
+
+ @state() private _config?: HumidifierModesTileFeatureConfig;
+
+ @state() _currentState?: HumidifierState;
+
+ static getStubConfig(): HumidifierModesTileFeatureConfig {
+ return {
+ type: "humidifier-modes",
+ };
+ }
+
+ public setConfig(config: HumidifierModesTileFeatureConfig): void {
+ if (!config) {
+ throw new Error("Invalid configuration");
+ }
+ this._config = config;
+ }
+
+ protected willUpdate(changedProp: PropertyValues): void {
+ super.willUpdate(changedProp);
+ if (changedProp.has("stateObj") && this.stateObj) {
+ this._currentState = this.stateObj.state as HumidifierState;
+ }
+ }
+
+ private async _valueChanged(ev: CustomEvent) {
+ const newState = (ev.detail as any).value as HumidifierState;
+
+ if (newState === this.stateObj!.state) return;
+
+ const oldState = this.stateObj!.state as HumidifierState;
+ this._currentState = newState;
+
+ try {
+ await this._setState(newState);
+ } catch (err) {
+ this._currentState = oldState;
+ }
+ }
+
+ private async _setState(newState: HumidifierState) {
+ await this.hass!.callService(
+ "humidifier",
+ newState === "on" ? "turn_on" : "turn_off",
+ {
+ entity_id: this.stateObj!.entity_id,
+ }
+ );
+ }
+
+ protected render(): TemplateResult | null {
+ if (
+ !this._config ||
+ !this.hass ||
+ !this.stateObj ||
+ !supportsHumidifierModesTileFeature(this.stateObj)
+ ) {
+ return null;
+ }
+
+ const color = stateColorCss(this.stateObj);
+
+ const options = ["on", "off"].map((entityState) => ({
+ value: entityState,
+ label: this.hass!.formatEntityState(this.stateObj!, entityState),
+ path: entityState === "on" ? mdiWaterPercent : mdiPower,
+ }));
+
+ return html`
+
+
+
+
+ `;
+ }
+
+ static get styles() {
+ return css`
+ ha-control-select {
+ --control-select-color: var(--tile-color);
+ --control-select-padding: 0;
+ --control-select-thickness: 40px;
+ --control-select-border-radius: 10px;
+ --control-select-button-border-radius: 10px;
+ }
+ .container {
+ padding: 0 12px 12px 12px;
+ width: auto;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-humidifier-modes-tile-feature": HuiHumidifierModeTileFeature;
+ }
+}
diff --git a/src/panels/lovelace/tile-features/types.ts b/src/panels/lovelace/tile-features/types.ts
index 8eb4fa556ef9..f91c13631bff 100644
--- a/src/panels/lovelace/tile-features/types.ts
+++ b/src/panels/lovelace/tile-features/types.ts
@@ -64,6 +64,10 @@ export interface WaterHeaterOperationModesTileFeatureConfig {
operation_modes?: OperationMode[];
}
+export interface HumidifierModesTileFeatureConfig {
+ type: "humidifier-modes";
+}
+
export const VACUUM_COMMANDS = [
"start_pause",
"stop",
@@ -97,6 +101,7 @@ export type LovelaceTileFeatureConfig =
| CoverTiltPositionTileFeatureConfig
| CoverTiltTileFeatureConfig
| FanSpeedTileFeatureConfig
+ | HumidifierModesTileFeatureConfig
| LawnMowerCommandsTileFeatureConfig
| LightBrightnessTileFeatureConfig
| LightColorTempTileFeatureConfig