Circular slider improvements (#17008)

This commit is contained in:
Paul Bottein 2023-06-26 11:49:11 +02:00 committed by GitHub
parent f77f7b3c36
commit 1dfd859a2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 158 additions and 61 deletions

View File

@ -10,23 +10,23 @@ export class DemoHaCircularSlider extends LitElement {
private current = 22;
@state()
private value = 19;
private low = 19;
@state()
private high = 25;
@state()
private changingValue?: number;
private changingLow?: number;
@state()
private changingHigh?: number;
private _valueChanged(ev) {
this.value = ev.detail.value;
private _lowChanged(ev) {
this.low = ev.detail.value;
}
private _valueChanging(ev) {
this.changingValue = ev.detail.value;
private _lowChanging(ev) {
this.changingLow = ev.detail.value;
}
private _highChanged(ev) {
@ -63,19 +63,40 @@ export class DemoHaCircularSlider extends LitElement {
<div class="card-content">
<p class="title"><b>Single</b></p>
<ha-control-circular-slider
@value-changed=${this._valueChanged}
@value-changing=${this._valueChanging}
.value=${this.value}
@value-changed=${this._lowChanged}
@value-changing=${this._lowChanging}
.value=${this.low}
.current=${this.current}
step="1"
min="10"
max="30"
></ha-control-circular-slider>
<div>
Value: ${this.value} °C
Low: ${this.low} °C
<br />
Changing:
${this.changingValue != null ? `${this.changingValue} °C` : "-"}
${this.changingLow != null ? `${this.changingLow} °C` : "-"}
</div>
</div>
</ha-card>
<ha-card>
<div class="card-content">
<p class="title"><b>Inverted</b></p>
<ha-control-circular-slider
inverted
@value-changed=${this._highChanged}
@value-changing=${this._highChanging}
.value=${this.high}
.current=${this.current}
step="1"
min="10"
max="30"
></ha-control-circular-slider>
<div>
High: ${this.high} °C
<br />
Changing:
${this.changingHigh != null ? `${this.changingHigh} °C` : "-"}
</div>
</div>
</ha-card>
@ -84,11 +105,11 @@ export class DemoHaCircularSlider extends LitElement {
<p class="title"><b>Dual</b></p>
<ha-control-circular-slider
dual
@low-changed=${this._valueChanged}
@low-changing=${this._valueChanging}
@low-changed=${this._lowChanged}
@low-changing=${this._lowChanging}
@high-changed=${this._highChanged}
@high-changing=${this._highChanging}
.low=${this.value}
.low=${this.low}
.high=${this.high}
.current=${this.current}
step="1"
@ -96,10 +117,10 @@ export class DemoHaCircularSlider extends LitElement {
max="30"
></ha-control-circular-slider>
<div>
Low value: ${this.value} °C
Low value: ${this.low} °C
<br />
Low changing:
${this.changingValue != null ? `${this.changingValue} °C` : "-"}
${this.changingLow != null ? `${this.changingLow} °C` : "-"}
<br />
High value: ${this.high} °C
<br />
@ -132,6 +153,10 @@ export class DemoHaCircularSlider extends LitElement {
--control-circular-slider-background: #ff9800;
--control-circular-slider-background-opacity: 0.3;
}
ha-control-circular-slider[inverted] {
--control-circular-slider-color: #2196f3;
--control-circular-slider-background: #2196f3;
}
ha-control-circular-slider[dual] {
--control-circular-slider-high-color: #2196f3;
--control-circular-slider-low-color: #ff9800;

View File

@ -68,6 +68,9 @@ export class HaControlCircularSlider extends LitElement {
@property({ type: Boolean })
public dual?: boolean;
@property({ type: Boolean, reflect: true })
public inverted?: boolean;
@property({ type: String })
public label?: string;
@ -80,15 +83,15 @@ export class HaControlCircularSlider extends LitElement {
@property({ type: Number })
public value?: number;
@property({ type: Number })
public current?: number;
@property({ type: Number })
public low?: number;
@property({ type: Number })
public high?: number;
@property({ type: Number })
public current?: number;
@property({ type: Number })
public step = 1;
@ -98,6 +101,15 @@ export class HaControlCircularSlider extends LitElement {
@property({ type: Number })
public max = 100;
@state()
public _localValue?: number = this.value;
@state()
public _localLow?: number = this.low;
@state()
public _localHigh?: number = this.high;
@state()
public _activeSlider?: ActiveSlider;
@ -120,17 +132,36 @@ export class HaControlCircularSlider extends LitElement {
private _boundedValue(value: number) {
const min =
this._activeSlider === "high" ? Math.min(this.low ?? this.max) : this.min;
this._activeSlider === "high"
? Math.min(this._localLow ?? this.max)
: this.min;
const max =
this._activeSlider === "low" ? Math.max(this.high ?? this.min) : this.max;
this._activeSlider === "low"
? Math.max(this._localHigh ?? this.min)
: this.max;
return Math.min(Math.max(value, min), max);
}
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._setupListeners();
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (!this._activeSlider) {
if (changedProps.has("value")) {
this._localValue = this.value;
}
if (changedProps.has("low")) {
this._localLow = this.low;
}
if (changedProps.has("high")) {
this._localHigh = this.high;
}
}
}
connectedCallback(): void {
super.connectedCallback();
this._setupListeners();
@ -164,8 +195,8 @@ export class HaControlCircularSlider extends LitElement {
private _findActiveSlider(value: number): ActiveSlider {
if (!this.dual) return "value";
const low = Math.max(this.low ?? this.min, this.min);
const high = Math.min(this.high ?? this.max, this.max);
const low = Math.max(this._localLow ?? this.min, this.min);
const high = Math.min(this._localHigh ?? this.max, this.max);
if (low >= value) {
return "low";
}
@ -178,13 +209,29 @@ export class HaControlCircularSlider extends LitElement {
}
private _setActiveValue(value: number) {
if (!this._activeSlider) return;
this[this._activeSlider] = value;
switch (this._activeSlider) {
case "high":
this._localHigh = value;
break;
case "low":
this._localLow = value;
break;
case "value":
this._localValue = value;
break;
}
}
private _getActiveValue(): number | undefined {
if (!this._activeSlider) return undefined;
return this[this._activeSlider];
switch (this._activeSlider) {
case "high":
return this._localHigh;
case "low":
return this._localLow;
case "value":
return this._localValue;
}
return undefined;
}
_setupListeners() {
@ -235,6 +282,7 @@ export class HaControlCircularSlider extends LitElement {
const raw = this._percentageToValue(percentage);
const bounded = this._boundedValue(raw);
const stepped = this._steppedValue(bounded);
this._setActiveValue(stepped);
if (this._activeSlider) {
fireEvent(this, `${this._activeSlider}-changing`, {
value: undefined,
@ -340,23 +388,41 @@ export class HaControlCircularSlider extends LitElement {
}
}
private _strokeDashArc(
percentage: number,
inverted?: boolean
): [string, string] {
const maxRatio = MAX_ANGLE / 360;
const f = RADIUS * 2 * Math.PI;
if (inverted) {
const arcLength = (1 - percentage) * f * maxRatio;
const strokeDasharray = `${arcLength} ${f - arcLength}`;
const strokeDashOffset = `${arcLength + f * (1 - maxRatio)}`;
return [strokeDasharray, strokeDashOffset];
}
const arcLength = percentage * f * maxRatio;
const strokeDasharray = `${arcLength} ${f - arcLength}`;
const strokeDashOffset = "0";
return [strokeDasharray, strokeDashOffset];
}
protected render(): TemplateResult {
const trackPath = arc({ x: 0, y: 0, start: 0, end: MAX_ANGLE, r: RADIUS });
const maxRatio = MAX_ANGLE / 360;
const f = RADIUS * 2 * Math.PI;
const lowValue = this.dual ? this.low : this.value;
const highValue = this.high;
const lowValue = this.dual ? this._localLow : this._localValue;
const highValue = this._localHigh;
const lowPercentage = this._valueToPercentage(lowValue ?? this.min);
const highPercentage = this._valueToPercentage(highValue ?? this.max);
const lowArcLength = lowPercentage * f * maxRatio;
const lowStrokeDasharray = `${lowArcLength} ${f - lowArcLength}`;
const [lowStrokeDasharray, lowStrokeDashOffset] = this._strokeDashArc(
lowPercentage,
this.inverted
);
const highArcLength = (1 - highPercentage) * f * maxRatio;
const highStrokeDasharray = `${highArcLength} ${f - highArcLength}`;
const highStrokeDashOffset = `${highArcLength + f * (1 - maxRatio)}`;
const [highStrokeDasharray, highStrokeDashOffset] = this._strokeDashArc(
highPercentage,
true
);
const currentPercentage = this._valueToPercentage(this.current ?? 0);
const currentAngle = currentPercentage * MAX_ANGLE;
@ -381,27 +447,31 @@ export class HaControlCircularSlider extends LitElement {
</g>
<g id="display">
<path class="background" d=${trackPath} />
<circle
.id=${this.dual ? "low" : "value"}
class="track"
cx="0"
cy="0"
r=${RADIUS}
stroke-dasharray=${lowStrokeDasharray}
stroke-dashoffset="0"
role="slider"
tabindex="0"
aria-valuemin=${this.min}
aria-valuemax=${this.max}
aria-valuenow=${lowValue != null
? this._steppedValue(lowValue)
: undefined}
aria-disabled=${this.disabled}
aria-label=${ifDefined(this.lowLabel ?? this.label)}
@keydown=${this._handleKeyDown}
@keyup=${this._handleKeyUp}
/>
${this.dual
${lowValue != null
? svg`
<circle
.id=${this.dual ? "low" : "value"}
class="track"
cx="0"
cy="0"
r=${RADIUS}
stroke-dasharray=${lowStrokeDasharray}
stroke-dashoffset=${lowStrokeDashOffset}
role="slider"
tabindex="0"
aria-valuemin=${this.min}
aria-valuemax=${this.max}
aria-valuenow=${
lowValue != null ? this._steppedValue(lowValue) : undefined
}
aria-disabled=${this.disabled}
aria-label=${ifDefined(this.lowLabel ?? this.label)}
@keydown=${this._handleKeyDown}
@keyup=${this._handleKeyUp}
/>
`
: nothing}
${this.dual && highValue != null
? svg`
<circle
id="high"
@ -496,6 +566,7 @@ export class HaControlCircularSlider extends LitElement {
fill: none;
stroke: var(--control-circular-slider-background);
opacity: var(--control-circular-slider-background-opacity);
transition: stroke 180ms ease-in-out, opacity 180ms ease-in-out;
stroke-linecap: round;
stroke-width: 24px;
}
@ -507,7 +578,8 @@ export class HaControlCircularSlider extends LitElement {
stroke-width: 24px;
transition: stroke-width 300ms ease-in-out,
stroke-dasharray 300ms ease-in-out,
stroke-dashoffset 300ms ease-in-out;
stroke-dashoffset 300ms ease-in-out, stroke 180ms ease-in-out,
opacity 180ms ease-in-out;
}
.track:focus-visible {