mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
Circular slider improvements (#17008)
This commit is contained in:
parent
f77f7b3c36
commit
1dfd859a2d
@ -10,23 +10,23 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
private current = 22;
|
private current = 22;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private value = 19;
|
private low = 19;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private high = 25;
|
private high = 25;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private changingValue?: number;
|
private changingLow?: number;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private changingHigh?: number;
|
private changingHigh?: number;
|
||||||
|
|
||||||
private _valueChanged(ev) {
|
private _lowChanged(ev) {
|
||||||
this.value = ev.detail.value;
|
this.low = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanging(ev) {
|
private _lowChanging(ev) {
|
||||||
this.changingValue = ev.detail.value;
|
this.changingLow = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _highChanged(ev) {
|
private _highChanged(ev) {
|
||||||
@ -63,19 +63,40 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="title"><b>Single</b></p>
|
<p class="title"><b>Single</b></p>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._lowChanged}
|
||||||
@value-changing=${this._valueChanging}
|
@value-changing=${this._lowChanging}
|
||||||
.value=${this.value}
|
.value=${this.low}
|
||||||
.current=${this.current}
|
.current=${this.current}
|
||||||
step="1"
|
step="1"
|
||||||
min="10"
|
min="10"
|
||||||
max="30"
|
max="30"
|
||||||
></ha-control-circular-slider>
|
></ha-control-circular-slider>
|
||||||
<div>
|
<div>
|
||||||
Value: ${this.value} °C
|
Low: ${this.low} °C
|
||||||
<br />
|
<br />
|
||||||
Changing:
|
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>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@ -84,11 +105,11 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
<p class="title"><b>Dual</b></p>
|
<p class="title"><b>Dual</b></p>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
dual
|
dual
|
||||||
@low-changed=${this._valueChanged}
|
@low-changed=${this._lowChanged}
|
||||||
@low-changing=${this._valueChanging}
|
@low-changing=${this._lowChanging}
|
||||||
@high-changed=${this._highChanged}
|
@high-changed=${this._highChanged}
|
||||||
@high-changing=${this._highChanging}
|
@high-changing=${this._highChanging}
|
||||||
.low=${this.value}
|
.low=${this.low}
|
||||||
.high=${this.high}
|
.high=${this.high}
|
||||||
.current=${this.current}
|
.current=${this.current}
|
||||||
step="1"
|
step="1"
|
||||||
@ -96,10 +117,10 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
max="30"
|
max="30"
|
||||||
></ha-control-circular-slider>
|
></ha-control-circular-slider>
|
||||||
<div>
|
<div>
|
||||||
Low value: ${this.value} °C
|
Low value: ${this.low} °C
|
||||||
<br />
|
<br />
|
||||||
Low changing:
|
Low changing:
|
||||||
${this.changingValue != null ? `${this.changingValue} °C` : "-"}
|
${this.changingLow != null ? `${this.changingLow} °C` : "-"}
|
||||||
<br />
|
<br />
|
||||||
High value: ${this.high} °C
|
High value: ${this.high} °C
|
||||||
<br />
|
<br />
|
||||||
@ -132,6 +153,10 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
--control-circular-slider-background: #ff9800;
|
--control-circular-slider-background: #ff9800;
|
||||||
--control-circular-slider-background-opacity: 0.3;
|
--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] {
|
ha-control-circular-slider[dual] {
|
||||||
--control-circular-slider-high-color: #2196f3;
|
--control-circular-slider-high-color: #2196f3;
|
||||||
--control-circular-slider-low-color: #ff9800;
|
--control-circular-slider-low-color: #ff9800;
|
||||||
|
@ -68,6 +68,9 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public dual?: boolean;
|
public dual?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
public inverted?: boolean;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public label?: string;
|
public label?: string;
|
||||||
|
|
||||||
@ -80,15 +83,15 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public value?: number;
|
public value?: number;
|
||||||
|
|
||||||
@property({ type: Number })
|
|
||||||
public current?: number;
|
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public low?: number;
|
public low?: number;
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public high?: number;
|
public high?: number;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public current?: number;
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public step = 1;
|
public step = 1;
|
||||||
|
|
||||||
@ -98,6 +101,15 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public max = 100;
|
public max = 100;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
public _localValue?: number = this.value;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
public _localLow?: number = this.low;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
public _localHigh?: number = this.high;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
public _activeSlider?: ActiveSlider;
|
public _activeSlider?: ActiveSlider;
|
||||||
|
|
||||||
@ -120,17 +132,36 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
|
|
||||||
private _boundedValue(value: number) {
|
private _boundedValue(value: number) {
|
||||||
const min =
|
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 =
|
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);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
protected firstUpdated(changedProps: PropertyValues): void {
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProps);
|
||||||
this._setupListeners();
|
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 {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._setupListeners();
|
this._setupListeners();
|
||||||
@ -164,8 +195,8 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
|
|
||||||
private _findActiveSlider(value: number): ActiveSlider {
|
private _findActiveSlider(value: number): ActiveSlider {
|
||||||
if (!this.dual) return "value";
|
if (!this.dual) return "value";
|
||||||
const low = Math.max(this.low ?? this.min, this.min);
|
const low = Math.max(this._localLow ?? this.min, this.min);
|
||||||
const high = Math.min(this.high ?? this.max, this.max);
|
const high = Math.min(this._localHigh ?? this.max, this.max);
|
||||||
if (low >= value) {
|
if (low >= value) {
|
||||||
return "low";
|
return "low";
|
||||||
}
|
}
|
||||||
@ -178,13 +209,29 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _setActiveValue(value: number) {
|
private _setActiveValue(value: number) {
|
||||||
if (!this._activeSlider) return;
|
switch (this._activeSlider) {
|
||||||
this[this._activeSlider] = value;
|
case "high":
|
||||||
|
this._localHigh = value;
|
||||||
|
break;
|
||||||
|
case "low":
|
||||||
|
this._localLow = value;
|
||||||
|
break;
|
||||||
|
case "value":
|
||||||
|
this._localValue = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getActiveValue(): number | undefined {
|
private _getActiveValue(): number | undefined {
|
||||||
if (!this._activeSlider) return undefined;
|
switch (this._activeSlider) {
|
||||||
return this[this._activeSlider];
|
case "high":
|
||||||
|
return this._localHigh;
|
||||||
|
case "low":
|
||||||
|
return this._localLow;
|
||||||
|
case "value":
|
||||||
|
return this._localValue;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setupListeners() {
|
_setupListeners() {
|
||||||
@ -235,6 +282,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
const raw = this._percentageToValue(percentage);
|
const raw = this._percentageToValue(percentage);
|
||||||
const bounded = this._boundedValue(raw);
|
const bounded = this._boundedValue(raw);
|
||||||
const stepped = this._steppedValue(bounded);
|
const stepped = this._steppedValue(bounded);
|
||||||
|
this._setActiveValue(stepped);
|
||||||
if (this._activeSlider) {
|
if (this._activeSlider) {
|
||||||
fireEvent(this, `${this._activeSlider}-changing`, {
|
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||||
value: undefined,
|
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 {
|
protected render(): TemplateResult {
|
||||||
const trackPath = arc({ x: 0, y: 0, start: 0, end: MAX_ANGLE, r: RADIUS });
|
const trackPath = arc({ x: 0, y: 0, start: 0, end: MAX_ANGLE, r: RADIUS });
|
||||||
|
|
||||||
const maxRatio = MAX_ANGLE / 360;
|
const lowValue = this.dual ? this._localLow : this._localValue;
|
||||||
|
const highValue = this._localHigh;
|
||||||
const f = RADIUS * 2 * Math.PI;
|
|
||||||
const lowValue = this.dual ? this.low : this.value;
|
|
||||||
const highValue = this.high;
|
|
||||||
const lowPercentage = this._valueToPercentage(lowValue ?? this.min);
|
const lowPercentage = this._valueToPercentage(lowValue ?? this.min);
|
||||||
const highPercentage = this._valueToPercentage(highValue ?? this.max);
|
const highPercentage = this._valueToPercentage(highValue ?? this.max);
|
||||||
|
|
||||||
const lowArcLength = lowPercentage * f * maxRatio;
|
const [lowStrokeDasharray, lowStrokeDashOffset] = this._strokeDashArc(
|
||||||
const lowStrokeDasharray = `${lowArcLength} ${f - lowArcLength}`;
|
lowPercentage,
|
||||||
|
this.inverted
|
||||||
|
);
|
||||||
|
|
||||||
const highArcLength = (1 - highPercentage) * f * maxRatio;
|
const [highStrokeDasharray, highStrokeDashOffset] = this._strokeDashArc(
|
||||||
const highStrokeDasharray = `${highArcLength} ${f - highArcLength}`;
|
highPercentage,
|
||||||
const highStrokeDashOffset = `${highArcLength + f * (1 - maxRatio)}`;
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const currentPercentage = this._valueToPercentage(this.current ?? 0);
|
const currentPercentage = this._valueToPercentage(this.current ?? 0);
|
||||||
const currentAngle = currentPercentage * MAX_ANGLE;
|
const currentAngle = currentPercentage * MAX_ANGLE;
|
||||||
@ -381,27 +447,31 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
</g>
|
</g>
|
||||||
<g id="display">
|
<g id="display">
|
||||||
<path class="background" d=${trackPath} />
|
<path class="background" d=${trackPath} />
|
||||||
<circle
|
${lowValue != null
|
||||||
.id=${this.dual ? "low" : "value"}
|
? svg`
|
||||||
class="track"
|
<circle
|
||||||
cx="0"
|
.id=${this.dual ? "low" : "value"}
|
||||||
cy="0"
|
class="track"
|
||||||
r=${RADIUS}
|
cx="0"
|
||||||
stroke-dasharray=${lowStrokeDasharray}
|
cy="0"
|
||||||
stroke-dashoffset="0"
|
r=${RADIUS}
|
||||||
role="slider"
|
stroke-dasharray=${lowStrokeDasharray}
|
||||||
tabindex="0"
|
stroke-dashoffset=${lowStrokeDashOffset}
|
||||||
aria-valuemin=${this.min}
|
role="slider"
|
||||||
aria-valuemax=${this.max}
|
tabindex="0"
|
||||||
aria-valuenow=${lowValue != null
|
aria-valuemin=${this.min}
|
||||||
? this._steppedValue(lowValue)
|
aria-valuemax=${this.max}
|
||||||
: undefined}
|
aria-valuenow=${
|
||||||
aria-disabled=${this.disabled}
|
lowValue != null ? this._steppedValue(lowValue) : undefined
|
||||||
aria-label=${ifDefined(this.lowLabel ?? this.label)}
|
}
|
||||||
@keydown=${this._handleKeyDown}
|
aria-disabled=${this.disabled}
|
||||||
@keyup=${this._handleKeyUp}
|
aria-label=${ifDefined(this.lowLabel ?? this.label)}
|
||||||
/>
|
@keydown=${this._handleKeyDown}
|
||||||
${this.dual
|
@keyup=${this._handleKeyUp}
|
||||||
|
/>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${this.dual && highValue != null
|
||||||
? svg`
|
? svg`
|
||||||
<circle
|
<circle
|
||||||
id="high"
|
id="high"
|
||||||
@ -496,6 +566,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
fill: none;
|
fill: none;
|
||||||
stroke: var(--control-circular-slider-background);
|
stroke: var(--control-circular-slider-background);
|
||||||
opacity: var(--control-circular-slider-background-opacity);
|
opacity: var(--control-circular-slider-background-opacity);
|
||||||
|
transition: stroke 180ms ease-in-out, opacity 180ms ease-in-out;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke-width: 24px;
|
stroke-width: 24px;
|
||||||
}
|
}
|
||||||
@ -507,7 +578,8 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
stroke-width: 24px;
|
stroke-width: 24px;
|
||||||
transition: stroke-width 300ms ease-in-out,
|
transition: stroke-width 300ms ease-in-out,
|
||||||
stroke-dasharray 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 {
|
.track:focus-visible {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user