diff --git a/gallery/src/pages/components/ha-temp-color-picker.markdown b/gallery/src/pages/components/ha-temp-color-picker.markdown deleted file mode 100644 index 3221e55ee8..0000000000 --- a/gallery/src/pages/components/ha-temp-color-picker.markdown +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Temp Color Picker ---- diff --git a/gallery/src/pages/components/ha-temp-color-picker.ts b/gallery/src/pages/components/ha-temp-color-picker.ts deleted file mode 100644 index d924e745bf..0000000000 --- a/gallery/src/pages/components/ha-temp-color-picker.ts +++ /dev/null @@ -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` - -
-

${this.liveValue ?? this.value} K

- -

Min temp : ${this.min} K

- - -

Max temp : ${this.max} K

- - -

Value : ${this.value} K

- - -
-
- `; - } - - 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; - } -} diff --git a/src/components/ha-hs-color-picker.ts b/src/components/ha-hs-color-picker.ts index cef11cfc38..f492be2d7c 100644 --- a/src/components/ha-hs-color-picker.ts +++ b/src/components/ha-hs-color-picker.ts @@ -7,6 +7,12 @@ import { hsv2rgb, rgb2hex } from "../common/color/convert-color"; import { rgbw2rgb, rgbww2rgb } from "../common/color/convert-light-color"; import { fireEvent } from "../common/dom/fire_event"; +declare global { + interface HASSDomEvents { + "cursor-moved": { value?: any }; + } +} + function xy2polar(x: number, y: number) { const r = Math.sqrt(x * x + y * y); const phi = Math.atan2(y, x); diff --git a/src/components/ha-temp-color-picker.ts b/src/components/ha-temp-color-picker.ts deleted file mode 100644 index ce471c207f..0000000000 --- a/src/components/ha-temp-color-picker.ts +++ /dev/null @@ -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` -
- - -
- `; - } - - renderSVGFilter() { - return svg` - - - - - `; - } - - 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; - } -} diff --git a/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts b/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts index 90b18f4223..0a66aabcf6 100644 --- a/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts +++ b/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts @@ -26,7 +26,6 @@ import "../../../../components/ha-hs-color-picker"; import "../../../../components/ha-icon"; import "../../../../components/ha-icon-button-prev"; import "../../../../components/ha-labeled-slider"; -import "../../../../components/ha-temp-color-picker"; import { getLightCurrentModeRgbColor, LightColor, diff --git a/src/dialogs/more-info/components/lights/light-color-temp-picker.ts b/src/dialogs/more-info/components/lights/light-color-temp-picker.ts index cd69b7ba7d..0331d24b31 100644 --- a/src/dialogs/more-info/components/lights/light-color-temp-picker.ts +++ b/src/dialogs/more-info/components/lights/light-color-temp-picker.ts @@ -1,25 +1,31 @@ import { - css, CSSResultGroup, - html, LitElement, - nothing, PropertyValues, + css, + html, + nothing, } from "lit"; 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 { stateColorCss } from "../../../../common/entity/state_color"; import { throttle } from "../../../../common/util/throttle"; -import "../../../../components/ha-temp-color-picker"; +import "../../../../components/ha-control-slider"; +import { UNAVAILABLE } from "../../../../data/entity"; import { LightColor, LightColorMode, LightEntity, } from "../../../../data/light"; import { HomeAssistant } from "../../../../types"; -import { - DEFAULT_MAX_KELVIN, - DEFAULT_MIN_KELVIN, -} from "../../../../common/color/convert-light-color"; declare global { 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") class LightColorTempPicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -46,18 +72,36 @@ class LightColorTempPicker extends LitElement { const maxKelvin = this.stateObj.attributes.max_color_temp_kelvin ?? DEFAULT_MAX_KELVIN; + const gradient = this._generateTemperatureGradient(minKelvin!, maxKelvin); + const color = stateColorCss(this.stateObj); + return html` - - + `; } + private _generateTemperatureGradient = memoizeOne( + (min: number, max: number) => generateColorTemperatureGradient(min, max) + ); + public _updateSliderValues() { const stateObj = this.stateObj; @@ -138,10 +182,18 @@ class LightColorTempPicker extends LitElement { flex-direction: column; } - ha-temp-color-picker { + ha-control-slider { height: 45vh; max-height: 320px; 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; } `, ]; diff --git a/src/panels/lovelace/tile-features/hui-light-color-temp-tile-feature.ts b/src/panels/lovelace/tile-features/hui-light-color-temp-tile-feature.ts index 43f16a4655..2c35e33eae 100644 --- a/src/panels/lovelace/tile-features/hui-light-color-temp-tile-feature.ts +++ b/src/panels/lovelace/tile-features/hui-light-color-temp-tile-feature.ts @@ -3,17 +3,16 @@ import { css, html, LitElement, nothing } from "lit"; 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 { computeDomain } from "../../../common/entity/compute_domain"; import { stateActive } from "../../../common/entity/state_active"; import "../../../components/ha-control-slider"; import { UNAVAILABLE } from "../../../data/entity"; import { LightColorMode, lightSupportsColorMode } from "../../../data/light"; +import { generateColorTemperatureGradient } from "../../../dialogs/more-info/components/lights/light-color-temp-picker"; import { HomeAssistant } from "../../../types"; import { LovelaceTileFeature } from "../types"; import { LightColorTempTileFeatureConfig } from "./types"; @@ -70,7 +69,7 @@ class HuiLightColorTempTileFeature const maxKelvin = this.stateObj.attributes.max_color_temp_kelvin ?? DEFAULT_MAX_KELVIN; - const gradient = this.generateTemperatureGradient(minKelvin!, maxKelvin); + const gradient = this._generateTemperatureGradient(minKelvin!, maxKelvin); return html`
@@ -91,26 +90,8 @@ class HuiLightColorTempTileFeature `; } - private generateTemperatureGradient = memoizeOne( - (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(", "); - } + private _generateTemperatureGradient = memoizeOne( + (min: number, max: number) => generateColorTemperatureGradient(min, max) ); private _valueChanged(ev: CustomEvent) {