mirror of
https://github.com/home-assistant/frontend
synced 2024-09-28 00:43:28 +02:00
Use slider for color temp instead of wheel (#17771)
This commit is contained in:
parent
fb69deb617
commit
0d0fe75f4e
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: Temp Color Picker
|
|
||||||
---
|
|
@ -1,117 +0,0 @@
|
|||||||
import "../../../../src/components/ha-temp-color-picker";
|
|
||||||
|
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, state } from "lit/decorators";
|
|
||||||
|
|
||||||
import "../../../../src/components/ha-card";
|
|
||||||
import "../../../../src/components/ha-slider";
|
|
||||||
|
|
||||||
@customElement("demo-components-ha-temp-color-picker")
|
|
||||||
export class DemoHaTempColorPicker extends LitElement {
|
|
||||||
@state()
|
|
||||||
min = 3000;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
max = 7000;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
value = 4000;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
liveValue?: number;
|
|
||||||
|
|
||||||
private _minChanged(ev) {
|
|
||||||
this.min = Number(ev.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _maxChanged(ev) {
|
|
||||||
this.max = Number(ev.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev) {
|
|
||||||
this.value = Number(ev.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _tempColorCursor(ev) {
|
|
||||||
this.liveValue = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _tempColorChanged(ev) {
|
|
||||||
this.value = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="value">${this.liveValue ?? this.value} K</p>
|
|
||||||
<ha-temp-color-picker
|
|
||||||
.min=${this.min}
|
|
||||||
.max=${this.max}
|
|
||||||
.value=${this.value}
|
|
||||||
@value-changed=${this._tempColorChanged}
|
|
||||||
@cursor-moved=${this._tempColorCursor}
|
|
||||||
></ha-temp-color-picker>
|
|
||||||
<p>Min temp : ${this.min} K</p>
|
|
||||||
<ha-slider
|
|
||||||
step="1"
|
|
||||||
pin
|
|
||||||
min="2000"
|
|
||||||
max="10000"
|
|
||||||
.value=${this.min}
|
|
||||||
@change=${this._minChanged}
|
|
||||||
>
|
|
||||||
</ha-slider>
|
|
||||||
<p>Max temp : ${this.max} K</p>
|
|
||||||
<ha-slider
|
|
||||||
step="1"
|
|
||||||
pin
|
|
||||||
min="2000"
|
|
||||||
max="10000"
|
|
||||||
.value=${this.max}
|
|
||||||
@change=${this._maxChanged}
|
|
||||||
>
|
|
||||||
</ha-slider>
|
|
||||||
<p>Value : ${this.value} K</p>
|
|
||||||
<ha-slider
|
|
||||||
step="1"
|
|
||||||
pin
|
|
||||||
min=${this.min}
|
|
||||||
max=${this.max}
|
|
||||||
.value=${this.value}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
>
|
|
||||||
</ha-slider>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
|
||||||
return css`
|
|
||||||
ha-card {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 24px auto;
|
|
||||||
}
|
|
||||||
.card-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
ha-temp-color-picker {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-components-ha-temp-color-picker": DemoHaTempColorPicker;
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,12 @@ import { hsv2rgb, rgb2hex } from "../common/color/convert-color";
|
|||||||
import { rgbw2rgb, rgbww2rgb } from "../common/color/convert-light-color";
|
import { rgbw2rgb, rgbww2rgb } from "../common/color/convert-light-color";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"cursor-moved": { value?: any };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function xy2polar(x: number, y: number) {
|
function xy2polar(x: number, y: number) {
|
||||||
const r = Math.sqrt(x * x + y * y);
|
const r = Math.sqrt(x * x + y * y);
|
||||||
const phi = Math.atan2(y, x);
|
const phi = Math.atan2(y, x);
|
||||||
|
@ -1,440 +0,0 @@
|
|||||||
import { DIRECTION_ALL, Manager, Pan, Tap } from "@egjs/hammerjs";
|
|
||||||
import { LitElement, PropertyValues, css, html, svg } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { styleMap } from "lit/directives/style-map";
|
|
||||||
import { rgb2hex } from "../common/color/convert-color";
|
|
||||||
import {
|
|
||||||
DEFAULT_MAX_KELVIN,
|
|
||||||
DEFAULT_MIN_KELVIN,
|
|
||||||
temperature2rgb,
|
|
||||||
} from "../common/color/convert-light-color";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
|
||||||
|
|
||||||
const SAFE_ZONE_FACTOR = 0.9;
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"cursor-moved": { value?: any };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const A11Y_KEY_CODES = new Set([
|
|
||||||
"ArrowRight",
|
|
||||||
"ArrowUp",
|
|
||||||
"ArrowLeft",
|
|
||||||
"ArrowDown",
|
|
||||||
"PageUp",
|
|
||||||
"PageDown",
|
|
||||||
"Home",
|
|
||||||
"End",
|
|
||||||
]);
|
|
||||||
|
|
||||||
function xy2polar(x: number, y: number) {
|
|
||||||
const r = Math.sqrt(x * x + y * y);
|
|
||||||
const phi = Math.atan2(y, x);
|
|
||||||
return [r, phi];
|
|
||||||
}
|
|
||||||
|
|
||||||
function polar2xy(r: number, phi: number) {
|
|
||||||
const x = Math.cos(phi) * r;
|
|
||||||
const y = Math.sin(phi) * r;
|
|
||||||
return [x, y];
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawColorWheel(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
minTemp: number,
|
|
||||||
maxTemp: number
|
|
||||||
) {
|
|
||||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
||||||
const radius = ctx.canvas.width / 2;
|
|
||||||
|
|
||||||
const min = Math.max(minTemp, 2000);
|
|
||||||
const max = Math.min(maxTemp, 40000);
|
|
||||||
|
|
||||||
for (let y = -radius; y < radius; y += 1) {
|
|
||||||
const x = radius * Math.sqrt(1 - (y / radius) ** 2);
|
|
||||||
|
|
||||||
const fraction = (y / (radius * SAFE_ZONE_FACTOR) + 1) / 2;
|
|
||||||
|
|
||||||
const temperature = Math.max(
|
|
||||||
Math.min(min + fraction * (max - min), max),
|
|
||||||
min
|
|
||||||
);
|
|
||||||
|
|
||||||
const color = rgb2hex(temperature2rgb(temperature));
|
|
||||||
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fillRect(radius - x, radius + y - 0.5, 2 * x, 2);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ha-temp-color-picker")
|
|
||||||
class HaTempColorPicker extends LitElement {
|
|
||||||
@property({ type: Boolean, reflect: true })
|
|
||||||
public disabled = false;
|
|
||||||
|
|
||||||
@property({ type: Number, attribute: false })
|
|
||||||
public renderSize?: number;
|
|
||||||
|
|
||||||
@property({ type: Number })
|
|
||||||
public value?: number;
|
|
||||||
|
|
||||||
@property({ type: Number })
|
|
||||||
public min = DEFAULT_MIN_KELVIN;
|
|
||||||
|
|
||||||
@property({ type: Number })
|
|
||||||
public max = DEFAULT_MAX_KELVIN;
|
|
||||||
|
|
||||||
@query("#canvas") private _canvas!: HTMLCanvasElement;
|
|
||||||
|
|
||||||
private _mc?: HammerManager;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
private _pressed?: string;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
private _cursorPosition?: [number, number];
|
|
||||||
|
|
||||||
@state()
|
|
||||||
private _localValue?: number;
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues): void {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._setupListeners();
|
|
||||||
this._generateColorWheel();
|
|
||||||
this.setAttribute("role", "slider");
|
|
||||||
this.setAttribute("aria-orientation", "vertical");
|
|
||||||
if (!this.hasAttribute("tabindex")) {
|
|
||||||
this.setAttribute("tabindex", "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _generateColorWheel() {
|
|
||||||
const ctx = this._canvas.getContext("2d")!;
|
|
||||||
drawColorWheel(ctx, this.min, this.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
this._setupListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback(): void {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this._destroyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
|
||||||
super.updated(changedProps);
|
|
||||||
if (changedProps.has("_localValue")) {
|
|
||||||
this.setAttribute("aria-valuenow", this._localValue?.toString() ?? "");
|
|
||||||
}
|
|
||||||
if (changedProps.has("min") || changedProps.has("max")) {
|
|
||||||
this._generateColorWheel();
|
|
||||||
this._resetPosition();
|
|
||||||
}
|
|
||||||
if (changedProps.has("min")) {
|
|
||||||
this.setAttribute("aria-valuemin", this.min.toString());
|
|
||||||
}
|
|
||||||
if (changedProps.has("max")) {
|
|
||||||
this.setAttribute("aria-valuemax", this.max.toString());
|
|
||||||
}
|
|
||||||
if (changedProps.has("value")) {
|
|
||||||
if (this._localValue !== this.value) {
|
|
||||||
this._resetPosition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setupListeners() {
|
|
||||||
if (this._canvas && !this._mc) {
|
|
||||||
this._mc = new Manager(this._canvas);
|
|
||||||
this._mc.add(
|
|
||||||
new Pan({
|
|
||||||
direction: DIRECTION_ALL,
|
|
||||||
enable: true,
|
|
||||||
threshold: 0,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this._mc.add(new Tap({ event: "singletap" }));
|
|
||||||
|
|
||||||
let savedPosition;
|
|
||||||
this._mc.on("panstart", (e) => {
|
|
||||||
if (this.disabled) return;
|
|
||||||
this._pressed = e.pointerType;
|
|
||||||
savedPosition = this._cursorPosition;
|
|
||||||
});
|
|
||||||
this._mc.on("pancancel", () => {
|
|
||||||
if (this.disabled) return;
|
|
||||||
this._pressed = undefined;
|
|
||||||
this._cursorPosition = savedPosition;
|
|
||||||
});
|
|
||||||
this._mc.on("panmove", (e) => {
|
|
||||||
if (this.disabled) return;
|
|
||||||
this._cursorPosition = this._getPositionFromEvent(e);
|
|
||||||
this._localValue = this._getValueFromCoord(...this._cursorPosition);
|
|
||||||
fireEvent(this, "cursor-moved", { value: this._localValue });
|
|
||||||
});
|
|
||||||
this._mc.on("panend", (e) => {
|
|
||||||
if (this.disabled) return;
|
|
||||||
this._pressed = undefined;
|
|
||||||
this._cursorPosition = this._getPositionFromEvent(e);
|
|
||||||
this._localValue = this._getValueFromCoord(...this._cursorPosition);
|
|
||||||
fireEvent(this, "cursor-moved", { value: undefined });
|
|
||||||
fireEvent(this, "value-changed", { value: this._localValue });
|
|
||||||
});
|
|
||||||
|
|
||||||
this._mc.on("singletap", (e) => {
|
|
||||||
if (this.disabled) return;
|
|
||||||
this._cursorPosition = this._getPositionFromEvent(e);
|
|
||||||
this._localValue = this._getValueFromCoord(...this._cursorPosition);
|
|
||||||
fireEvent(this, "value-changed", { value: this._localValue });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addEventListener("keydown", this._handleKeyDown);
|
|
||||||
this.addEventListener("keyup", this._handleKeyUp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _resetPosition() {
|
|
||||||
if (this.value === undefined) {
|
|
||||||
this._cursorPosition = undefined;
|
|
||||||
this._localValue = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const [, y] = this._getCoordsFromValue(this.value);
|
|
||||||
const currentX = this._cursorPosition?.[0] ?? 0;
|
|
||||||
const x =
|
|
||||||
Math.sign(currentX) * Math.min(Math.sqrt(1 - y ** 2), Math.abs(currentX));
|
|
||||||
this._cursorPosition = [x, y];
|
|
||||||
this._localValue = this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getCoordsFromValue = (temperature: number): [number, number] => {
|
|
||||||
if (this.value === this.min) {
|
|
||||||
return [0, -1];
|
|
||||||
}
|
|
||||||
if (this.value === this.max) {
|
|
||||||
return [0, 1];
|
|
||||||
}
|
|
||||||
const fraction = (temperature - this.min) / (this.max - this.min);
|
|
||||||
const y = (2 * fraction - 1) * SAFE_ZONE_FACTOR;
|
|
||||||
return [0, y];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _getValueFromCoord = (_x: number, y: number): number => {
|
|
||||||
const fraction = (y / SAFE_ZONE_FACTOR + 1) / 2;
|
|
||||||
const temperature = Math.max(
|
|
||||||
Math.min(this.min + fraction * (this.max - this.min), this.max),
|
|
||||||
this.min
|
|
||||||
);
|
|
||||||
return Math.round(temperature);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _getPositionFromEvent = (e: HammerInput): [number, number] => {
|
|
||||||
const x = e.center.x;
|
|
||||||
const y = e.center.y;
|
|
||||||
const boundingRect = e.target.getBoundingClientRect();
|
|
||||||
const offsetX = boundingRect.left;
|
|
||||||
const offsetY = boundingRect.top;
|
|
||||||
const maxX = e.target.clientWidth;
|
|
||||||
const maxY = e.target.clientHeight;
|
|
||||||
|
|
||||||
const _x = (2 * (x - offsetX)) / maxX - 1;
|
|
||||||
const _y = (2 * (y - offsetY)) / maxY - 1;
|
|
||||||
|
|
||||||
const [r, phi] = xy2polar(_x, _y);
|
|
||||||
const [__x, __y] = polar2xy(Math.min(1, r), phi);
|
|
||||||
return [__x, __y];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _destroyListeners() {
|
|
||||||
if (this._mc) {
|
|
||||||
this._mc.destroy();
|
|
||||||
this._mc = undefined;
|
|
||||||
}
|
|
||||||
this.removeEventListener("keydown", this._handleKeyDown);
|
|
||||||
this.removeEventListener("keyup", this._handleKeyDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleKeyDown(e: KeyboardEvent) {
|
|
||||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const step = 1;
|
|
||||||
const tenPercentStep = Math.max(step, (this.max - this.min) / 10);
|
|
||||||
const currentValue =
|
|
||||||
this._localValue ?? Math.round((this.max + this.min) / 2);
|
|
||||||
switch (e.code) {
|
|
||||||
case "ArrowRight":
|
|
||||||
case "ArrowUp":
|
|
||||||
this._localValue = Math.round(Math.min(currentValue + step, this.max));
|
|
||||||
break;
|
|
||||||
case "ArrowLeft":
|
|
||||||
case "ArrowDown":
|
|
||||||
this._localValue = Math.round(Math.max(currentValue - step, this.min));
|
|
||||||
break;
|
|
||||||
case "PageUp":
|
|
||||||
this._localValue = Math.round(
|
|
||||||
Math.min(currentValue + tenPercentStep, this.max)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "PageDown":
|
|
||||||
this._localValue = Math.round(
|
|
||||||
Math.max(currentValue - tenPercentStep, this.min)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "Home":
|
|
||||||
this._localValue = this.min;
|
|
||||||
break;
|
|
||||||
case "End":
|
|
||||||
this._localValue = this.max;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (this._localValue != null) {
|
|
||||||
const [_, y] = this._getCoordsFromValue(this._localValue);
|
|
||||||
const currentX = this._cursorPosition?.[0] ?? 0;
|
|
||||||
const x =
|
|
||||||
Math.sign(currentX) *
|
|
||||||
Math.min(Math.sqrt(1 - y ** 2), Math.abs(currentX));
|
|
||||||
this._cursorPosition = [x, y];
|
|
||||||
fireEvent(this, "cursor-moved", { value: this._localValue });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleKeyUp(e: KeyboardEvent) {
|
|
||||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
|
||||||
e.preventDefault();
|
|
||||||
this.value = this._localValue;
|
|
||||||
fireEvent(this, "value-changed", { value: this._localValue });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const size = this.renderSize || 400;
|
|
||||||
const canvasSize = size * window.devicePixelRatio;
|
|
||||||
|
|
||||||
const rgb = temperature2rgb(
|
|
||||||
this._localValue ?? Math.round((this.max + this.min) / 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
const [x, y] = this._cursorPosition ?? [0, 0];
|
|
||||||
|
|
||||||
const cx = ((x + 1) * size) / 2;
|
|
||||||
const cy = ((y + 1) * size) / 2;
|
|
||||||
|
|
||||||
const markerPosition = `${cx}px, ${cy}px`;
|
|
||||||
const markerScale = this._pressed
|
|
||||||
? this._pressed === "touch"
|
|
||||||
? "2.5"
|
|
||||||
: "1.5"
|
|
||||||
: "1";
|
|
||||||
const markerOffset =
|
|
||||||
this._pressed === "touch" ? `0px, -${size / 16}px` : "0px, 0px";
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="container ${classMap({ pressed: Boolean(this._pressed) })}">
|
|
||||||
<canvas id="canvas" .width=${canvasSize} .height=${canvasSize}></canvas>
|
|
||||||
<svg
|
|
||||||
id="interaction"
|
|
||||||
viewBox="0 0 ${size} ${size}"
|
|
||||||
overflow="visible"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<defs>${this.renderSVGFilter()}</defs>
|
|
||||||
<g
|
|
||||||
style=${styleMap({
|
|
||||||
fill: rgb2hex(rgb),
|
|
||||||
transform: `translate(${markerPosition})`,
|
|
||||||
})}
|
|
||||||
class="cursor"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
cx="0"
|
|
||||||
cy="0"
|
|
||||||
r="16"
|
|
||||||
style=${styleMap({
|
|
||||||
fill: rgb2hex(rgb),
|
|
||||||
transform: `translate(${markerOffset}) scale(${markerScale})`,
|
|
||||||
visibility: this._cursorPosition ? undefined : "hidden",
|
|
||||||
})}
|
|
||||||
></circle>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSVGFilter() {
|
|
||||||
return svg`
|
|
||||||
<filter
|
|
||||||
id="marker-shadow"
|
|
||||||
x="-50%"
|
|
||||||
y="-50%"
|
|
||||||
width="200%"
|
|
||||||
height="200%"
|
|
||||||
filterUnits="objectBoundingBox"
|
|
||||||
>
|
|
||||||
<feDropShadow dx="0" dy="1" stdDeviation="2" flood-opacity="0.3" flood-color="rgba(0, 0, 0, 1)"/>
|
|
||||||
<feDropShadow dx="0" dy="1" stdDeviation="3" flood-opacity="0.15" flood-color="rgba(0, 0, 0, 1)"/>
|
|
||||||
</filter>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
|
||||||
return css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
canvas {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: box-shadow 180ms ease-in-out;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
:host(:focus-visible) canvas {
|
|
||||||
box-shadow: 0 0 0 2px rgb(255, 160, 0);
|
|
||||||
}
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
circle {
|
|
||||||
fill: black;
|
|
||||||
stroke: white;
|
|
||||||
stroke-width: 2;
|
|
||||||
filter: url(#marker-shadow);
|
|
||||||
}
|
|
||||||
.container:not(.pressed) circle {
|
|
||||||
transition:
|
|
||||||
transform 100ms ease-in-out,
|
|
||||||
fill 100ms ease-in-out;
|
|
||||||
}
|
|
||||||
.container:not(.pressed) .cursor {
|
|
||||||
transition: transform 200ms ease-in-out;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-temp-color-picker": HaTempColorPicker;
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,7 +26,6 @@ import "../../../../components/ha-hs-color-picker";
|
|||||||
import "../../../../components/ha-icon";
|
import "../../../../components/ha-icon";
|
||||||
import "../../../../components/ha-icon-button-prev";
|
import "../../../../components/ha-icon-button-prev";
|
||||||
import "../../../../components/ha-labeled-slider";
|
import "../../../../components/ha-labeled-slider";
|
||||||
import "../../../../components/ha-temp-color-picker";
|
|
||||||
import {
|
import {
|
||||||
getLightCurrentModeRgbColor,
|
getLightCurrentModeRgbColor,
|
||||||
LightColor,
|
LightColor,
|
||||||
|
@ -1,25 +1,31 @@
|
|||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
nothing,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
} 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 memoizeOne from "memoize-one";
|
||||||
|
import { rgb2hex } from "../../../../common/color/convert-color";
|
||||||
|
import {
|
||||||
|
DEFAULT_MAX_KELVIN,
|
||||||
|
DEFAULT_MIN_KELVIN,
|
||||||
|
temperature2rgb,
|
||||||
|
} from "../../../../common/color/convert-light-color";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||||
import { throttle } from "../../../../common/util/throttle";
|
import { throttle } from "../../../../common/util/throttle";
|
||||||
import "../../../../components/ha-temp-color-picker";
|
import "../../../../components/ha-control-slider";
|
||||||
|
import { UNAVAILABLE } from "../../../../data/entity";
|
||||||
import {
|
import {
|
||||||
LightColor,
|
LightColor,
|
||||||
LightColorMode,
|
LightColorMode,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
} from "../../../../data/light";
|
} from "../../../../data/light";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import {
|
|
||||||
DEFAULT_MAX_KELVIN,
|
|
||||||
DEFAULT_MIN_KELVIN,
|
|
||||||
} from "../../../../common/color/convert-light-color";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -28,6 +34,26 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const generateColorTemperatureGradient = (min: number, max: number) => {
|
||||||
|
const count = 10;
|
||||||
|
|
||||||
|
const gradient: [number, string][] = [];
|
||||||
|
|
||||||
|
const step = (max - min) / count;
|
||||||
|
const percentageStep = 1 / count;
|
||||||
|
|
||||||
|
for (let i = 0; i < count + 1; i++) {
|
||||||
|
const value = min + step * i;
|
||||||
|
|
||||||
|
const hex = rgb2hex(temperature2rgb(value));
|
||||||
|
gradient.push([percentageStep * i, hex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gradient
|
||||||
|
.map(([stop, color]) => `${color} ${(stop as number) * 100}%`)
|
||||||
|
.join(", ");
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("light-color-temp-picker")
|
@customElement("light-color-temp-picker")
|
||||||
class LightColorTempPicker extends LitElement {
|
class LightColorTempPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -46,18 +72,36 @@ class LightColorTempPicker extends LitElement {
|
|||||||
const maxKelvin =
|
const maxKelvin =
|
||||||
this.stateObj.attributes.max_color_temp_kelvin ?? DEFAULT_MAX_KELVIN;
|
this.stateObj.attributes.max_color_temp_kelvin ?? DEFAULT_MAX_KELVIN;
|
||||||
|
|
||||||
|
const gradient = this._generateTemperatureGradient(minKelvin!, maxKelvin);
|
||||||
|
const color = stateColorCss(this.stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-temp-color-picker
|
<ha-control-slider
|
||||||
@value-changed=${this._ctColorChanged}
|
inverted
|
||||||
@cursor-moved=${this._ctColorCursorMoved}
|
vertical
|
||||||
|
.value=${this._ctPickerValue}
|
||||||
.min=${minKelvin}
|
.min=${minKelvin}
|
||||||
.max=${maxKelvin}
|
.max=${maxKelvin}
|
||||||
.value=${this._ctPickerValue}
|
mode="cursor"
|
||||||
|
@value-changed=${this._ctColorChanged}
|
||||||
|
@slider-moved=${this._ctColorCursorMoved}
|
||||||
|
.ariaLabel=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.light.color_temp"
|
||||||
|
)}
|
||||||
|
style=${styleMap({
|
||||||
|
"--control-slider-color": color,
|
||||||
|
"--gradient": gradient,
|
||||||
|
})}
|
||||||
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
>
|
>
|
||||||
</ha-temp-color-picker>
|
</ha-control-slider>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _generateTemperatureGradient = memoizeOne(
|
||||||
|
(min: number, max: number) => generateColorTemperatureGradient(min, max)
|
||||||
|
);
|
||||||
|
|
||||||
public _updateSliderValues() {
|
public _updateSliderValues() {
|
||||||
const stateObj = this.stateObj;
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
@ -138,10 +182,18 @@ class LightColorTempPicker extends LitElement {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-temp-color-picker {
|
ha-control-slider {
|
||||||
height: 45vh;
|
height: 45vh;
|
||||||
max-height: 320px;
|
max-height: 320px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
|
--control-slider-thickness: 100px;
|
||||||
|
--control-slider-border-radius: 24px;
|
||||||
|
--control-slider-color: var(--primary-color);
|
||||||
|
--control-slider-background: -webkit-linear-gradient(
|
||||||
|
top,
|
||||||
|
var(--gradient)
|
||||||
|
);
|
||||||
|
--control-slider-background-opacity: 1;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -3,17 +3,16 @@ import { css, html, LitElement, nothing } 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 memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { rgb2hex } from "../../../common/color/convert-color";
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_MAX_KELVIN,
|
DEFAULT_MAX_KELVIN,
|
||||||
DEFAULT_MIN_KELVIN,
|
DEFAULT_MIN_KELVIN,
|
||||||
temperature2rgb,
|
|
||||||
} from "../../../common/color/convert-light-color";
|
} from "../../../common/color/convert-light-color";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { stateActive } from "../../../common/entity/state_active";
|
import { stateActive } from "../../../common/entity/state_active";
|
||||||
import "../../../components/ha-control-slider";
|
import "../../../components/ha-control-slider";
|
||||||
import { UNAVAILABLE } from "../../../data/entity";
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
import { LightColorMode, lightSupportsColorMode } from "../../../data/light";
|
import { LightColorMode, lightSupportsColorMode } from "../../../data/light";
|
||||||
|
import { generateColorTemperatureGradient } from "../../../dialogs/more-info/components/lights/light-color-temp-picker";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { LovelaceTileFeature } from "../types";
|
import { LovelaceTileFeature } from "../types";
|
||||||
import { LightColorTempTileFeatureConfig } from "./types";
|
import { LightColorTempTileFeatureConfig } from "./types";
|
||||||
@ -70,7 +69,7 @@ class HuiLightColorTempTileFeature
|
|||||||
const maxKelvin =
|
const maxKelvin =
|
||||||
this.stateObj.attributes.max_color_temp_kelvin ?? DEFAULT_MAX_KELVIN;
|
this.stateObj.attributes.max_color_temp_kelvin ?? DEFAULT_MAX_KELVIN;
|
||||||
|
|
||||||
const gradient = this.generateTemperatureGradient(minKelvin!, maxKelvin);
|
const gradient = this._generateTemperatureGradient(minKelvin!, maxKelvin);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -91,26 +90,8 @@ class HuiLightColorTempTileFeature
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateTemperatureGradient = memoizeOne(
|
private _generateTemperatureGradient = memoizeOne(
|
||||||
(min: number, max: number) => {
|
(min: number, max: number) => generateColorTemperatureGradient(min, max)
|
||||||
const count = 10;
|
|
||||||
|
|
||||||
const gradient: [number, string][] = [];
|
|
||||||
|
|
||||||
const step = (max - min) / count;
|
|
||||||
const percentageStep = 1 / count;
|
|
||||||
|
|
||||||
for (let i = 0; i < count + 1; i++) {
|
|
||||||
const value = min + step * i;
|
|
||||||
|
|
||||||
const hex = rgb2hex(temperature2rgb(value));
|
|
||||||
gradient.push([percentageStep * i, hex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return gradient
|
|
||||||
.map(([stop, color]) => `${color} ${(stop as number) * 100}%`)
|
|
||||||
.join(", ");
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
|
Loading…
Reference in New Issue
Block a user