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:
Paul Bottein 2023-08-23 14:35:54 +02:00 committed by GitHub
parent 370ec9cd98
commit 5ce31f3177
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 75 deletions

View File

@ -59,6 +59,8 @@ const A11Y_KEY_CODES = new Set([
"End",
]);
export type ControlCircularSliderMode = "start" | "end" | "full";
@customElement("ha-control-circular-slider")
export class HaControlCircularSlider extends LitElement {
@property({ type: Boolean, reflect: true })
@ -67,8 +69,11 @@ export class HaControlCircularSlider extends LitElement {
@property({ type: Boolean })
public dual?: boolean;
@property({ type: Boolean, reflect: true })
public inverted?: boolean;
@property({ type: String })
public mode?: ControlCircularSliderMode;
@property({ type: Boolean })
public inactive?: boolean;
@property({ type: String })
public label?: string;
@ -407,12 +412,10 @@ export class HaControlCircularSlider extends LitElement {
protected renderArc(
id: string,
value: number | undefined,
inverted: boolean | undefined
mode: ControlCircularSliderMode
) {
if (this.disabled) return nothing;
const limit = inverted ? this.max : this.min;
const path = svgArc({
x: 0,
y: 0,
@ -421,82 +424,100 @@ export class HaControlCircularSlider extends LitElement {
r: RADIUS,
});
const limit = mode === "end" ? this.max : this.min;
const current = this.current ?? 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
? inverted
const activeArc = showActive
? mode === "end"
? this._strokeDashArc(target, current)
: this._strokeDashArc(current, target)
: this._strokeCircleDashArc(target);
const arcDashArray = inverted
? this._strokeDashArc(target, limit)
: this._strokeDashArc(limit, target);
const coloredArc =
mode === "full"
? this._strokeDashArc(this.min, this.max)
: mode === "end"
? this._strokeDashArc(target, limit)
: this._strokeDashArc(limit, target);
const targetCircleDashArray = this._strokeCircleDashArc(target);
const targetCircle = this._strokeCircleDashArc(target);
const currentCircleDashArray =
const currentCircle =
this.current != null &&
showActive &&
current <= this.max &&
current >= this.min
this.current <= this.max &&
this.current >= this.min &&
(showActive || this.mode === "full")
? this._strokeCircleDashArc(this.current)
: undefined;
return svg`
<path
class="arc arc-clear"
d=${path}
stroke-dasharray=${arcDashArray[0]}
stroke-dashoffset=${arcDashArray[1]}
/>
<path
class="arc arc-background ${classMap({ [id]: true })}"
d=${path}
stroke-dasharray=${arcDashArray[0]}
stroke-dashoffset=${arcDashArray[1]}
/>
<path
.id=${id}
d=${path}
class="arc arc-active ${classMap({ [id]: true })}"
stroke-dasharray=${activeArcDashArray[0]}
stroke-dashoffset=${activeArcDashArray[1]}
role="slider"
tabindex="0"
aria-valuemin=${this.min}
aria-valuemax=${this.max}
aria-valuenow=${
this._localValue != null
? this._steppedValue(this._localValue)
: undefined
<g class=${classMap({ inactive: Boolean(this.inactive) })}>
<path
class="arc arc-clear"
d=${path}
stroke-dasharray=${coloredArc[0]}
stroke-dashoffset=${coloredArc[1]}
/>
<path
class="arc arc-colored ${classMap({ [id]: true })}"
d=${path}
stroke-dasharray=${coloredArc[0]}
stroke-dashoffset=${coloredArc[1]}
/>
<path
.id=${id}
d=${path}
class="arc arc-active ${classMap({ [id]: true })}"
stroke-dasharray=${activeArc[0]}
stroke-dashoffset=${activeArc[1]}
role="slider"
tabindex="0"
aria-valuemin=${this.min}
aria-valuemax=${this.max}
aria-valuenow=${
this._localValue != null
? this._steppedValue(this._localValue)
: undefined
}
aria-disabled=${this.disabled}
aria-label=${ifDefined(this.lowLabel ?? this.label)}
@keydown=${this._handleKeyDown}
@keyup=${this._handleKeyUp}
/>
${
currentCircle
? svg`
<path
class="current arc-current"
d=${path}
stroke-dasharray=${currentCircle[0]}
stroke-dashoffset=${currentCircle[1]}
/>
`
: nothing
}
aria-disabled=${this.disabled}
aria-label=${ifDefined(this.lowLabel ?? this.label)}
@keydown=${this._handleKeyDown}
@keyup=${this._handleKeyUp}
/>
${
currentCircleDashArray
? svg`
<path
class="current arc-current"
d=${path}
stroke-dasharray=${currentCircleDashArray[0]}
stroke-dashoffset=${currentCircleDashArray[1]}
/>
`
: nothing
}
<path
class="target"
d=${path}
stroke-dasharray=${targetCircleDashArray[0]}
stroke-dashoffset=${targetCircleDashArray[1]}
/>
<path
class="target-border ${classMap({ [id]: true })}"
d=${path}
stroke-dasharray=${targetCircle[0]}
stroke-dashoffset=${targetCircle[1]}
/>
<path
class="target"
d=${path}
stroke-dasharray=${targetCircle[0]}
stroke-dashoffset=${targetCircle[1]}
/>
</g>
`;
}
@ -551,11 +572,11 @@ export class HaControlCircularSlider extends LitElement {
? this.renderArc(
this.dual ? "low" : "value",
lowValue,
this.inverted
(!this.dual && this.mode) || "start"
)
: nothing}
${this.dual && highValue != null
? this.renderArc("high", highValue, true)
? this.renderArc("high", highValue, "end")
: nothing}
</g>
</g>
@ -634,6 +655,19 @@ export class HaControlCircularSlider extends LitElement {
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 {
fill: none;
stroke-linecap: round;
@ -655,7 +689,7 @@ export class HaControlCircularSlider extends LitElement {
.arc-clear {
stroke: var(--clear-background-color);
}
.arc-background {
.arc-colored {
opacity: 0.5;
}
.arc-active {
@ -667,6 +701,7 @@ export class HaControlCircularSlider extends LitElement {
.pressed .arc,
.pressed .target,
.pressed .target-border,
.pressed .current {
transition:
stroke-width 300ms ease-in-out,
@ -674,6 +709,11 @@ export class HaControlCircularSlider extends LitElement {
opacity 180ms ease-in-out;
}
.inactive .arc,
.inactive .arc-current {
opacity: 0;
}
.value {
stroke: var(--control-circular-slider-color);
}

View File

@ -163,6 +163,7 @@ export class HaMoreInfoClimateHumidity extends LitElement {
})}
>
<ha-control-circular-slider
.inactive=${!active}
.value=${this._targetHumidity}
.min=${this._min}
.max=${this._max}

View File

@ -18,12 +18,14 @@ import { clamp } from "../../../../common/number/clamp";
import { formatNumber } from "../../../../common/number/format_number";
import { debounce } from "../../../../common/util/debounce";
import "../../../../components/ha-control-circular-slider";
import type { ControlCircularSliderMode } from "../../../../components/ha-control-circular-slider";
import "../../../../components/ha-outlined-icon-button";
import "../../../../components/ha-svg-icon";
import {
CLIMATE_HVAC_ACTION_TO_MODE,
ClimateEntity,
ClimateEntityFeature,
HvacMode,
} from "../../../../data/climate";
import { UNAVAILABLE } from "../../../../data/entity";
import { HomeAssistant } from "../../../../types";
@ -31,6 +33,16 @@ import { moreInfoControlCircularSliderStyle } from "../ha-more-info-control-circ
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")
export class HaMoreInfoClimateTemperature extends LitElement {
@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 (
supportsTargetTemperature &&
this._targetTemperature.value != null &&
this.stateObj.state !== UNAVAILABLE
) {
const hasOnlyCoolMode =
hvacModes.length === 2 &&
hvacModes.includes("cool") &&
hvacModes.includes("off");
return html`
<div
class="container"
@ -287,7 +297,10 @@ export class HaMoreInfoClimateTemperature extends LitElement {
})}
>
<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}
.min=${this._min}
.max=${this._max}
@ -324,6 +337,7 @@ export class HaMoreInfoClimateTemperature extends LitElement {
})}
>
<ha-control-circular-slider
.inactive=${!active}
dual
.low=${this._targetTemperature.low}
.high=${this._targetTemperature.high}

View File

@ -184,7 +184,8 @@ export class HaMoreInfoHumidifierHumidity extends LitElement {
})}
>
<ha-control-circular-slider
.inverted=${inverted}
.inactive=${!active}
.mode=${inverted ? "end" : "start"}
.value=${targetHumidity}
.min=${this._min}
.max=${this._max}

View File

@ -9,6 +9,7 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
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";
@ -163,6 +164,7 @@ export class HaMoreInfoWaterHeaterTemperature extends LitElement {
);
const stateColor = stateColorCss(this.stateObj);
const active = stateActive(this.stateObj);
if (
supportsTargetTemperature &&
@ -177,6 +179,7 @@ export class HaMoreInfoWaterHeaterTemperature extends LitElement {
})}
>
<ha-control-circular-slider
.inactive=${!active}
.value=${this._targetTemperature}
.min=${this._min}
.max=${this._max}