mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
Adapt circular slider style for climate, water_heater and humidifier (#17677)
* Add more rendering mode for circular slider * Improve transitions
This commit is contained in:
parent
370ec9cd98
commit
5ce31f3177
@ -59,6 +59,8 @@ const A11Y_KEY_CODES = new Set([
|
|||||||
"End",
|
"End",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export type ControlCircularSliderMode = "start" | "end" | "full";
|
||||||
|
|
||||||
@customElement("ha-control-circular-slider")
|
@customElement("ha-control-circular-slider")
|
||||||
export class HaControlCircularSlider extends LitElement {
|
export class HaControlCircularSlider extends LitElement {
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
@ -67,8 +69,11 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public dual?: boolean;
|
public dual?: boolean;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: String })
|
||||||
public inverted?: boolean;
|
public mode?: ControlCircularSliderMode;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public inactive?: boolean;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public label?: string;
|
public label?: string;
|
||||||
@ -407,12 +412,10 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
protected renderArc(
|
protected renderArc(
|
||||||
id: string,
|
id: string,
|
||||||
value: number | undefined,
|
value: number | undefined,
|
||||||
inverted: boolean | undefined
|
mode: ControlCircularSliderMode
|
||||||
) {
|
) {
|
||||||
if (this.disabled) return nothing;
|
if (this.disabled) return nothing;
|
||||||
|
|
||||||
const limit = inverted ? this.max : this.min;
|
|
||||||
|
|
||||||
const path = svgArc({
|
const path = svgArc({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@ -421,50 +424,61 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
r: RADIUS,
|
r: RADIUS,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const limit = mode === "end" ? this.max : this.min;
|
||||||
|
|
||||||
const current = this.current ?? limit;
|
const current = this.current ?? limit;
|
||||||
const target = value ?? limit;
|
const target = value ?? limit;
|
||||||
|
|
||||||
const showActive = inverted ? target <= current : current <= target;
|
const showActive =
|
||||||
|
mode === "end"
|
||||||
|
? target <= current
|
||||||
|
: mode === "start"
|
||||||
|
? current <= target
|
||||||
|
: false;
|
||||||
|
|
||||||
const activeArcDashArray = showActive
|
const activeArc = showActive
|
||||||
? inverted
|
? mode === "end"
|
||||||
? this._strokeDashArc(target, current)
|
? this._strokeDashArc(target, current)
|
||||||
: this._strokeDashArc(current, target)
|
: this._strokeDashArc(current, target)
|
||||||
: this._strokeCircleDashArc(target);
|
: this._strokeCircleDashArc(target);
|
||||||
|
|
||||||
const arcDashArray = inverted
|
const coloredArc =
|
||||||
|
mode === "full"
|
||||||
|
? this._strokeDashArc(this.min, this.max)
|
||||||
|
: mode === "end"
|
||||||
? this._strokeDashArc(target, limit)
|
? this._strokeDashArc(target, limit)
|
||||||
: this._strokeDashArc(limit, target);
|
: this._strokeDashArc(limit, target);
|
||||||
|
|
||||||
const targetCircleDashArray = this._strokeCircleDashArc(target);
|
const targetCircle = this._strokeCircleDashArc(target);
|
||||||
|
|
||||||
const currentCircleDashArray =
|
const currentCircle =
|
||||||
this.current != null &&
|
this.current != null &&
|
||||||
showActive &&
|
this.current <= this.max &&
|
||||||
current <= this.max &&
|
this.current >= this.min &&
|
||||||
current >= this.min
|
(showActive || this.mode === "full")
|
||||||
? this._strokeCircleDashArc(this.current)
|
? this._strokeCircleDashArc(this.current)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return svg`
|
return svg`
|
||||||
|
<g class=${classMap({ inactive: Boolean(this.inactive) })}>
|
||||||
<path
|
<path
|
||||||
class="arc arc-clear"
|
class="arc arc-clear"
|
||||||
d=${path}
|
d=${path}
|
||||||
stroke-dasharray=${arcDashArray[0]}
|
stroke-dasharray=${coloredArc[0]}
|
||||||
stroke-dashoffset=${arcDashArray[1]}
|
stroke-dashoffset=${coloredArc[1]}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
class="arc arc-background ${classMap({ [id]: true })}"
|
class="arc arc-colored ${classMap({ [id]: true })}"
|
||||||
d=${path}
|
d=${path}
|
||||||
stroke-dasharray=${arcDashArray[0]}
|
stroke-dasharray=${coloredArc[0]}
|
||||||
stroke-dashoffset=${arcDashArray[1]}
|
stroke-dashoffset=${coloredArc[1]}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
.id=${id}
|
.id=${id}
|
||||||
d=${path}
|
d=${path}
|
||||||
class="arc arc-active ${classMap({ [id]: true })}"
|
class="arc arc-active ${classMap({ [id]: true })}"
|
||||||
stroke-dasharray=${activeArcDashArray[0]}
|
stroke-dasharray=${activeArc[0]}
|
||||||
stroke-dashoffset=${activeArcDashArray[1]}
|
stroke-dashoffset=${activeArc[1]}
|
||||||
role="slider"
|
role="slider"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-valuemin=${this.min}
|
aria-valuemin=${this.min}
|
||||||
@ -480,23 +494,30 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@keyup=${this._handleKeyUp}
|
@keyup=${this._handleKeyUp}
|
||||||
/>
|
/>
|
||||||
${
|
${
|
||||||
currentCircleDashArray
|
currentCircle
|
||||||
? svg`
|
? svg`
|
||||||
<path
|
<path
|
||||||
class="current arc-current"
|
class="current arc-current"
|
||||||
d=${path}
|
d=${path}
|
||||||
stroke-dasharray=${currentCircleDashArray[0]}
|
stroke-dasharray=${currentCircle[0]}
|
||||||
stroke-dashoffset=${currentCircleDashArray[1]}
|
stroke-dashoffset=${currentCircle[1]}
|
||||||
/>
|
/>
|
||||||
`
|
`
|
||||||
: nothing
|
: nothing
|
||||||
}
|
}
|
||||||
|
<path
|
||||||
|
class="target-border ${classMap({ [id]: true })}"
|
||||||
|
d=${path}
|
||||||
|
stroke-dasharray=${targetCircle[0]}
|
||||||
|
stroke-dashoffset=${targetCircle[1]}
|
||||||
|
/>
|
||||||
<path
|
<path
|
||||||
class="target"
|
class="target"
|
||||||
d=${path}
|
d=${path}
|
||||||
stroke-dasharray=${targetCircleDashArray[0]}
|
stroke-dasharray=${targetCircle[0]}
|
||||||
stroke-dashoffset=${targetCircleDashArray[1]}
|
stroke-dashoffset=${targetCircle[1]}
|
||||||
/>
|
/>
|
||||||
|
</g>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,11 +572,11 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
? this.renderArc(
|
? this.renderArc(
|
||||||
this.dual ? "low" : "value",
|
this.dual ? "low" : "value",
|
||||||
lowValue,
|
lowValue,
|
||||||
this.inverted
|
(!this.dual && this.mode) || "start"
|
||||||
)
|
)
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.dual && highValue != null
|
${this.dual && highValue != null
|
||||||
? this.renderArc("high", highValue, true)
|
? this.renderArc("high", highValue, "end")
|
||||||
: nothing}
|
: nothing}
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
@ -634,6 +655,19 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
opacity 180ms ease-in-out;
|
opacity 180ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.target-border {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-width: 24px;
|
||||||
|
stroke: white;
|
||||||
|
transition:
|
||||||
|
stroke-width 300ms ease-in-out,
|
||||||
|
stroke-dasharray 300ms ease-in-out,
|
||||||
|
stroke-dashoffset 300ms ease-in-out,
|
||||||
|
stroke 180ms ease-in-out,
|
||||||
|
opacity 180ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
.current {
|
.current {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
@ -655,7 +689,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
.arc-clear {
|
.arc-clear {
|
||||||
stroke: var(--clear-background-color);
|
stroke: var(--clear-background-color);
|
||||||
}
|
}
|
||||||
.arc-background {
|
.arc-colored {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
.arc-active {
|
.arc-active {
|
||||||
@ -667,6 +701,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
|
|
||||||
.pressed .arc,
|
.pressed .arc,
|
||||||
.pressed .target,
|
.pressed .target,
|
||||||
|
.pressed .target-border,
|
||||||
.pressed .current {
|
.pressed .current {
|
||||||
transition:
|
transition:
|
||||||
stroke-width 300ms ease-in-out,
|
stroke-width 300ms ease-in-out,
|
||||||
@ -674,6 +709,11 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
opacity 180ms ease-in-out;
|
opacity 180ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inactive .arc,
|
||||||
|
.inactive .arc-current {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
stroke: var(--control-circular-slider-color);
|
stroke: var(--control-circular-slider-color);
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,7 @@ export class HaMoreInfoClimateHumidity extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
|
.inactive=${!active}
|
||||||
.value=${this._targetHumidity}
|
.value=${this._targetHumidity}
|
||||||
.min=${this._min}
|
.min=${this._min}
|
||||||
.max=${this._max}
|
.max=${this._max}
|
||||||
|
@ -18,12 +18,14 @@ import { clamp } from "../../../../common/number/clamp";
|
|||||||
import { formatNumber } from "../../../../common/number/format_number";
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
import { debounce } from "../../../../common/util/debounce";
|
import { debounce } from "../../../../common/util/debounce";
|
||||||
import "../../../../components/ha-control-circular-slider";
|
import "../../../../components/ha-control-circular-slider";
|
||||||
|
import type { ControlCircularSliderMode } from "../../../../components/ha-control-circular-slider";
|
||||||
import "../../../../components/ha-outlined-icon-button";
|
import "../../../../components/ha-outlined-icon-button";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
CLIMATE_HVAC_ACTION_TO_MODE,
|
CLIMATE_HVAC_ACTION_TO_MODE,
|
||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
|
HvacMode,
|
||||||
} from "../../../../data/climate";
|
} from "../../../../data/climate";
|
||||||
import { UNAVAILABLE } from "../../../../data/entity";
|
import { UNAVAILABLE } from "../../../../data/entity";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
@ -31,6 +33,16 @@ import { moreInfoControlCircularSliderStyle } from "../ha-more-info-control-circ
|
|||||||
|
|
||||||
type Target = "value" | "low" | "high";
|
type Target = "value" | "low" | "high";
|
||||||
|
|
||||||
|
const SLIDER_MODES: Record<HvacMode, ControlCircularSliderMode> = {
|
||||||
|
auto: "full",
|
||||||
|
cool: "end",
|
||||||
|
dry: "full",
|
||||||
|
fan_only: "full",
|
||||||
|
heat: "start",
|
||||||
|
heat_cool: "full",
|
||||||
|
off: "full",
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("ha-more-info-climate-temperature")
|
@customElement("ha-more-info-climate-temperature")
|
||||||
export class HaMoreInfoClimateTemperature extends LitElement {
|
export class HaMoreInfoClimateTemperature extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -267,17 +279,15 @@ export class HaMoreInfoClimateTemperature extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hvacModes = this.stateObj.attributes.hvac_modes;
|
const activeModes = this.stateObj.attributes.hvac_modes.filter(
|
||||||
|
(m) => m !== "off"
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
supportsTargetTemperature &&
|
supportsTargetTemperature &&
|
||||||
this._targetTemperature.value != null &&
|
this._targetTemperature.value != null &&
|
||||||
this.stateObj.state !== UNAVAILABLE
|
this.stateObj.state !== UNAVAILABLE
|
||||||
) {
|
) {
|
||||||
const hasOnlyCoolMode =
|
|
||||||
hvacModes.length === 2 &&
|
|
||||||
hvacModes.includes("cool") &&
|
|
||||||
hvacModes.includes("off");
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="container"
|
class="container"
|
||||||
@ -287,7 +297,10 @@ export class HaMoreInfoClimateTemperature extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
.inverted=${mode === "cool" || hasOnlyCoolMode}
|
.inactive=${!active}
|
||||||
|
.mode=${mode === "off" && activeModes.length === 1
|
||||||
|
? SLIDER_MODES[activeModes[0]]
|
||||||
|
: SLIDER_MODES[mode]}
|
||||||
.value=${this._targetTemperature.value}
|
.value=${this._targetTemperature.value}
|
||||||
.min=${this._min}
|
.min=${this._min}
|
||||||
.max=${this._max}
|
.max=${this._max}
|
||||||
@ -324,6 +337,7 @@ export class HaMoreInfoClimateTemperature extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
|
.inactive=${!active}
|
||||||
dual
|
dual
|
||||||
.low=${this._targetTemperature.low}
|
.low=${this._targetTemperature.low}
|
||||||
.high=${this._targetTemperature.high}
|
.high=${this._targetTemperature.high}
|
||||||
|
@ -184,7 +184,8 @@ export class HaMoreInfoHumidifierHumidity extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
.inverted=${inverted}
|
.inactive=${!active}
|
||||||
|
.mode=${inverted ? "end" : "start"}
|
||||||
.value=${targetHumidity}
|
.value=${targetHumidity}
|
||||||
.min=${this._min}
|
.min=${this._min}
|
||||||
.max=${this._max}
|
.max=${this._max}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
|
import { stateActive } from "../../../../common/entity/state_active";
|
||||||
import { stateColorCss } from "../../../../common/entity/state_color";
|
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||||
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||||
import { clamp } from "../../../../common/number/clamp";
|
import { clamp } from "../../../../common/number/clamp";
|
||||||
@ -163,6 +164,7 @@ export class HaMoreInfoWaterHeaterTemperature extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const stateColor = stateColorCss(this.stateObj);
|
const stateColor = stateColorCss(this.stateObj);
|
||||||
|
const active = stateActive(this.stateObj);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
supportsTargetTemperature &&
|
supportsTargetTemperature &&
|
||||||
@ -177,6 +179,7 @@ export class HaMoreInfoWaterHeaterTemperature extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
|
.inactive=${!active}
|
||||||
.value=${this._targetTemperature}
|
.value=${this._targetTemperature}
|
||||||
.min=${this._min}
|
.min=${this._min}
|
||||||
.max=${this._max}
|
.max=${this._max}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user