mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-19 10:00:02 +00:00
Compare commits
5 Commits
section-vi
...
state_colo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bf78effe29 | ||
![]() |
6a51308ee3 | ||
![]() |
acab2d5ead | ||
![]() |
046fc00f73 | ||
![]() |
05775c411b |
@@ -135,7 +135,7 @@
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "3.0.0",
|
||||
"ua-parser-js": "2.0.4",
|
||||
"ua-parser-js": "2.0.5",
|
||||
"vue": "2.7.16",
|
||||
"vue2-daterange-picker": "0.6.8",
|
||||
"weekstart": "2.0.0",
|
||||
|
@@ -1,15 +1,13 @@
|
||||
export const batteryStateColorProperty = (
|
||||
state: string
|
||||
): string | undefined => {
|
||||
export const batteryStateColor = (state: string): string | undefined => {
|
||||
const value = Number(state);
|
||||
if (isNaN(value)) {
|
||||
return undefined;
|
||||
}
|
||||
if (value >= 70) {
|
||||
return "--state-sensor-battery-high-color";
|
||||
return "var(--state-sensor-battery-high-color)";
|
||||
}
|
||||
if (value >= 30) {
|
||||
return "--state-sensor-battery-medium-color";
|
||||
return "var(--state-sensor-battery-medium-color)";
|
||||
}
|
||||
return "--state-sensor-battery-low-color";
|
||||
return "var(--state-sensor-battery-low-color)";
|
||||
};
|
||||
|
@@ -5,12 +5,15 @@ import { computeDomain } from "./compute_domain";
|
||||
export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
return domainStateActive(domain, compareState);
|
||||
}
|
||||
|
||||
export function domainStateActive(domain: string, state: string) {
|
||||
if (["button", "event", "input_button", "scene"].includes(domain)) {
|
||||
return compareState !== UNAVAILABLE;
|
||||
return state !== UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (isUnavailableState(compareState)) {
|
||||
if (isUnavailableState(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,40 +21,40 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
// such as "alert" where "off" is still a somewhat active state and
|
||||
// therefore gets a custom color and "idle" is instead the state that
|
||||
// matches what most other domains consider inactive.
|
||||
if (compareState === OFF && domain !== "alert") {
|
||||
if (state === OFF && domain !== "alert") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Custom cases
|
||||
switch (domain) {
|
||||
case "alarm_control_panel":
|
||||
return compareState !== "disarmed";
|
||||
return state !== "disarmed";
|
||||
case "alert":
|
||||
// "on" and "off" are active, as "off" just means alert was acknowledged but is still active
|
||||
return compareState !== "idle";
|
||||
return state !== "idle";
|
||||
case "cover":
|
||||
return compareState !== "closed";
|
||||
return state !== "closed";
|
||||
case "device_tracker":
|
||||
case "person":
|
||||
return compareState !== "not_home";
|
||||
return state !== "not_home";
|
||||
case "lawn_mower":
|
||||
return ["mowing", "error"].includes(compareState);
|
||||
return ["mowing", "error"].includes(state);
|
||||
case "lock":
|
||||
return compareState !== "locked";
|
||||
return state !== "locked";
|
||||
case "media_player":
|
||||
return compareState !== "standby";
|
||||
return state !== "standby";
|
||||
case "vacuum":
|
||||
return !["idle", "docked", "paused"].includes(compareState);
|
||||
return !["idle", "docked", "paused"].includes(state);
|
||||
case "valve":
|
||||
return compareState !== "closed";
|
||||
return state !== "closed";
|
||||
case "plant":
|
||||
return compareState === "problem";
|
||||
return state === "problem";
|
||||
case "group":
|
||||
return ["on", "home", "open", "locked", "problem"].includes(compareState);
|
||||
return ["on", "home", "open", "locked", "problem"].includes(state);
|
||||
case "timer":
|
||||
return compareState === "active";
|
||||
return state === "active";
|
||||
case "camera":
|
||||
return compareState === "streaming";
|
||||
return state === "streaming";
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -1,13 +1,11 @@
|
||||
/** Return a color representing a state. */
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import type { GroupEntity } from "../../data/group";
|
||||
import { computeGroupDomain } from "../../data/group";
|
||||
import { computeCssVariable } from "../../resources/css-variables";
|
||||
import { slugify } from "../string/slugify";
|
||||
import { batteryStateColorProperty } from "./color/battery_color";
|
||||
import { batteryStateColor } from "./color/battery_color";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { stateActive } from "./state_active";
|
||||
import { domainStateActive } from "./state_active";
|
||||
|
||||
const STATE_COLORED_DOMAIN = new Set([
|
||||
"alarm_control_panel",
|
||||
@@ -42,73 +40,20 @@ const STATE_COLORED_DOMAIN = new Set([
|
||||
"water_heater",
|
||||
]);
|
||||
|
||||
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
if (compareState === UNAVAILABLE) {
|
||||
return `var(--state-unavailable-color)`;
|
||||
}
|
||||
|
||||
const properties = stateColorProperties(stateObj, state);
|
||||
if (properties) {
|
||||
return computeCssVariable(properties);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const domainStateColorProperties = (
|
||||
domain: string,
|
||||
export const stateColor = (
|
||||
element: HTMLElement | CSSStyleDeclaration,
|
||||
stateObj: HassEntity,
|
||||
state?: string
|
||||
): string[] => {
|
||||
const compareState = state !== undefined ? state : stateObj.state;
|
||||
const active = stateActive(stateObj, state);
|
||||
|
||||
return domainColorProperties(
|
||||
domain,
|
||||
stateObj.attributes.device_class,
|
||||
compareState,
|
||||
active
|
||||
);
|
||||
};
|
||||
|
||||
export const domainColorProperties = (
|
||||
domain: string,
|
||||
deviceClass: string | undefined,
|
||||
state: string,
|
||||
active: boolean
|
||||
) => {
|
||||
const properties: string[] = [];
|
||||
|
||||
const stateKey = slugify(state, "_");
|
||||
const activeKey = active ? "active" : "inactive";
|
||||
|
||||
if (deviceClass) {
|
||||
properties.push(`--state-${domain}-${deviceClass}-${stateKey}-color`);
|
||||
}
|
||||
|
||||
properties.push(
|
||||
`--state-${domain}-${stateKey}-color`,
|
||||
`--state-${domain}-${activeKey}-color`,
|
||||
`--state-${activeKey}-color`
|
||||
);
|
||||
|
||||
return properties;
|
||||
};
|
||||
|
||||
export const stateColorProperties = (
|
||||
stateObj: HassEntity,
|
||||
state?: string
|
||||
): string[] | undefined => {
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
const dc = stateObj.attributes.device_class;
|
||||
const compareState = state !== undefined ? state : stateObj.state;
|
||||
|
||||
// Special rules for battery coloring
|
||||
if (domain === "sensor" && dc === "battery") {
|
||||
const property = batteryStateColorProperty(compareState);
|
||||
const property = batteryStateColor(compareState);
|
||||
if (property) {
|
||||
return [property];
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,17 +61,52 @@ export const stateColorProperties = (
|
||||
if (domain === "group") {
|
||||
const groupDomain = computeGroupDomain(stateObj as GroupEntity);
|
||||
if (groupDomain && STATE_COLORED_DOMAIN.has(groupDomain)) {
|
||||
return domainStateColorProperties(groupDomain, stateObj, state);
|
||||
return domainStateColor(element, groupDomain, undefined, compareState);
|
||||
}
|
||||
}
|
||||
|
||||
if (STATE_COLORED_DOMAIN.has(domain)) {
|
||||
return domainStateColorProperties(domain, stateObj, state);
|
||||
return domainStateColor(element, domain, dc, compareState);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const domainStateColor = (
|
||||
element: HTMLElement | CSSStyleDeclaration,
|
||||
domain: string,
|
||||
deviceClass: string | undefined,
|
||||
state: string
|
||||
) => {
|
||||
const style =
|
||||
element instanceof CSSStyleDeclaration
|
||||
? element
|
||||
: getComputedStyle(element);
|
||||
|
||||
const stateKey = slugify(state, "_");
|
||||
|
||||
const active = domainStateActive(domain, state);
|
||||
const activeKey = active ? "active" : "inactive";
|
||||
|
||||
const variables = [
|
||||
`--state-${domain}-${stateKey}-color`,
|
||||
`--state-${domain}-${activeKey}-color`,
|
||||
`--state-${activeKey}-color`,
|
||||
];
|
||||
|
||||
if (deviceClass) {
|
||||
variables.unshift(`--state-${domain}-${deviceClass}-${stateKey}-color`);
|
||||
}
|
||||
|
||||
for (const variable of variables) {
|
||||
const value = style.getPropertyValue(variable).trim();
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const stateColorBrightness = (stateObj: HassEntity): string => {
|
||||
if (
|
||||
stateObj.attributes.brightness &&
|
||||
|
@@ -3,7 +3,7 @@ import { getGraphColorByIndex } from "../../common/color/colors";
|
||||
import { hex2rgb, lab2hex, rgb2lab } from "../../common/color/convert-color";
|
||||
import { labBrighten } from "../../common/color/lab";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stateColorProperties } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { computeCssValue } from "../../resources/css-variables";
|
||||
|
||||
@@ -30,22 +30,16 @@ function computeTimelineStateColor(
|
||||
return computeCssValue("--history-unknown-color", computedStyles);
|
||||
}
|
||||
|
||||
const properties = stateColorProperties(stateObj, state);
|
||||
const color = stateColor(computedStyles, stateObj, state);
|
||||
|
||||
if (!properties) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const rgb = computeCssValue(properties, computedStyles);
|
||||
|
||||
if (!rgb) return undefined;
|
||||
if (!color) return undefined;
|
||||
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
const shade = DOMAIN_STATE_SHADES[domain]?.[state] as number | number;
|
||||
if (!shade) {
|
||||
return rgb;
|
||||
return color;
|
||||
}
|
||||
return lab2hex(labBrighten(rgb2lab(hex2rgb(rgb)), shade));
|
||||
return lab2hex(labBrighten(rgb2lab(hex2rgb(color)), shade));
|
||||
}
|
||||
|
||||
let colorIndex = 0;
|
||||
|
@@ -9,7 +9,7 @@ import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import {
|
||||
stateColorBrightness,
|
||||
stateColorCss,
|
||||
stateColor,
|
||||
} from "../../common/entity/state_color";
|
||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
||||
@@ -148,7 +148,7 @@ export class StateBadge extends LitElement {
|
||||
// Externally provided overriding color wins over state color
|
||||
iconStyle.color = this.color;
|
||||
} else if (this._stateColor) {
|
||||
const color = stateColorCss(stateObj);
|
||||
const color = stateColor(this, stateObj);
|
||||
if (color) {
|
||||
iconStyle.color = color;
|
||||
}
|
||||
@@ -169,7 +169,8 @@ export class StateBadge extends LitElement {
|
||||
if (stateObj.attributes.hvac_action) {
|
||||
const hvacAction = stateObj.attributes.hvac_action;
|
||||
if (hvacAction in CLIMATE_HVAC_ACTION_TO_MODE) {
|
||||
iconStyle.color = stateColorCss(
|
||||
iconStyle.color = stateColor(
|
||||
this,
|
||||
stateObj,
|
||||
CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]
|
||||
)!;
|
||||
|
@@ -1,262 +1,62 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { css, html, LitElement, type PropertyValues } from "lit";
|
||||
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
||||
const ANIMATION_DURATION_MS = 300;
|
||||
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||
|
||||
/**
|
||||
* A bottom sheet component that slides up from the bottom of the screen.
|
||||
*
|
||||
* The bottom sheet provides a draggable interface that allows users to resize
|
||||
* the sheet by dragging the handle at the top. It supports both mouse and touch
|
||||
* interactions and automatically closes when dragged below a 20% of screen height.
|
||||
*
|
||||
* @fires bottom-sheet-closed - Fired when the bottom sheet is closed
|
||||
*
|
||||
* @cssprop --ha-bottom-sheet-border-width - Border width for the sheet
|
||||
* @cssprop --ha-bottom-sheet-border-style - Border style for the sheet
|
||||
* @cssprop --ha-bottom-sheet-border-color - Border color for the sheet
|
||||
*/
|
||||
@customElement("ha-bottom-sheet")
|
||||
export class HaBottomSheet extends LitElement {
|
||||
@query("dialog") private _dialog!: HTMLDialogElement;
|
||||
@property({ type: Boolean }) public open = false;
|
||||
|
||||
private _dragging = false;
|
||||
@state() private _drawerOpen = false;
|
||||
|
||||
private _dragStartY = 0;
|
||||
private _handleAfterHide() {
|
||||
this.open = false;
|
||||
const ev = new Event("closed", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
});
|
||||
this.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
private _initialSize = 0;
|
||||
|
||||
@state() private _dialogMaxViewpointHeight = 70;
|
||||
|
||||
@state() private _dialogMinViewpointHeight = 55;
|
||||
|
||||
@state() private _dialogViewportHeight?: number;
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("open")) {
|
||||
this._drawerOpen = this.open;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<dialog
|
||||
open
|
||||
@transitionend=${this._handleTransitionEnd}
|
||||
style=${styleMap({
|
||||
height: this._dialogViewportHeight
|
||||
? `${this._dialogViewportHeight}vh`
|
||||
: "auto",
|
||||
maxHeight: `${this._dialogMaxViewpointHeight}vh`,
|
||||
minHeight: `${this._dialogMinViewpointHeight}vh`,
|
||||
})}
|
||||
>
|
||||
<div class="handle-wrapper">
|
||||
<div
|
||||
@mousedown=${this._handleMouseDown}
|
||||
@touchstart=${this._handleTouchStart}
|
||||
class="handle"
|
||||
></div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</dialog>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._openSheet();
|
||||
}
|
||||
|
||||
private _openSheet() {
|
||||
requestAnimationFrame(() => {
|
||||
// trigger opening animation
|
||||
this._dialog.classList.add("show");
|
||||
});
|
||||
}
|
||||
|
||||
public closeSheet() {
|
||||
requestAnimationFrame(() => {
|
||||
this._dialog.classList.remove("show");
|
||||
});
|
||||
}
|
||||
|
||||
private _handleTransitionEnd() {
|
||||
if (this._dialog.classList.contains("show")) {
|
||||
// after show animation is done
|
||||
// - set the height to the natural height, to prevent content shift when switch content
|
||||
// - set max height to 90vh, so it opens at max 70vh but can be resized to 90vh
|
||||
this._dialogViewportHeight =
|
||||
(this._dialog.offsetHeight / window.innerHeight) * 100;
|
||||
this._dialogMaxViewpointHeight = 90;
|
||||
this._dialogMinViewpointHeight = 20;
|
||||
} else {
|
||||
// after close animation is done close dialog element and fire closed event
|
||||
this._dialog.close();
|
||||
fireEvent(this, "bottom-sheet-closed");
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// register event listeners for drag handling
|
||||
document.addEventListener("mousemove", this._handleMouseMove);
|
||||
document.addEventListener("mouseup", this._handleMouseUp);
|
||||
document.addEventListener("touchmove", this._handleTouchMove, {
|
||||
passive: false,
|
||||
});
|
||||
document.addEventListener("touchend", this._handleTouchEnd);
|
||||
document.addEventListener("touchcancel", this._handleTouchEnd);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
// unregister event listeners for drag handling
|
||||
document.removeEventListener("mousemove", this._handleMouseMove);
|
||||
document.removeEventListener("mouseup", this._handleMouseUp);
|
||||
document.removeEventListener("touchmove", this._handleTouchMove);
|
||||
document.removeEventListener("touchend", this._handleTouchEnd);
|
||||
document.removeEventListener("touchcancel", this._handleTouchEnd);
|
||||
}
|
||||
|
||||
private _handleMouseDown = (ev: MouseEvent) => {
|
||||
this._startDrag(ev.clientY);
|
||||
};
|
||||
|
||||
private _handleTouchStart = (ev: TouchEvent) => {
|
||||
// Prevent the browser from interpreting this as a scroll/PTR gesture.
|
||||
ev.preventDefault();
|
||||
this._startDrag(ev.touches[0].clientY);
|
||||
};
|
||||
|
||||
private _startDrag(clientY: number) {
|
||||
this._dragging = true;
|
||||
this._dragStartY = clientY;
|
||||
this._initialSize = (this._dialog.offsetHeight / window.innerHeight) * 100;
|
||||
document.body.style.setProperty("cursor", "grabbing");
|
||||
}
|
||||
|
||||
private _handleMouseMove = (ev: MouseEvent) => {
|
||||
if (!this._dragging) {
|
||||
return;
|
||||
}
|
||||
this._updateSize(ev.clientY);
|
||||
};
|
||||
|
||||
private _handleTouchMove = (ev: TouchEvent) => {
|
||||
if (!this._dragging) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault(); // Prevent scrolling
|
||||
this._updateSize(ev.touches[0].clientY);
|
||||
};
|
||||
|
||||
private _updateSize(clientY: number) {
|
||||
const deltaY = this._dragStartY - clientY;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const deltaVh = (deltaY / viewportHeight) * 100;
|
||||
|
||||
// Calculate new size and clamp between 10vh and 90vh
|
||||
let newSize = this._initialSize + deltaVh;
|
||||
newSize = Math.max(10, Math.min(90, newSize));
|
||||
|
||||
// on drag down and below 20vh
|
||||
if (newSize < 20 && deltaY < 0) {
|
||||
this._endDrag();
|
||||
this.closeSheet();
|
||||
return;
|
||||
}
|
||||
|
||||
this._dialogViewportHeight = newSize;
|
||||
}
|
||||
|
||||
private _handleMouseUp = () => {
|
||||
this._endDrag();
|
||||
};
|
||||
|
||||
private _handleTouchEnd = () => {
|
||||
this._endDrag();
|
||||
};
|
||||
|
||||
private _endDrag() {
|
||||
if (!this._dragging) {
|
||||
return;
|
||||
}
|
||||
this._dragging = false;
|
||||
document.body.style.removeProperty("cursor");
|
||||
return html`
|
||||
<wa-drawer
|
||||
placement="bottom"
|
||||
.open=${this._drawerOpen}
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
without-header
|
||||
>
|
||||
<slot></slot>
|
||||
</wa-drawer>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.handle-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding-bottom: 2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
.handle-wrapper .handle {
|
||||
height: 20px;
|
||||
width: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 7;
|
||||
padding-bottom: 76px;
|
||||
}
|
||||
.handle-wrapper .handle::after {
|
||||
content: "";
|
||||
border-radius: 8px;
|
||||
height: 4px;
|
||||
background: var(--divider-color, #e0e0e0);
|
||||
width: 80px;
|
||||
}
|
||||
.handle-wrapper .handle:active::after {
|
||||
cursor: grabbing;
|
||||
}
|
||||
dialog {
|
||||
height: auto;
|
||||
max-height: 70vh;
|
||||
min-height: 30vh;
|
||||
background-color: var(
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--mdc-theme-surface, #fff)
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
position: fixed;
|
||||
width: calc(100% - 4px);
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
box-shadow: var(--wa-shadow-l);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
top: auto;
|
||||
inset-inline-end: auto;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
|
||||
border-top-left-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-2xl)
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-2xl)
|
||||
);
|
||||
transform: translateY(100%);
|
||||
transition: transform ${ANIMATION_DURATION_MS}ms ease;
|
||||
border-top-width: var(--ha-bottom-sheet-border-width);
|
||||
border-right-width: var(--ha-bottom-sheet-border-width);
|
||||
border-left-width: var(--ha-bottom-sheet-border-width);
|
||||
border-bottom-width: 0;
|
||||
border-style: var(--ha-bottom-sheet-border-style);
|
||||
border-color: var(--ha-bottom-sheet-border-color);
|
||||
--spacing: 0;
|
||||
--size: auto;
|
||||
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
|
||||
dialog.show {
|
||||
transform: translateY(0);
|
||||
wa-drawer::part(dialog) {
|
||||
border-top-left-radius: var(--ha-border-radius-lg);
|
||||
border-top-right-radius: var(--ha-border-radius-lg);
|
||||
max-height: 90vh;
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -265,8 +65,4 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-bottom-sheet": HaBottomSheet;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"bottom-sheet-closed": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -57,6 +57,8 @@ export class HaButton extends Button {
|
||||
|
||||
font-size: var(--ha-font-size-m);
|
||||
line-height: 1;
|
||||
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
:host([size="small"]) .button {
|
||||
|
271
src/components/ha-resizable-bottom-sheet.ts
Normal file
271
src/components/ha-resizable-bottom-sheet.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { BOTTOM_SHEET_ANIMATION_DURATION_MS } from "./ha-bottom-sheet";
|
||||
|
||||
/**
|
||||
* A bottom sheet component that slides up from the bottom of the screen.
|
||||
*
|
||||
* The bottom sheet provides a draggable interface that allows users to resize
|
||||
* the sheet by dragging the handle at the top. It supports both mouse and touch
|
||||
* interactions and automatically closes when dragged below a 20% of screen height.
|
||||
*
|
||||
* @fires bottom-sheet-closed - Fired when the bottom sheet is closed
|
||||
*
|
||||
* @cssprop --ha-bottom-sheet-border-width - Border width for the sheet
|
||||
* @cssprop --ha-bottom-sheet-border-style - Border style for the sheet
|
||||
* @cssprop --ha-bottom-sheet-border-color - Border color for the sheet
|
||||
*/
|
||||
@customElement("ha-resizable-bottom-sheet")
|
||||
export class HaResizableBottomSheet extends LitElement {
|
||||
@query("dialog") private _dialog!: HTMLDialogElement;
|
||||
|
||||
private _dragging = false;
|
||||
|
||||
private _dragStartY = 0;
|
||||
|
||||
private _initialSize = 0;
|
||||
|
||||
@state() private _dialogMaxViewpointHeight = 70;
|
||||
|
||||
@state() private _dialogMinViewpointHeight = 55;
|
||||
|
||||
@state() private _dialogViewportHeight?: number;
|
||||
|
||||
render() {
|
||||
return html`<dialog
|
||||
open
|
||||
@transitionend=${this._handleTransitionEnd}
|
||||
style=${styleMap({
|
||||
height: this._dialogViewportHeight
|
||||
? `${this._dialogViewportHeight}vh`
|
||||
: "auto",
|
||||
maxHeight: `${this._dialogMaxViewpointHeight}vh`,
|
||||
minHeight: `${this._dialogMinViewpointHeight}vh`,
|
||||
})}
|
||||
>
|
||||
<div class="handle-wrapper">
|
||||
<div
|
||||
@mousedown=${this._handleMouseDown}
|
||||
@touchstart=${this._handleTouchStart}
|
||||
class="handle"
|
||||
></div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</dialog>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._openSheet();
|
||||
}
|
||||
|
||||
private _openSheet() {
|
||||
requestAnimationFrame(() => {
|
||||
// trigger opening animation
|
||||
this._dialog.classList.add("show");
|
||||
});
|
||||
}
|
||||
|
||||
public closeSheet() {
|
||||
requestAnimationFrame(() => {
|
||||
this._dialog.classList.remove("show");
|
||||
});
|
||||
}
|
||||
|
||||
private _handleTransitionEnd() {
|
||||
if (this._dialog.classList.contains("show")) {
|
||||
// after show animation is done
|
||||
// - set the height to the natural height, to prevent content shift when switch content
|
||||
// - set max height to 90vh, so it opens at max 70vh but can be resized to 90vh
|
||||
this._dialogViewportHeight =
|
||||
(this._dialog.offsetHeight / window.innerHeight) * 100;
|
||||
this._dialogMaxViewpointHeight = 90;
|
||||
this._dialogMinViewpointHeight = 20;
|
||||
} else {
|
||||
// after close animation is done close dialog element and fire closed event
|
||||
this._dialog.close();
|
||||
fireEvent(this, "bottom-sheet-closed");
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// register event listeners for drag handling
|
||||
document.addEventListener("mousemove", this._handleMouseMove);
|
||||
document.addEventListener("mouseup", this._handleMouseUp);
|
||||
document.addEventListener("touchmove", this._handleTouchMove, {
|
||||
passive: false,
|
||||
});
|
||||
document.addEventListener("touchend", this._handleTouchEnd);
|
||||
document.addEventListener("touchcancel", this._handleTouchEnd);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
// unregister event listeners for drag handling
|
||||
document.removeEventListener("mousemove", this._handleMouseMove);
|
||||
document.removeEventListener("mouseup", this._handleMouseUp);
|
||||
document.removeEventListener("touchmove", this._handleTouchMove);
|
||||
document.removeEventListener("touchend", this._handleTouchEnd);
|
||||
document.removeEventListener("touchcancel", this._handleTouchEnd);
|
||||
}
|
||||
|
||||
private _handleMouseDown = (ev: MouseEvent) => {
|
||||
this._startDrag(ev.clientY);
|
||||
};
|
||||
|
||||
private _handleTouchStart = (ev: TouchEvent) => {
|
||||
// Prevent the browser from interpreting this as a scroll/PTR gesture.
|
||||
ev.preventDefault();
|
||||
this._startDrag(ev.touches[0].clientY);
|
||||
};
|
||||
|
||||
private _startDrag(clientY: number) {
|
||||
this._dragging = true;
|
||||
this._dragStartY = clientY;
|
||||
this._initialSize = (this._dialog.offsetHeight / window.innerHeight) * 100;
|
||||
document.body.style.setProperty("cursor", "grabbing");
|
||||
}
|
||||
|
||||
private _handleMouseMove = (ev: MouseEvent) => {
|
||||
if (!this._dragging) {
|
||||
return;
|
||||
}
|
||||
this._updateSize(ev.clientY);
|
||||
};
|
||||
|
||||
private _handleTouchMove = (ev: TouchEvent) => {
|
||||
if (!this._dragging) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault(); // Prevent scrolling
|
||||
this._updateSize(ev.touches[0].clientY);
|
||||
};
|
||||
|
||||
private _updateSize(clientY: number) {
|
||||
const deltaY = this._dragStartY - clientY;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const deltaVh = (deltaY / viewportHeight) * 100;
|
||||
|
||||
// Calculate new size and clamp between 10vh and 90vh
|
||||
let newSize = this._initialSize + deltaVh;
|
||||
newSize = Math.max(10, Math.min(90, newSize));
|
||||
|
||||
// on drag down and below 20vh
|
||||
if (newSize < 20 && deltaY < 0) {
|
||||
this._endDrag();
|
||||
this.closeSheet();
|
||||
return;
|
||||
}
|
||||
|
||||
this._dialogViewportHeight = newSize;
|
||||
}
|
||||
|
||||
private _handleMouseUp = () => {
|
||||
this._endDrag();
|
||||
};
|
||||
|
||||
private _handleTouchEnd = () => {
|
||||
this._endDrag();
|
||||
};
|
||||
|
||||
private _endDrag() {
|
||||
if (!this._dragging) {
|
||||
return;
|
||||
}
|
||||
this._dragging = false;
|
||||
document.body.style.removeProperty("cursor");
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.handle-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding-bottom: 2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
.handle-wrapper .handle {
|
||||
height: 20px;
|
||||
width: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 7;
|
||||
padding-bottom: 76px;
|
||||
}
|
||||
.handle-wrapper .handle::after {
|
||||
content: "";
|
||||
border-radius: 8px;
|
||||
height: 4px;
|
||||
background: var(--divider-color, #e0e0e0);
|
||||
width: 80px;
|
||||
}
|
||||
.handle-wrapper .handle:active::after {
|
||||
cursor: grabbing;
|
||||
}
|
||||
dialog {
|
||||
height: auto;
|
||||
max-height: 70vh;
|
||||
min-height: 30vh;
|
||||
background-color: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--mdc-theme-surface, #fff)
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
position: fixed;
|
||||
width: calc(100% - 4px);
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
box-shadow: var(--wa-shadow-l);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
top: auto;
|
||||
inset-inline-end: auto;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
|
||||
border-top-left-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-2xl)
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-2xl)
|
||||
);
|
||||
transform: translateY(100%);
|
||||
transition: transform ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms ease;
|
||||
border-top-width: var(--ha-bottom-sheet-border-width);
|
||||
border-right-width: var(--ha-bottom-sheet-border-width);
|
||||
border-left-width: var(--ha-bottom-sheet-border-width);
|
||||
border-bottom-width: 0;
|
||||
border-style: var(--ha-bottom-sheet-border-style);
|
||||
border-color: var(--ha-bottom-sheet-border-color);
|
||||
}
|
||||
|
||||
dialog.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-resizable-bottom-sheet": HaResizableBottomSheet;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"bottom-sheet-closed": undefined;
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-bottom-sheet";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-md-list";
|
||||
@@ -40,6 +41,54 @@ export class ListItemsDialog
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const content = html`
|
||||
<div class="container">
|
||||
<ha-md-list>
|
||||
${this._params.items.map(
|
||||
(item) => html`
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
@click=${this._itemClicked}
|
||||
.item=${item}
|
||||
>
|
||||
${item.iconPath
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: item.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
icon=${item.icon}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-icon>
|
||||
`
|
||||
: nothing}
|
||||
<span class="headline">${item.label}</span>
|
||||
${item.description
|
||||
? html`
|
||||
<span class="supporting-text">${item.description}</span>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (this._params.mode === "bottom-sheet") {
|
||||
return html`
|
||||
<ha-bottom-sheet placement="bottom" open @closed=${this._dialogClosed}>
|
||||
${content}
|
||||
</ha-bottom-sheet>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@@ -47,43 +96,7 @@ export class ListItemsDialog
|
||||
@closed=${this._dialogClosed}
|
||||
hideActions
|
||||
>
|
||||
<div class="container">
|
||||
<ha-md-list>
|
||||
${this._params.items.map(
|
||||
(item) => html`
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
@click=${this._itemClicked}
|
||||
.item=${item}
|
||||
>
|
||||
${item.iconPath
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: item.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
icon=${item.icon}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-icon>
|
||||
`
|
||||
: nothing}
|
||||
<span class="headline">${item.label}</span>
|
||||
${item.description
|
||||
? html`
|
||||
<span class="supporting-text">${item.description}</span>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</div>
|
||||
${content}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ interface ListItem {
|
||||
export interface ListItemsDialogParams {
|
||||
title?: string;
|
||||
items: ListItem[];
|
||||
mode?: "dialog" | "bottom-sheet";
|
||||
}
|
||||
|
||||
export const showListItemsDialog = (
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
temperature2rgb,
|
||||
} from "../../../../common/color/convert-light-color";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../../common/entity/state_color";
|
||||
import { throttle } from "../../../../common/util/throttle";
|
||||
import "../../../../components/ha-control-slider";
|
||||
import { UNAVAILABLE } from "../../../../data/entity";
|
||||
@@ -66,7 +66,7 @@ class LightColorTempPicker extends LitElement {
|
||||
this.stateObj.attributes.max_color_temp_kelvin ?? DEFAULT_MAX_KELVIN;
|
||||
|
||||
const gradient = this._generateTemperatureGradient(minKelvin!, maxKelvin);
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
|
||||
return html`
|
||||
<ha-control-slider
|
||||
|
@@ -2,7 +2,7 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-state-icon";
|
||||
import type { AlarmControlPanelEntity } from "../../../data/alarm_control_panel";
|
||||
@@ -32,7 +32,7 @@ class MoreInfoAlarmControlPanel extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
const style = {
|
||||
"--icon-color": color,
|
||||
};
|
||||
|
@@ -3,7 +3,7 @@ import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-control-button";
|
||||
@@ -82,7 +82,7 @@ class MoreInfoLock extends LitElement {
|
||||
|
||||
const supportsOpen = supportsFeature(this.stateObj, LockEntityFeature.OPEN);
|
||||
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
const style = {
|
||||
"--state-color": color,
|
||||
};
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import "../../../components/ha-bottom-sheet";
|
||||
import type { HaBottomSheet } from "../../../components/ha-bottom-sheet";
|
||||
import "../../../components/ha-resizable-bottom-sheet";
|
||||
import type { HaResizableBottomSheet } from "../../../components/ha-resizable-bottom-sheet";
|
||||
import {
|
||||
isCondition,
|
||||
isScriptField,
|
||||
@@ -37,7 +37,8 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@query("ha-bottom-sheet") private _bottomSheetElement?: HaBottomSheet;
|
||||
@query("ha-resizable-bottom-sheet")
|
||||
private _bottomSheetElement?: HaResizableBottomSheet;
|
||||
|
||||
private _renderContent() {
|
||||
// get config type
|
||||
@@ -147,9 +148,9 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
|
||||
if (this.narrow) {
|
||||
return html`
|
||||
<ha-bottom-sheet @bottom-sheet-closed=${this._closeSidebar}>
|
||||
<ha-resizable-bottom-sheet @bottom-sheet-closed=${this._closeSidebar}>
|
||||
${this._renderContent()}
|
||||
</ha-bottom-sheet>
|
||||
</ha-resizable-bottom-sheet>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-badge";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-state-icon";
|
||||
@@ -132,7 +132,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
}
|
||||
|
||||
// Fallback to state color
|
||||
return stateColorCss(stateObj);
|
||||
return stateColor(this, stateObj);
|
||||
}
|
||||
);
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-control-select";
|
||||
@@ -143,7 +143,7 @@ class HuiAlarmModeCardFeature
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const color = stateColorCss(this._stateObj);
|
||||
const color = stateColor(this, this._stateObj);
|
||||
|
||||
const supportedModes = supportedAlarmModes(this._stateObj).reverse();
|
||||
|
||||
|
@@ -11,14 +11,13 @@ import {
|
||||
toggleGroupEntities,
|
||||
} from "../../../common/entity/group_entities";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { domainColorProperties } from "../../../common/entity/state_color";
|
||||
import { domainStateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-domain-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { computeCssVariable } from "../../../resources/css-variables";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { AreaCardFeatureContext } from "../cards/hui-area-card";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
@@ -258,14 +257,12 @@ class HuiAreaControlsCardFeature
|
||||
? ensureArray(button.filter.device_class)[0]
|
||||
: undefined;
|
||||
|
||||
const activeColor = computeCssVariable(
|
||||
domainColorProperties(domain, deviceClass, groupState, true)
|
||||
);
|
||||
const color = domainStateColor(this, domain, deviceClass, groupState);
|
||||
|
||||
return html`
|
||||
<ha-control-button
|
||||
style=${styleMap({
|
||||
"--active-color": activeColor,
|
||||
"--active-color": color,
|
||||
})}
|
||||
.title=${label}
|
||||
aria-label=${label}
|
||||
|
@@ -5,7 +5,7 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../../components/ha-control-select";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
@@ -145,7 +145,7 @@ class HuiClimateHvacModesCardFeature
|
||||
return null;
|
||||
}
|
||||
|
||||
const color = stateColorCss(this._stateObj);
|
||||
const color = stateColor(this, this._stateObj);
|
||||
|
||||
const ordererHvacModes = (this._stateObj.attributes.hvac_modes || [])
|
||||
.concat()
|
||||
|
@@ -5,7 +5,7 @@ import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-slider";
|
||||
import { CoverEntityFeature, type CoverEntity } from "../../../data/cover";
|
||||
@@ -84,11 +84,11 @@ class HuiCoverPositionCardFeature
|
||||
|
||||
const value = Math.max(Math.round(percentage), 0);
|
||||
|
||||
const openColor = stateColorCss(this._stateObj, "open");
|
||||
const openColor = stateColor(this, this._stateObj, "open");
|
||||
|
||||
const color = this.color
|
||||
? computeCssColor(this.color)
|
||||
: stateColorCss(this._stateObj);
|
||||
: stateColor(this, this._stateObj);
|
||||
|
||||
const style = {
|
||||
"--feature-color": color,
|
||||
|
@@ -4,7 +4,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import type { CoverEntity } from "../../../data/cover";
|
||||
import { CoverEntityFeature } from "../../../data/cover";
|
||||
@@ -84,11 +84,11 @@ class HuiCoverTiltPositionCardFeature
|
||||
|
||||
const value = Math.max(Math.round(percentage), 0);
|
||||
|
||||
const openColor = stateColorCss(this._stateObj, "open");
|
||||
const openColor = stateColor(this, this._stateObj, "open");
|
||||
|
||||
const color = this.color
|
||||
? computeCssColor(this.color)
|
||||
: stateColorCss(this._stateObj);
|
||||
: stateColor(this, this._stateObj);
|
||||
|
||||
const style = {
|
||||
"--feature-color": color,
|
||||
|
@@ -4,7 +4,7 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../../components/ha-control-select";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
@@ -112,7 +112,7 @@ class HuiFanOscillateCardFeature
|
||||
return null;
|
||||
}
|
||||
|
||||
const color = stateColorCss(this._stateObj);
|
||||
const color = stateColor(this, this._stateObj);
|
||||
|
||||
const yesNo = ["no", "yes"] as const;
|
||||
const options = yesNo.map<ControlSelectOption>((oscillating) => ({
|
||||
|
@@ -4,7 +4,7 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../../components/ha-control-select";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
@@ -117,7 +117,7 @@ class HuiHumidifierToggleCardFeature
|
||||
return null;
|
||||
}
|
||||
|
||||
const color = stateColorCss(this._stateObj);
|
||||
const color = stateColor(this, this._stateObj);
|
||||
|
||||
const options = ["off", "on"].map<ControlSelectOption>((entityState) => ({
|
||||
value: entityState,
|
||||
|
@@ -5,7 +5,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { UNIT_F } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-control-button-group";
|
||||
@@ -192,7 +192,7 @@ class HuiTargetTemperatureCardFeature
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateColor = stateColorCss(this._stateObj);
|
||||
const color = stateColor(this, this._stateObj);
|
||||
const digits = this._step.toString().split(".")?.[1]?.length ?? 0;
|
||||
|
||||
const options = {
|
||||
@@ -221,7 +221,7 @@ class HuiTargetTemperatureCardFeature
|
||||
"temperature"
|
||||
)}
|
||||
style=${styleMap({
|
||||
"--control-number-buttons-focus-color": stateColor,
|
||||
"--control-number-buttons-focus-color": color,
|
||||
})}
|
||||
.disabled=${this._stateObj!.state === UNAVAILABLE}
|
||||
.locale=${this.hass.locale}
|
||||
|
@@ -14,7 +14,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-control-switch";
|
||||
@@ -134,7 +134,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
|
||||
}
|
||||
|
||||
const onColor = "var(--feature-color)";
|
||||
const offColor = stateColorCss(this._stateObj, "off");
|
||||
const offColor = stateColor(this, this._stateObj, "off");
|
||||
|
||||
const isOn = this._stateObj.state === "on";
|
||||
const isOff = this._stateObj.state === "off";
|
||||
|
@@ -5,7 +5,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-svg-icon";
|
||||
@@ -125,8 +125,8 @@ class HuiValveOpenCloseCardFeature
|
||||
}
|
||||
|
||||
// Determine colors and active states for toggle-style UI
|
||||
const openColor = stateColorCss(this._stateObj, "open");
|
||||
const closedColor = stateColorCss(this._stateObj, "closed");
|
||||
const openColor = stateColor(this, this._stateObj, "open");
|
||||
const closedColor = stateColor(this, this._stateObj, "closed");
|
||||
const openIcon = mdiValveOpen;
|
||||
const closedIcon = mdiValveClosed;
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-slider";
|
||||
import { ValveEntityFeature, type ValveEntity } from "../../../data/valve";
|
||||
@@ -84,11 +84,11 @@ class HuiValvePositionCardFeature
|
||||
|
||||
const value = Math.max(Math.round(percentage), 0);
|
||||
|
||||
const openColor = stateColorCss(this._stateObj, "open");
|
||||
const openColor = stateColor(this, this._stateObj, "open");
|
||||
|
||||
const color = this.color
|
||||
? computeCssColor(this.color)
|
||||
: stateColorCss(this._stateObj);
|
||||
: stateColor(this, this._stateObj);
|
||||
|
||||
const style = {
|
||||
"--feature-color": color,
|
||||
|
@@ -3,7 +3,7 @@ import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-control-select";
|
||||
@@ -130,7 +130,7 @@ class HuiWaterHeaterOperationModeCardFeature
|
||||
return null;
|
||||
}
|
||||
|
||||
const color = stateColorCss(this._stateObj);
|
||||
const color = stateColor(this, this._stateObj);
|
||||
|
||||
const orderedModes = (this._stateObj.attributes.operation_list || [])
|
||||
.concat()
|
||||
|
@@ -7,7 +7,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/chips/ha-assist-chip";
|
||||
import "../../../components/ha-button";
|
||||
@@ -241,7 +241,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||
<ha-assist-chip
|
||||
filled
|
||||
style=${styleMap({
|
||||
"--alarm-state-color": stateColorCss(stateObj),
|
||||
"--alarm-state-color": stateColor(this, stateObj),
|
||||
})}
|
||||
class=${classMap({ [stateObj.state]: true })}
|
||||
@click=${this._handleMoreInfo}
|
||||
|
@@ -18,7 +18,7 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import {
|
||||
stateColorBrightness,
|
||||
stateColorCss,
|
||||
stateColor,
|
||||
} from "../../../common/entity/state_color";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
||||
@@ -341,11 +341,15 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
if (stateObj.attributes.hvac_action) {
|
||||
const hvacAction = stateObj.attributes.hvac_action;
|
||||
if (hvacAction in CLIMATE_HVAC_ACTION_TO_MODE) {
|
||||
return stateColorCss(stateObj, CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]);
|
||||
return stateColor(
|
||||
this,
|
||||
stateObj,
|
||||
CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const iconColor = stateColorCss(stateObj);
|
||||
const iconColor = stateColor(this, stateObj);
|
||||
if (iconColor) {
|
||||
return iconColor;
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import {
|
||||
stateColorBrightness,
|
||||
stateColorCss,
|
||||
stateColor,
|
||||
} from "../../../common/entity/state_color";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import {
|
||||
@@ -203,14 +203,18 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
if (stateObj.attributes.hvac_action) {
|
||||
const hvacAction = stateObj.attributes.hvac_action;
|
||||
if (hvacAction in CLIMATE_HVAC_ACTION_TO_MODE) {
|
||||
return stateColorCss(stateObj, CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]);
|
||||
return stateColor(
|
||||
this,
|
||||
stateObj,
|
||||
CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (stateObj.attributes.rgb_color) {
|
||||
return `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
||||
}
|
||||
const iconColor = stateColorCss(stateObj);
|
||||
const iconColor = stateColor(this, stateObj);
|
||||
if (iconColor) {
|
||||
return iconColor;
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import type { HumidifierEntity } from "../../../data/humidifier";
|
||||
@@ -135,7 +135,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const name = this._config!.name || computeStateName(stateObj);
|
||||
|
||||
const color = stateColorCss(stateObj);
|
||||
const color = stateColor(this, stateObj);
|
||||
|
||||
const controlMaxWidth = this._resizeController.value
|
||||
? `${this._resizeController.value}px`
|
||||
|
@@ -7,7 +7,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import type { ClimateEntity } from "../../../data/climate";
|
||||
@@ -127,7 +127,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const name = this._config!.name || computeStateName(stateObj);
|
||||
|
||||
const color = stateColorCss(stateObj);
|
||||
const color = stateColor(this, stateObj);
|
||||
|
||||
const controlMaxWidth = this._resizeController.value
|
||||
? `${this._resizeController.value}px`
|
||||
|
@@ -11,7 +11,7 @@ import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-state-icon";
|
||||
@@ -202,7 +202,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
// Fallback to state color
|
||||
return stateColorCss(entity);
|
||||
return stateColor(this, entity);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -323,7 +323,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></ha-state-icon>
|
||||
${renderTileBadge(stateObj, this.hass)}
|
||||
${renderTileBadge(this, stateObj, this.hass)}
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info
|
||||
id="info"
|
||||
|
@@ -1,13 +1,17 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../../../common/entity/state_color";
|
||||
import "../../../../../components/ha-attribute-icon";
|
||||
import "../../../../../components/tile/ha-tile-badge";
|
||||
import type { ClimateEntity } from "../../../../../data/climate";
|
||||
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../../../../data/climate";
|
||||
import type { RenderBadgeFunction } from "./tile-badge";
|
||||
|
||||
export const renderClimateBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
export const renderClimateBadge: RenderBadgeFunction = (
|
||||
element,
|
||||
stateObj,
|
||||
hass
|
||||
) => {
|
||||
const hvacAction = (stateObj as ClimateEntity).attributes.hvac_action;
|
||||
|
||||
if (!hvacAction || hvacAction === "off") {
|
||||
@@ -17,7 +21,8 @@ export const renderClimateBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
return html`
|
||||
<ha-tile-badge
|
||||
style=${styleMap({
|
||||
"--tile-badge-background-color": stateColorCss(
|
||||
"--tile-badge-background-color": stateColor(
|
||||
element,
|
||||
stateObj,
|
||||
CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]
|
||||
),
|
||||
|
@@ -1,13 +1,17 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../../../common/entity/state_color";
|
||||
import "../../../../../components/ha-attribute-icon";
|
||||
import "../../../../../components/tile/ha-tile-badge";
|
||||
import type { HumidifierEntity } from "../../../../../data/humidifier";
|
||||
import { HUMIDIFIER_ACTION_MODE } from "../../../../../data/humidifier";
|
||||
import type { RenderBadgeFunction } from "./tile-badge";
|
||||
|
||||
export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
export const renderHumidifierBadge: RenderBadgeFunction = (
|
||||
element,
|
||||
stateObj,
|
||||
hass
|
||||
) => {
|
||||
const action = (stateObj as HumidifierEntity).attributes.action;
|
||||
|
||||
if (!action || action === "off") {
|
||||
@@ -17,7 +21,8 @@ export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
return html`
|
||||
<ha-tile-badge
|
||||
style=${styleMap({
|
||||
"--tile-badge-background-color": stateColorCss(
|
||||
"--tile-badge-background-color": stateColor(
|
||||
element,
|
||||
stateObj,
|
||||
HUMIDIFIER_ACTION_MODE[action]
|
||||
),
|
||||
|
@@ -2,7 +2,7 @@ import { mdiHome, mdiHomeExportOutline } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html } from "lit";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../../../common/entity/state_color";
|
||||
import "../../../../../components/ha-icon";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/tile/ha-tile-badge";
|
||||
@@ -20,7 +20,11 @@ function getZone(entity: HassEntity, hass: HomeAssistant) {
|
||||
return zones.find((z) => state === z.attributes.friendly_name);
|
||||
}
|
||||
|
||||
export const renderPersonBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
export const renderPersonBadge: RenderBadgeFunction = (
|
||||
element,
|
||||
stateObj,
|
||||
hass
|
||||
) => {
|
||||
const zone = getZone(stateObj, hass);
|
||||
|
||||
const zoneIcon = zone?.attributes.icon;
|
||||
@@ -29,7 +33,7 @@ export const renderPersonBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
return html`
|
||||
<ha-tile-badge
|
||||
style=${styleMap({
|
||||
"--tile-badge-background-color": stateColorCss(stateObj),
|
||||
"--tile-badge-background-color": stateColor(element, stateObj),
|
||||
})}
|
||||
>
|
||||
<ha-icon .icon=${zoneIcon}></ha-icon>
|
||||
@@ -43,7 +47,7 @@ export const renderPersonBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
return html`
|
||||
<ha-tile-badge
|
||||
style=${styleMap({
|
||||
"--tile-badge-background-color": stateColorCss(stateObj),
|
||||
"--tile-badge-background-color": stateColor(element, stateObj),
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon .path=${defaultIcon}></ha-svg-icon>
|
||||
|
@@ -13,11 +13,16 @@ import "../../../../../components/tile/ha-tile-badge";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
|
||||
export type RenderBadgeFunction = (
|
||||
element: HTMLElement,
|
||||
stateObj: HassEntity,
|
||||
hass: HomeAssistant
|
||||
) => TemplateResult | typeof nothing;
|
||||
|
||||
export const renderTileBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
export const renderTileBadge: RenderBadgeFunction = (
|
||||
element,
|
||||
stateObj,
|
||||
hass
|
||||
) => {
|
||||
if (stateObj.state === UNKNOWN) {
|
||||
return nothing;
|
||||
}
|
||||
@@ -36,11 +41,11 @@ export const renderTileBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
switch (domain) {
|
||||
case "person":
|
||||
case "device_tracker":
|
||||
return renderPersonBadge(stateObj, hass);
|
||||
return renderPersonBadge(element, stateObj, hass);
|
||||
case "climate":
|
||||
return renderClimateBadge(stateObj, hass);
|
||||
return renderClimateBadge(element, stateObj, hass);
|
||||
case "humidifier":
|
||||
return renderHumidifierBadge(stateObj, hass);
|
||||
return renderHumidifierBadge(element, stateObj, hass);
|
||||
default:
|
||||
return nothing;
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-heading-badge";
|
||||
import "../../../components/ha-state-icon";
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
@@ -97,7 +97,7 @@ export class HuiEntityHeadingBadge
|
||||
return rgb2hex(hsv2rgb(hsvColor));
|
||||
}
|
||||
// Fallback to state color
|
||||
return stateColorCss(entity);
|
||||
return stateColor(this, entity);
|
||||
}
|
||||
|
||||
if (color) {
|
||||
|
@@ -230,10 +230,10 @@ class HUIRoot extends LitElement {
|
||||
},
|
||||
{
|
||||
icon: mdiSofa,
|
||||
key: "ui.panel.lovelace.menu.add_area",
|
||||
key: "ui.panel.lovelace.menu.create_area",
|
||||
visible: true,
|
||||
action: this._addArea,
|
||||
overflowAction: this._handleAddArea,
|
||||
action: this._createArea,
|
||||
overflowAction: this._handleCreateArea,
|
||||
},
|
||||
{
|
||||
icon: mdiAccount,
|
||||
@@ -366,6 +366,7 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
showListItemsDialog(this, {
|
||||
title: title,
|
||||
mode: this.narrow ? "bottom-sheet" : "dialog",
|
||||
items: i.subItems!.map((si) => ({
|
||||
iconPath: si.icon,
|
||||
label: this.hass!.localize(si.key),
|
||||
@@ -837,14 +838,14 @@ class HUIRoot extends LitElement {
|
||||
showNewAutomationDialog(this, { mode: "automation" });
|
||||
};
|
||||
|
||||
private _handleAddArea(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
private _handleCreateArea(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._addArea();
|
||||
this._createArea();
|
||||
}
|
||||
|
||||
private _addArea = async () => {
|
||||
private _createArea = async () => {
|
||||
await this.hass.loadFragmentTranslation("config");
|
||||
showAreaRegistryDetailDialog(this, {
|
||||
createEntry: async (values) => {
|
||||
@@ -854,13 +855,15 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.lovelace.menu.add_area_success"
|
||||
"ui.panel.lovelace.menu.create_area_success"
|
||||
),
|
||||
action: {
|
||||
action: () => {
|
||||
navigate(`/config/areas/area/${area.area_id}`);
|
||||
},
|
||||
text: this.hass.localize("ui.panel.lovelace.menu.add_area_action"),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.lovelace.menu.create_area_action"
|
||||
),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@@ -2,7 +2,7 @@ import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import { mdiEyeOff, mdiViewGridPlus } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
@@ -14,10 +14,7 @@ import "../../../components/ha-sortable";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type {
|
||||
LovelaceSectionConfig,
|
||||
LovelaceSectionRawConfig,
|
||||
} from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HuiBadge } from "../badges/hui-badge";
|
||||
@@ -31,7 +28,6 @@ import {
|
||||
getLovelaceContainerPath,
|
||||
parseLovelaceCardPath,
|
||||
} from "../editor/lovelace-path";
|
||||
import "../sections/hui-section";
|
||||
import type { HuiSection } from "../sections/hui-section";
|
||||
import type { Lovelace } from "../types";
|
||||
import "./hui-view-header";
|
||||
@@ -50,6 +46,8 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
@property({ attribute: false }) public isStrategy = false;
|
||||
|
||||
@property({ attribute: false }) public sections: HuiSection[] = [];
|
||||
|
||||
@property({ attribute: false }) public cards: HuiCard[] = [];
|
||||
|
||||
@property({ attribute: false }) public badges: HuiBadge[] = [];
|
||||
@@ -60,8 +58,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
@state() _dragging = false;
|
||||
|
||||
@query(".container") private _container?: HTMLElement;
|
||||
|
||||
private _columnsController = new ResizeController(this, {
|
||||
callback: (entries) => {
|
||||
const totalWidth = entries[0]?.contentRect.width;
|
||||
@@ -92,9 +88,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _sectionConfigKeys = new WeakMap<LovelaceSectionRawConfig, string>();
|
||||
private _sectionConfigKeys = new WeakMap<HuiSection, string>();
|
||||
|
||||
private _getSectionKey(section: LovelaceSectionRawConfig) {
|
||||
private _getSectionKey(section: HuiSection) {
|
||||
if (!this._sectionConfigKeys.has(section)) {
|
||||
this._sectionConfigKeys.set(section, Math.random().toString());
|
||||
}
|
||||
@@ -102,44 +98,52 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
}
|
||||
|
||||
private _computeSectionsCount() {
|
||||
const sections = Array.from(
|
||||
this._container?.querySelectorAll("hui-section") ?? []
|
||||
);
|
||||
|
||||
this._sectionColumnCount =
|
||||
sections
|
||||
.filter((section: HuiSection) => !section.hidden)
|
||||
.map((section) => section.config.column_span ?? 1)
|
||||
.reduce((acc, val) => acc + val, 0) || 0;
|
||||
this._sectionColumnCount = this.sections
|
||||
.filter((section) => !section.hidden)
|
||||
.map((section) => section.config.column_span ?? 1)
|
||||
.reduce((acc, val) => acc + val, 0);
|
||||
}
|
||||
|
||||
private _sectionVisibilityChanged = () => {
|
||||
this._computeSectionsCount();
|
||||
};
|
||||
|
||||
public updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.addEventListener(
|
||||
"section-visibility-changed",
|
||||
this._sectionVisibilityChanged
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProps.has("_config")) {
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener(
|
||||
"section-visibility-changed",
|
||||
this._sectionVisibilityChanged
|
||||
);
|
||||
}
|
||||
|
||||
willUpdate(changedProperties: PropertyValues<typeof this>): void {
|
||||
if (changedProperties.has("sections")) {
|
||||
this._computeSectionsCount();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.lovelace || !this._config) return nothing;
|
||||
if (!this.lovelace) return nothing;
|
||||
|
||||
const sections = this.sections;
|
||||
const totalSectionCount =
|
||||
this._sectionColumnCount + (this.lovelace?.editMode ? 1 : 0);
|
||||
const editMode = this.lovelace.editMode;
|
||||
|
||||
const maxColumnCount = this._columnsController.value ?? 1;
|
||||
|
||||
const sections = this._config.sections || [];
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="wrapper ${classMap({
|
||||
"top-margin": Boolean(this._config.top_margin),
|
||||
"top-margin": Boolean(this._config?.top_margin),
|
||||
})}"
|
||||
>
|
||||
<hui-view-header
|
||||
@@ -147,7 +151,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
.badges=${this.badges}
|
||||
.lovelace=${this.lovelace}
|
||||
.viewIndex=${this.index}
|
||||
.config=${this._config.header}
|
||||
.config=${this._config?.header}
|
||||
style=${styleMap({
|
||||
"--max-column-count": maxColumnCount,
|
||||
})}
|
||||
@@ -162,7 +166,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
>
|
||||
<div
|
||||
class="container ${classMap({
|
||||
dense: Boolean(this._config.dense_section_placement),
|
||||
dense: Boolean(this._config?.dense_section_placement),
|
||||
})}"
|
||||
style=${styleMap({
|
||||
"--total-section-count": totalSectionCount,
|
||||
@@ -171,14 +175,13 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
>
|
||||
${repeat(
|
||||
sections,
|
||||
(section: LovelaceSectionRawConfig) =>
|
||||
this._getSectionKey(section),
|
||||
(section: LovelaceSectionRawConfig, idx) => {
|
||||
(section) => this._getSectionKey(section),
|
||||
(section, idx) => {
|
||||
const columnSpan = Math.min(
|
||||
section.column_span || 1,
|
||||
section.config.column_span || 1,
|
||||
maxColumnCount
|
||||
);
|
||||
const rowSpan = section.row_span || 1;
|
||||
const rowSpan = section.config.row_span || 1;
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -202,15 +205,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
`
|
||||
: section
|
||||
}
|
||||
<hui-section
|
||||
.lovelace=${this.lovelace}
|
||||
.hass=${this.hass}
|
||||
.config=${this._config!.sections![idx]}
|
||||
.viewIndex=${this.index}
|
||||
.index=${idx}
|
||||
.preview=${this.lovelace?.editMode || false}
|
||||
@section-visibility-changed=${this._sectionVisibilityChanged}
|
||||
></hui-section>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -249,7 +243,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
</ha-sortable>
|
||||
`
|
||||
: nothing}
|
||||
${editMode && this._config.cards?.length
|
||||
${editMode && this._config?.cards?.length
|
||||
? html`
|
||||
<div class="section imported-cards">
|
||||
<div class="imported-card-header">
|
||||
|
@@ -12,6 +12,7 @@ import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import { ensureBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type {
|
||||
LovelaceViewConfig,
|
||||
LovelaceViewRawConfig,
|
||||
@@ -38,6 +39,9 @@ import {
|
||||
} from "../editor/delete-card";
|
||||
import type { LovelaceCardPath } from "../editor/lovelace-path";
|
||||
import { parseLovelaceCardPath } from "../editor/lovelace-path";
|
||||
import { createErrorSectionConfig } from "../sections/hui-error-section";
|
||||
import "../sections/hui-section";
|
||||
import type { HuiSection } from "../sections/hui-section";
|
||||
import { generateLovelaceViewStrategy } from "../strategies/get-strategy";
|
||||
import type { Lovelace } from "../types";
|
||||
import { getViewType } from "./get-view-type";
|
||||
@@ -80,6 +84,8 @@ export class HUIView extends ReactiveElement {
|
||||
|
||||
@state() private _badges: HuiBadge[] = [];
|
||||
|
||||
@state() private _sections: HuiSection[] = [];
|
||||
|
||||
private _layoutElementType?: string;
|
||||
|
||||
private _layoutElement?: LovelaceViewElement;
|
||||
@@ -122,6 +128,28 @@ export class HUIView extends ReactiveElement {
|
||||
return element;
|
||||
}
|
||||
|
||||
// Public to make demo happy
|
||||
public createSectionElement(sectionConfig: LovelaceSectionConfig) {
|
||||
const element = document.createElement("hui-section");
|
||||
element.hass = this.hass;
|
||||
element.lovelace = this.lovelace;
|
||||
element.config = sectionConfig;
|
||||
element.viewIndex = this.index;
|
||||
element.preview = this.lovelace.editMode;
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev: Event) => {
|
||||
// In edit mode let it go to hui-root and rebuild whole view.
|
||||
if (!this.lovelace!.editMode) {
|
||||
ev.stopPropagation();
|
||||
this._rebuildSection(element, sectionConfig);
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@@ -233,6 +261,14 @@ export class HUIView extends ReactiveElement {
|
||||
element.hass = this.hass;
|
||||
});
|
||||
|
||||
this._sections.forEach((element) => {
|
||||
try {
|
||||
element.hass = this.hass;
|
||||
} catch (e: any) {
|
||||
this._rebuildSection(element, createErrorSectionConfig(e.message));
|
||||
}
|
||||
});
|
||||
|
||||
this._layoutElement.hass = this.hass;
|
||||
}
|
||||
if (changedProperties.has("narrow")) {
|
||||
@@ -240,6 +276,15 @@ export class HUIView extends ReactiveElement {
|
||||
}
|
||||
if (changedProperties.has("lovelace")) {
|
||||
this._layoutElement.lovelace = this.lovelace;
|
||||
this._sections.forEach((element) => {
|
||||
try {
|
||||
element.hass = this.hass;
|
||||
element.lovelace = this.lovelace;
|
||||
element.preview = this.lovelace.editMode;
|
||||
} catch (e: any) {
|
||||
this._rebuildSection(element, createErrorSectionConfig(e.message));
|
||||
}
|
||||
});
|
||||
this._cards.forEach((element) => {
|
||||
element.preview = this.lovelace.editMode;
|
||||
});
|
||||
@@ -290,6 +335,7 @@ export class HUIView extends ReactiveElement {
|
||||
this._layoutElementConfig = viewConfig;
|
||||
this._createBadges(viewConfig);
|
||||
this._createCards(viewConfig);
|
||||
this._createSections(viewConfig);
|
||||
this._layoutElement!.isStrategy = isStrategy;
|
||||
this._layoutElement!.hass = this.hass;
|
||||
this._layoutElement!.narrow = this.narrow;
|
||||
@@ -297,6 +343,7 @@ export class HUIView extends ReactiveElement {
|
||||
this._layoutElement!.index = this.index;
|
||||
this._layoutElement!.cards = this._cards;
|
||||
this._layoutElement!.badges = this._badges;
|
||||
this._layoutElement!.sections = this._sections;
|
||||
|
||||
if (addLayoutElement) {
|
||||
while (this.lastChild) {
|
||||
@@ -427,6 +474,36 @@ export class HUIView extends ReactiveElement {
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
private _createSections(config: LovelaceViewConfig): void {
|
||||
if (!config || !config.sections || !Array.isArray(config.sections)) {
|
||||
this._sections = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this._sections = config.sections.map((sectionConfig, index) => {
|
||||
const element = this.createSectionElement(sectionConfig);
|
||||
element.index = index;
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
private _rebuildSection(
|
||||
sectionElToReplace: HuiSection,
|
||||
config: LovelaceSectionConfig
|
||||
): void {
|
||||
const newSectionEl = this.createSectionElement(config);
|
||||
newSectionEl.index = sectionElToReplace.index;
|
||||
if (sectionElToReplace.parentElement) {
|
||||
sectionElToReplace.parentElement!.replaceChild(
|
||||
newSectionEl,
|
||||
sectionElToReplace
|
||||
);
|
||||
}
|
||||
this._sections = this._sections!.map((curSectionEl) =>
|
||||
curSectionEl === sectionElToReplace ? newSectionEl : curSectionEl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -3,7 +3,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import "../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../components/ha-control-select";
|
||||
@@ -71,7 +71,7 @@ export class HaStateControlAlarmControlPanelModes extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
|
||||
const modes = this._modes(this.stateObj);
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateActive } from "../../common/entity/state_active";
|
||||
import { domainStateColorProperties } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
@@ -15,7 +15,6 @@ import "../../components/ha-svg-icon";
|
||||
import type { ClimateEntity } from "../../data/climate";
|
||||
import { ClimateEntityFeature } from "../../data/climate";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import { computeCssVariable } from "../../resources/css-variables";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
createStateControlCircularSliderController,
|
||||
@@ -172,14 +171,7 @@ export class HaStateControlClimateHumidity extends LitElement {
|
||||
const active = stateActive(this.stateObj);
|
||||
|
||||
// Use humidifier state color
|
||||
const stateColor = computeCssVariable(
|
||||
domainStateColorProperties(
|
||||
"humidifier",
|
||||
this.stateObj,
|
||||
active ? "on" : "off"
|
||||
)
|
||||
);
|
||||
|
||||
const color = stateColor(this, this.stateObj);
|
||||
const targetHumidity = this._targetHumidity;
|
||||
const currentHumidity = this.stateObj.attributes.current_humidity;
|
||||
|
||||
@@ -196,7 +188,7 @@ export class HaStateControlClimateHumidity extends LitElement {
|
||||
<div
|
||||
class="container${containerSizeClass}"
|
||||
style=${styleMap({
|
||||
"--state-color": stateColor,
|
||||
"--state-color": color,
|
||||
})}
|
||||
>
|
||||
<ha-control-circular-slider
|
||||
|
@@ -6,7 +6,6 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { UNIT_F } from "../../common/const";
|
||||
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";
|
||||
import { formatNumber } from "../../common/number/format_number";
|
||||
@@ -28,6 +27,7 @@ import {
|
||||
createStateControlCircularSliderController,
|
||||
stateControlCircularSliderStyle,
|
||||
} from "../state-control-circular-slider-style";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
|
||||
type Target = "value" | "low" | "high";
|
||||
|
||||
@@ -189,8 +189,8 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
}
|
||||
|
||||
private _renderTemperatureButtons(target: Target, colored?: boolean) {
|
||||
const lowColor = stateColorCss(this.stateObj, "heat");
|
||||
const highColor = stateColorCss(this.stateObj, "cool");
|
||||
const lowColor = stateColor(this, this.stateObj, "heat");
|
||||
const highColor = stateColor(this, this.stateObj, "cool");
|
||||
|
||||
const color =
|
||||
colored && stateActive(this.stateObj)
|
||||
@@ -414,13 +414,14 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
const action = this.stateObj.attributes.hvac_action;
|
||||
const active = stateActive(this.stateObj);
|
||||
|
||||
const stateColor = stateColorCss(this.stateObj);
|
||||
const lowColor = stateColorCss(this.stateObj, active ? "heat" : "off");
|
||||
const highColor = stateColorCss(this.stateObj, active ? "cool" : "off");
|
||||
const color = stateColor(this, this.stateObj);
|
||||
const lowColor = stateColor(this, this.stateObj, active ? "heat" : "off");
|
||||
const highColor = stateColor(this, this.stateObj, active ? "cool" : "off");
|
||||
|
||||
let actionColor: string | undefined;
|
||||
if (action && action !== "idle" && action !== "off" && active) {
|
||||
actionColor = stateColorCss(
|
||||
actionColor = stateColor(
|
||||
this,
|
||||
this.stateObj,
|
||||
CLIMATE_HVAC_ACTION_TO_MODE[action]
|
||||
);
|
||||
@@ -448,7 +449,7 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
<div
|
||||
class="container${containerSizeClass}"
|
||||
style=${styleMap({
|
||||
"--state-color": stateColor,
|
||||
"--state-color": color,
|
||||
"--action-color": actionColor,
|
||||
})}
|
||||
>
|
||||
|
@@ -3,7 +3,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-slider";
|
||||
import type { CoverEntity } from "../../data/cover";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
@@ -37,8 +37,8 @@ export class HaStateControlCoverPosition extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const openColor = stateColorCss(this.stateObj, "open");
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const openColor = stateColor(this, this.stateObj, "open");
|
||||
const color = stateColor(this, this.stateObj);
|
||||
|
||||
return html`
|
||||
<ha-control-slider
|
||||
|
@@ -3,7 +3,7 @@ import { css, html, LitElement, unsafeCSS } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-slider";
|
||||
import type { CoverEntity } from "../../data/cover";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
@@ -67,8 +67,8 @@ export class HaStateControlInfoCoverTiltPosition extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const openColor = stateColorCss(this.stateObj, "open");
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const openColor = stateColor(this, this.stateObj, "open");
|
||||
const color = stateColor(this, this.stateObj);
|
||||
|
||||
return html`
|
||||
<ha-control-slider
|
||||
|
@@ -4,7 +4,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-button";
|
||||
import "../../components/ha-control-switch";
|
||||
import "../../components/ha-state-icon";
|
||||
@@ -52,8 +52,8 @@ export class HaStateControlCoverToggle extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const onColor = stateColorCss(this.stateObj, "open");
|
||||
const offColor = stateColorCss(this.stateObj, "closed");
|
||||
const onColor = stateColor(this, this.stateObj, "open");
|
||||
const offColor = stateColor(this, this.stateObj, "closed");
|
||||
|
||||
const isOn =
|
||||
this.stateObj.state === "open" ||
|
||||
|
@@ -3,7 +3,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
|
||||
import { stateActive } from "../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../components/ha-control-select";
|
||||
import "../../components/ha-control-slider";
|
||||
@@ -73,7 +73,7 @@ export class HaStateControlFanSpeed extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
|
||||
const speedCount = computeFanSpeedCount(this.stateObj);
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import { stateColorCss } from "../common/entity/state_color";
|
||||
import { stateColor } from "../common/entity/state_color";
|
||||
import "../components/ha-control-button";
|
||||
import "../components/ha-control-switch";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../data/entity";
|
||||
@@ -65,8 +65,8 @@ export class HaStateControlToggle extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const onColor = stateColorCss(this.stateObj, "on");
|
||||
const offColor = stateColorCss(this.stateObj, "off");
|
||||
const onColor = stateColor(this, this.stateObj, "on");
|
||||
const offColor = stateColor(this, this.stateObj, "off");
|
||||
|
||||
const isOn = this.stateObj.state === "on";
|
||||
const isOff = this.stateObj.state === "off";
|
||||
|
@@ -4,7 +4,7 @@ import { LitElement, html, nothing } 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 { stateColor } from "../../common/entity/state_color";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import "../../components/ha-big-number";
|
||||
@@ -248,14 +248,15 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const stateColor = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
const active = stateActive(this.stateObj);
|
||||
|
||||
const action = this.stateObj.attributes.action;
|
||||
|
||||
let actionColor: string | undefined;
|
||||
if (action && action !== "idle" && action !== "off" && active) {
|
||||
actionColor = stateColorCss(
|
||||
actionColor = stateColor(
|
||||
this,
|
||||
this.stateObj,
|
||||
HUMIDIFIER_ACTION_MODE[action]
|
||||
);
|
||||
@@ -277,7 +278,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
<div
|
||||
class="container${containerSizeClass}"
|
||||
style=${styleMap({
|
||||
"--state-color": stateColor,
|
||||
"--state-color": color,
|
||||
"--action-color": actionColor,
|
||||
})}
|
||||
>
|
||||
@@ -303,7 +304,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
<div
|
||||
class="container${containerSizeClass}"
|
||||
style=${styleMap({
|
||||
"--state-color": stateColor,
|
||||
"--state-color": color,
|
||||
"--action-color": actionColor,
|
||||
})}
|
||||
>
|
||||
|
@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../common/color/convert-color";
|
||||
import { stateActive } from "../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-slider";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import type { LightEntity } from "../../data/light";
|
||||
@@ -41,7 +41,7 @@ export class HaStateControlLightBrightness extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let color = stateColorCss(this.stateObj);
|
||||
let color = stateColor(this, this.stateObj);
|
||||
|
||||
if (this.stateObj.attributes.rgb_color) {
|
||||
const hsvColor = rgb2hsv(this.stateObj.attributes.rgb_color);
|
||||
|
@@ -3,7 +3,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-button";
|
||||
import "../../components/ha-control-switch";
|
||||
import "../../components/ha-state-icon";
|
||||
@@ -82,7 +82,7 @@ export class HaStateControlLockToggle extends LitElement {
|
||||
const locking = this.stateObj.state === "locking";
|
||||
const unlocking = this.stateObj.state === "unlocking";
|
||||
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
|
||||
if (this.stateObj.state === UNKNOWN) {
|
||||
return html`
|
||||
|
@@ -3,7 +3,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-slider";
|
||||
import type { CoverEntity } from "../../data/cover";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
@@ -37,7 +37,7 @@ export class HaStateControlValvePosition extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
|
||||
return html`
|
||||
<ha-control-slider
|
||||
|
@@ -4,7 +4,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-button";
|
||||
import "../../components/ha-control-switch";
|
||||
import "../../components/ha-state-icon";
|
||||
@@ -52,8 +52,8 @@ export class HaStateControlValveToggle extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const onColor = stateColorCss(this.stateObj, "open");
|
||||
const offColor = stateColorCss(this.stateObj, "closed");
|
||||
const onColor = stateColor(this, this.stateObj, "open");
|
||||
const offColor = stateColor(this, this.stateObj, "closed");
|
||||
|
||||
const isOn =
|
||||
this.stateObj.state === "open" ||
|
||||
|
@@ -5,7 +5,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { UNIT_F } from "../../common/const";
|
||||
import { stateActive } from "../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
@@ -179,7 +179,7 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
);
|
||||
|
||||
const stateColor = stateColorCss(this.stateObj);
|
||||
const color = stateColor(this, this.stateObj);
|
||||
const active = stateActive(this.stateObj);
|
||||
|
||||
const containerSizeClass = this._sizeController.value
|
||||
@@ -195,7 +195,7 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
<div
|
||||
class="container${containerSizeClass}"
|
||||
style=${styleMap({
|
||||
"--state-color": stateColor,
|
||||
"--state-color": color,
|
||||
})}
|
||||
>
|
||||
<ha-control-circular-slider
|
||||
@@ -226,7 +226,7 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
<div
|
||||
class="container${containerSizeClass}"
|
||||
style=${styleMap({
|
||||
"--state-color": stateColor,
|
||||
"--state-color": color,
|
||||
})}
|
||||
>
|
||||
<ha-control-circular-slider
|
||||
|
@@ -7059,9 +7059,9 @@
|
||||
"add": "Add to Home Assistant",
|
||||
"add_device": "Add device",
|
||||
"create_automation": "Create automation",
|
||||
"add_area": "Add area",
|
||||
"add_area_success": "Area added",
|
||||
"add_area_action": "View area",
|
||||
"create_area": "Create area",
|
||||
"create_area_success": "Area created",
|
||||
"create_area_action": "View area",
|
||||
"add_person_success": "Person added",
|
||||
"add_person_action": "View persons",
|
||||
"add_person": "Add person"
|
||||
|
@@ -1,31 +1,31 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { batteryStateColorProperty } from "../../../../src/common/entity/color/battery_color";
|
||||
import { batteryStateColor } from "../../../../src/common/entity/color/battery_color";
|
||||
|
||||
describe("battery_color", () => {
|
||||
it("should return green for high battery level", () => {
|
||||
let color = batteryStateColorProperty("70");
|
||||
let color = batteryStateColor("70");
|
||||
expect(color).toBe("--state-sensor-battery-high-color");
|
||||
color = batteryStateColorProperty("200");
|
||||
color = batteryStateColor("200");
|
||||
expect(color).toBe("--state-sensor-battery-high-color");
|
||||
});
|
||||
|
||||
it("should return yellow for medium battery level", () => {
|
||||
let color = batteryStateColorProperty("69.99");
|
||||
let color = batteryStateColor("69.99");
|
||||
expect(color).toBe("--state-sensor-battery-medium-color");
|
||||
color = batteryStateColorProperty("30");
|
||||
color = batteryStateColor("30");
|
||||
expect(color).toBe("--state-sensor-battery-medium-color");
|
||||
});
|
||||
|
||||
it("should return red for low battery level", () => {
|
||||
let color = batteryStateColorProperty("29.999");
|
||||
let color = batteryStateColor("29.999");
|
||||
expect(color).toBe("--state-sensor-battery-low-color");
|
||||
color = batteryStateColorProperty("-20");
|
||||
color = batteryStateColor("-20");
|
||||
expect(color).toBe("--state-sensor-battery-low-color");
|
||||
});
|
||||
|
||||
// add nan test
|
||||
it("should return undefined for non-numeric state", () => {
|
||||
const color = batteryStateColorProperty("not a number");
|
||||
const color = batteryStateColor("not a number");
|
||||
expect(color).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
32
yarn.lock
32
yarn.lock
@@ -4752,16 +4752,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node-fetch@npm:^2.6.12":
|
||||
version: 2.6.13
|
||||
resolution: "@types/node-fetch@npm:2.6.13"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
form-data: "npm:^4.0.4"
|
||||
checksum: 10/944d52214791ebba482ca1393a4f0d62b0dbac5f7343ff42c128b75d5356d8bcefd4df77771b55c1acd19d118e16e9bd5d2792819c51bc13402d1c87c0975435
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node-forge@npm:^1.3.0":
|
||||
version: 1.3.14
|
||||
resolution: "@types/node-forge@npm:1.3.14"
|
||||
@@ -9467,7 +9457,7 @@ __metadata:
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:5.9.2"
|
||||
typescript-eslint: "npm:8.43.0"
|
||||
ua-parser-js: "npm:2.0.4"
|
||||
ua-parser-js: "npm:2.0.5"
|
||||
vite-tsconfig-paths: "npm:5.1.4"
|
||||
vitest: "npm:3.2.4"
|
||||
vue: "npm:2.7.16"
|
||||
@@ -11510,7 +11500,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.1, node-fetch@npm:^2.7.0":
|
||||
"node-fetch@npm:^2.6.1":
|
||||
version: 2.7.0
|
||||
resolution: "node-fetch@npm:2.7.0"
|
||||
dependencies:
|
||||
@@ -14560,18 +14550,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ua-parser-js@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "ua-parser-js@npm:2.0.4"
|
||||
"ua-parser-js@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "ua-parser-js@npm:2.0.5"
|
||||
dependencies:
|
||||
"@types/node-fetch": "npm:^2.6.12"
|
||||
detect-europe-js: "npm:^0.1.2"
|
||||
is-standalone-pwa: "npm:^0.1.1"
|
||||
node-fetch: "npm:^2.7.0"
|
||||
ua-is-frozen: "npm:^0.1.2"
|
||||
undici: "npm:^7.12.0"
|
||||
bin:
|
||||
ua-parser-js: script/cli.js
|
||||
checksum: 10/eb3a57cd4aea6c42d2d766761ccf38cdc4576075646dec611efc336f0d1e640896ec4ca084142a1fedbf25c589e093e2cad50c49a22d089e234029ecb9b8d2e4
|
||||
checksum: 10/e946cb1c85bfcd0f2d30c7d5e1b605e340bb458432e7e87fc4aa1b2f90117e4220521d4e0bc7dd8c2a5cadd0935dedb5ac434b70efdc0007221288c1d98b3cd5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14634,6 +14623,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:^7.12.0":
|
||||
version: 7.16.0
|
||||
resolution: "undici@npm:7.16.0"
|
||||
checksum: 10/2bb71672b23d3dc0f56f1b7fb6c936e4487a350db46eaafc03f2f9107f99cdf8e51ecdd32e589e2381ef47a64b6369cfb31f328b2c3ea663023aa47bc5258b9e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unicode-canonical-property-names-ecmascript@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1"
|
||||
|
Reference in New Issue
Block a user