diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts index c9afb21227..cfa34d81e8 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -120,7 +120,8 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {

${name}

diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 391cd34c13..cedd812916 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -456,6 +456,7 @@ export interface ThermostatCardConfig extends LovelaceCardConfig { entity: string; theme?: string; name?: string; + show_current_as_primary?: boolean; features?: LovelaceCardFeatureConfig[]; } diff --git a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts index a2f33e6b59..09d2efe0cf 100644 --- a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts @@ -6,19 +6,23 @@ import { array, assert, assign, + boolean, 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 { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; -import type { ThermostatCardConfig } from "../../cards/types"; import { LovelaceCardFeatureConfig, LovelaceCardFeatureContext, } from "../../card-features/types"; +import type { ThermostatCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import "../hui-sub-element-editor"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; @@ -37,6 +41,7 @@ const cardConfigStruct = assign( entity: optional(string()), name: optional(string()), theme: optional(string()), + show_current_as_primary: optional(boolean()), features: optional(array(any())), }) ); @@ -51,7 +56,13 @@ const SCHEMA = [ { name: "theme", selector: { theme: {} } }, ], }, -] as const; + { + name: "show_current_as_primary", + selector: { + boolean: {}, + }, + }, +] as const satisfies readonly HaFormSchema[]; @customElement("hui-thermostat-card-editor") export class HuiThermostatCardEditor @@ -175,9 +186,9 @@ export class HuiThermostatCardEditor } private _computeLabelCallback = (schema: SchemaUnion) => { - if (schema.name === "entity") { + if (schema.name === "show_current_as_primary") { return this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.entity" + "ui.panel.lovelace.editor.card.thermostat.show_current_as_primary" ); } diff --git a/src/state-control/climate/ha-state-control-climate-temperature.ts b/src/state-control/climate/ha-state-control-climate-temperature.ts index 7d074b2e4e..7634fd2f2d 100644 --- a/src/state-control/climate/ha-state-control-climate-temperature.ts +++ b/src/state-control/climate/ha-state-control-climate-temperature.ts @@ -1,5 +1,12 @@ -import { mdiMinus, mdiPlus, mdiThermometer } from "@mdi/js"; -import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit"; +import { mdiMinus, mdiPlus, mdiThermometer, mdiThermostat } from "@mdi/js"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; @@ -8,6 +15,8 @@ import { stateActive } from "../../common/entity/state_active"; import { stateColorCss } from "../../common/entity/state_color"; import { supportsFeature } from "../../common/entity/supports-feature"; import { clamp } from "../../common/number/clamp"; +import { formatNumber } from "../../common/number/format_number"; +import { blankBeforeUnit } from "../../common/translations/blank_before_unit"; import { debounce } from "../../common/util/debounce"; import "../../components/ha-big-number"; import "../../components/ha-control-circular-slider"; @@ -45,8 +54,11 @@ export class HaStateControlClimateTemperature extends LitElement { @property({ attribute: false }) public stateObj!: ClimateEntity; - @property({ attribute: "show-current", type: Boolean }) - public showCurrent?: boolean; + @property({ attribute: "show-secondary", type: Boolean }) + public showSecondary?: boolean; + + @property({ attribute: "use-current-as-primary", type: Boolean }) + public showCurrentAsPrimary?: boolean; @property({ type: Boolean, attribute: "prevent-interaction-on-scroll" }) public preventInteractionOnScroll?: boolean; @@ -163,36 +175,13 @@ export class HaStateControlClimateTemperature extends LitElement { `; } - if ( - (!supportsFeature( - this.stateObj, - ClimateEntityFeature.TARGET_TEMPERATURE - ) || - this._targetTemperature.value === null) && - (!supportsFeature( - this.stateObj, - ClimateEntityFeature.TARGET_TEMPERATURE_RANGE - ) || - this._targetTemperature.low === null || - this._targetTemperature.high === null) - ) { - return html` -

${this.hass.formatEntityState(this.stateObj)}

- `; - } - const action = this.stateObj.attributes.hvac_action; - const actionLabel = this.hass.formatEntityAttributeValue( - this.stateObj, - "hvac_action" - ); - return html`

- ${action && action !== "off" && action !== "idle" - ? actionLabel - : this.hass.localize("ui.card.climate.target")} + ${action + ? this.hass.formatEntityAttributeValue(this.stateObj, "hvac_action") + : this.hass.formatEntityState(this.stateObj)}

`; } @@ -234,52 +223,175 @@ export class HaStateControlClimateTemperature extends LitElement { `; } - private _renderTargetTemperature(temperature: number) { + private _renderTarget( + temperature: number, + style: "normal" | "big", + hideUnit?: boolean + ) { const digits = this._step.toString().split(".")?.[1]?.length ?? 0; const formatOptions: Intl.NumberFormatOptions = { maximumFractionDigits: digits, minimumFractionDigits: digits, }; - return html` - - `; + + const unit = hideUnit ? "" : this.hass.config.unit_system.temperature; + + if (style === "big") { + return html` + + `; + } + + const formatted = formatNumber( + temperature, + this.hass.locale, + formatOptions + ); + return html`${formatted}${blankBeforeUnit(unit)}${unit}`; } - private _renderCurrentTemperature(temperature?: number) { - if (!this.showCurrent || temperature == null) { - return html`

 

`; + private _renderCurrent(temperature: number, style: "normal" | "big") { + const formatOptions: Intl.NumberFormatOptions = { + maximumFractionDigits: 1, + }; + if (style === "big") { + return html` + + `; } return html` -

- - - ${this.hass.formatEntityAttributeValue( - this.stateObj, - "current_temperature", - temperature - )} - -

+ ${this.hass.formatEntityAttributeValue( + this.stateObj, + "current_temperature", + temperature + )} `; } + private _renderPrimary() { + const currentTemperature = this.stateObj.attributes.current_temperature; + + if (currentTemperature != null && this.showCurrentAsPrimary) { + return this._renderCurrent(currentTemperature, "big"); + } + + if (this._supportsTargetTemperature && !this.showCurrentAsPrimary) { + return this._renderTarget(this._targetTemperature.value!, "big"); + } + + if (this._supportsTargetTemperatureRange && !this.showCurrentAsPrimary) { + return html` +
+ + +
+ `; + } + + return nothing; + } + + private _renderSecondary() { + if (!this.showSecondary) { + return html`

`; + } + + const currentTemperature = this.stateObj.attributes.current_temperature; + + if (currentTemperature && !this.showCurrentAsPrimary) { + return html` +

+ + ${this._renderCurrent(currentTemperature, "normal")} +

+ `; + } + + if (this._supportsTargetTemperature && this.showCurrentAsPrimary) { + return html` +

+ + ${this._renderTarget(this._targetTemperature.value!, "normal")} +

+ `; + } + + if (this._supportsTargetTemperatureRange && this.showCurrentAsPrimary) { + return html` +

+ + + βΈ± + +

+ `; + } + + return html`

`; + } + + get _supportsTargetTemperature() { + return ( + supportsFeature(this.stateObj, ClimateEntityFeature.TARGET_TEMPERATURE) && + this._targetTemperature.value != null + ); + } + + get _supportsTargetTemperatureRange() { + return ( + supportsFeature( + this.stateObj, + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) && + this._targetTemperature.low != null && + this._targetTemperature.high != null + ); + } + protected render() { - const supportsTargetTemperature = supportsFeature( - this.stateObj, - ClimateEntityFeature.TARGET_TEMPERATURE - ); - - const supportsTargetTemperatureRange = supportsFeature( - this.stateObj, - ClimateEntityFeature.TARGET_TEMPERATURE_RANGE - ); - const mode = this.stateObj.state; const action = this.stateObj.attributes.hvac_action; const active = stateActive(this.stateObj); @@ -301,8 +413,7 @@ export class HaStateControlClimateTemperature extends LitElement { : ""; if ( - supportsTargetTemperature && - this._targetTemperature.value != null && + this._supportsTargetTemperature && this.stateObj.state !== UNAVAILABLE ) { const heatCoolModes = this.stateObj.attributes.hvac_modes.filter((m) => @@ -337,11 +448,7 @@ export class HaStateControlClimateTemperature extends LitElement { >
- ${this._renderLabel()} - ${this._renderTargetTemperature(this._targetTemperature.value)} - ${this._renderCurrentTemperature( - this.stateObj.attributes.current_temperature - )} + ${this._renderLabel()}${this._renderPrimary()}${this._renderSecondary()}
${this._renderTemperatureButtons("value")} @@ -349,9 +456,7 @@ export class HaStateControlClimateTemperature extends LitElement { } if ( - supportsTargetTemperatureRange && - this._targetTemperature.low != null && - this._targetTemperature.high != null && + this._supportsTargetTemperatureRange && this.stateObj.state !== UNAVAILABLE ) { return html` @@ -380,30 +485,7 @@ export class HaStateControlClimateTemperature extends LitElement { >
- ${this._renderLabel()} -
- - -
- ${this._renderCurrentTemperature( - this.stateObj.attributes.current_temperature - )} + ${this._renderLabel()}${this._renderPrimary()}${this._renderSecondary()}
${this._renderTemperatureButtons(this._selectTargetTemperature, true)} @@ -429,10 +511,7 @@ export class HaStateControlClimateTemperature extends LitElement { >
- ${this._renderLabel()} - ${this._renderCurrentTemperature( - this.stateObj.attributes.current_temperature - )} + ${this._renderLabel()} ${this._renderSecondary()}
`; @@ -448,24 +527,26 @@ export class HaStateControlClimateTemperature extends LitElement { flex-direction: row; gap: 24px; } - .dual button { + .target-button { outline: none; background: none; color: inherit; font-family: inherit; + font-size: inherit; + font-weight: inherit; -webkit-tap-highlight-color: transparent; border: none; - opacity: 0.5; + opacity: 0.7; padding: 0; transition: opacity 180ms ease-in-out, transform 180ms ease-in-out; cursor: pointer; } - .dual button:focus-visible { + .target-button:focus-visible { transform: scale(1.1); } - .dual button.selected { + .target-button.selected { opacity: 1; } .container.md .dual { @@ -475,6 +556,9 @@ export class HaStateControlClimateTemperature extends LitElement { .container.xs .dual { gap: 8px; } + .container.sm .target-icon { + display: none; + } ha-control-circular-slider { --control-circular-slider-low-color: var( --low-color, diff --git a/src/translations/en.json b/src/translations/en.json index 0de14a10ec..e6890de1d7 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5235,7 +5235,8 @@ }, "thermostat": { "name": "Thermostat", - "description": "The Thermostat card gives control of your climate entity. Allowing you to change the temperature and mode of the entity." + "description": "The Thermostat card gives control of your climate entity. Allowing you to change the temperature and mode of the entity.", + "show_current_as_primary": "Show current temperature as primary information" }, "tile": { "name": "Tile",