fix: Update components and styles to work with >= HA 2026.2 (#2333)

Caution: Requires HA >= `2026.2`

- Closes #2329
- Closes #2332
This commit is contained in:
Dermot Duffy
2026-02-07 20:32:05 -08:00
committed by GitHub
parent 6eb0a87ca8
commit 49073fee48
17 changed files with 827 additions and 62 deletions
@@ -79,6 +79,7 @@ elements:
| `selected` | `false` | Whether or not to show this item as selected. |
| `state_color` | `true` | Whether or not the title and icon should be stylized based on state. |
| `style` | | Position and style the element using CSS. |
| `subtitle` | | Optional additional text to display. |
| `tap_action`, `double_tap_action`, `hold_action`, `start_tap`, `end_tap` | | The [actions](../../actions/README.md) to take when this item is interacted with. |
| `title` | | An optional title to display. |
+5 -1
View File
@@ -39,8 +39,12 @@ export class FrigateEventWatcher implements FrigateEventWatcherSubscriptionInter
);
if (!this._hasSubscribers(request.instanceID)) {
await this._unsubscribeCallback[request.instanceID]();
const callback = this._unsubscribeCallback[request.instanceID];
delete this._unsubscribeCallback[request.instanceID];
// Callback may be undefined if unsubscribe is called while subscribe is
// still awaiting the Home Assistant connection.
await callback?.();
}
}
+7 -8
View File
@@ -7,7 +7,6 @@ import {
unsafeCSS,
} from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { KeyAssignerController } from '../components-lib/key-assigner-controller';
import { KeyboardShortcut } from '../config/schema/view';
import { localize } from '../localize/localize';
@@ -44,21 +43,22 @@ export class AdvancedCameraCardKeyAssigner extends LitElement {
return html`
<div class="label">${this.label}</div>
<ha-button
title="${localize('key_assigner.assign')}"
aria-label="${localize('key_assigner.assign')}"
class="assign"
@click=${() => {
this._controller.toggleAssigning();
}}
>
<advanced-camera-card-icon .icon=${{ icon: 'mdi:keyboard-settings' }}></advanced-camera-card-icon>
<span class="${classMap({
dotdotdot: this._controller.isAssigning(),
})}">
${this._controller.isAssigning() ? '' : localize('key_assigner.assign')}
</span>
<advanced-camera-card-icon
.icon=${{ icon: 'mdi:keyboard-settings' }}>
</advanced-camera-card-icon>
</ha-button>
${
this._controller.hasValue()
? html`<ha-button
title="${localize('key_assigner.unassign')}"
aria-label="${localize('key_assigner.unassign')}"
@click=${() => {
this._controller.setValue(null);
}}
@@ -66,7 +66,6 @@ export class AdvancedCameraCardKeyAssigner extends LitElement {
<advanced-camera-card-icon
.icon=${{ icon: 'mdi:keyboard-off' }}
></advanced-camera-card-icon>
<span> ${localize('key_assigner.unassign')} </span>
</ha-button>`
: ''
}
+12 -23
View File
@@ -1,6 +1,5 @@
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { styleMap } from 'lit/directives/style-map.js';
import { actionHandler } from '../../action-handler-directive.js';
import { getEntityTitle } from '../../ha/get-entity-title.js';
@@ -28,20 +27,20 @@ export class AdvancedCameraCardSubmenu extends LitElement {
const title = item.title ?? getEntityTitle(this.hass, item.entity);
const style = styleMap(item.style || {});
const disabled = item.enabled === false;
return html`
<mwc-list-item
graphic=${ifDefined(item.icon || item.entity ? 'icon' : undefined)}
?twoline=${!!item.subtitle}
?selected=${item.selected}
?activated=${item.selected}
?disabled=${item.enabled === false}
<ha-dropdown-item
class=${item.selected ? 'selected' : ''}
?disabled=${disabled}
aria-label="${title ?? ''}"
@action=${(ev: CustomEvent<SubmenuInteraction>) => {
// Attach the item so ascendants have access to it.
ev.detail.item = item;
}}
.actionHandler=${actionHandler({
.actionHandler=${disabled
? undefined
: actionHandler({
allowPropagation: true,
hasHold: hasAction(item.hold_action),
hasDoubleClick: hasAction(item.double_tap_action),
@@ -49,10 +48,10 @@ export class AdvancedCameraCardSubmenu extends LitElement {
>
<span style="${style}">${title ?? ''}</span>
${item.subtitle
? html`<span slot="secondary" style="${style}">${item.subtitle}</span>`
? html`<span slot="details" style="${style}">${item.subtitle}</span>`
: ''}
<advanced-camera-card-icon
slot="graphic"
slot="icon"
.hass=${this.hass}
.icon=${{
icon: item.icon,
@@ -60,26 +59,16 @@ export class AdvancedCameraCardSubmenu extends LitElement {
}}
style="${style}"
></advanced-camera-card-icon>
</mwc-list-item>
</ha-dropdown-item>
`;
}
protected render(): TemplateResult {
return html`
<ha-button-menu
fixed
corner=${'BOTTOM_LEFT'}
@closed=${
// Prevent the submenu closing from closing anything upstream (e.g.
// selecting a submenu in the editor dialog should not close the
// editor, see https://github.com/dermotduffy/advanced-camera-card/issues/377).
(ev) => ev.stopPropagation()
}
@click=${(ev: Event) => stopEventFromActivatingCardWideActions(ev)}
>
<ha-dropdown @click=${(ev: Event) => stopEventFromActivatingCardWideActions(ev)}>
<slot slot="trigger"></slot>
${this.items?.map(this._renderItem.bind(this))}
</ha-button-menu>
</ha-dropdown>
`;
}
-4
View File
@@ -35,10 +35,6 @@ export class AdvancedCameraCardSubmenuButton extends LitElement {
.hass=${this.hass}
.icon=${{ icon: this.submenu.icon }}
.actionHandler=${actionHandler({
// Need to allow event to propagate upwards, as it's caught by the
// <ha-button-menu> trigger slot to open/close the menu. Further
// propagation is forbidden by the @click handler on
// <ha-button-menu>.
allowPropagation: true,
hasHold: hasAction(this.submenu.hold_action),
hasDoubleClick: hasAction(this.submenu.double_tap_action),
+2 -1
View File
@@ -8,11 +8,12 @@ import { CardHelpers, LovelaceCardWithEditor } from '../types';
*/
export const sideLoadHomeAssistantElements = async (): Promise<boolean> => {
const neededElements = [
'ha-button-menu',
'ha-button',
'ha-camera-stream',
'ha-card',
'ha-combo-box',
'ha-dropdown-item',
'ha-dropdown',
'ha-hls-player',
'ha-icon-button',
'ha-icon',
+2
View File
@@ -28,6 +28,8 @@
.values {
background: var(--secondary-background-color);
display: grid;
grid-template-columns: minmax(0, 1fr);
}
// Space submenus from the next top-level options.
.values + .option,
+11 -2
View File
@@ -1,5 +1,3 @@
@use 'dotdotdot.scss';
:host {
display: flex;
flex-direction: row;
@@ -13,8 +11,19 @@
:host([assigning]) ha-button.assign span,
:host([assigning]) ha-button.assign advanced-camera-card-icon {
animation: pulse 0.5s infinite;
@keyframes pulse {
50% {
color: var(--warning-color);
}
}
}
ha-button {
padding-left: 4px;
padding-right: 4px;
}
advanced-camera-card-icon {
padding: 10px;
+18 -2
View File
@@ -1,8 +1,24 @@
@use './menu-button.scss';
@import './z-index.scss';
:host {
// Without this the submenu (in the card menu) cannot be interacted with (e.g.
// hover states don't work).
pointer-events: auto;
}
--mdc-menu-z-index: #{$z-index-submenu};
ha-dropdown-item {
color: var(--advanced-camera-card-submenu-color);
background-color: var(--advanced-camera-card-submenu-background-color);
}
ha-dropdown-item:hover {
color: var(--advanced-camera-card-submenu-hover-color);
background-color: var(--advanced-camera-card-submenu-hover-background-color);
}
ha-dropdown-item.selected:not(:hover) {
font-weight: var(--ha-font-weight-medium);
color: var(--advanced-camera-card-submenu-selected-color);
background-color: var(--advanced-camera-card-submenu-selected-background-color);
}
+18
View File
@@ -98,6 +98,24 @@
// --advanced-camera-card-menu-position-left-background: purple;
// --advanced-camera-card-menu-style-overlay-background: pink;
/*********
* Submenu
*********/
--advanced-camera-card-submenu-color: var(--advanced-camera-card-text-color);
--advanced-camera-card-submenu-background-color: var(
--advanced-camera-card-background
);
--advanced-camera-card-submenu-hover-color: var(--advanced-camera-card-text-color);
--advanced-camera-card-submenu-hover-background-color: var(
--ha-color-fill-neutral-normal-resting
);
--advanced-camera-card-submenu-selected-color: var(
--advanced-camera-card-foreground-primary
);
--advanced-camera-card-submenu-selected-background-color: var(
--ha-color-fill-primary-quiet-resting
);
/************
* Status bar
************/
+197 -5
View File
@@ -1,12 +1,204 @@
:host([themes~='dark']) {
// This file should include only, and exactly match, values from
// https://github.com/home-assistant/frontend/blob/dev/src/resources/styles-data.ts .
--sidebar-text-color: var(--primary-text-color);
--sidebar-background-color: var(--card-background-color);
--app-header-text-color: var(--sidebar-text-color);
--app-header-background-color: var(--sidebar-background-color);
--app-header-border-bottom: 1px solid var(--divider-color);
--card-background-color: #1c1c1c;
// =====================
// Core global color styles
// https://github.com/home-assistant/frontend/blob/dev/src/resources/theme/color/color.globals.ts
// =====================
--primary-background-color: #111111;
--card-background-color: #1c1c1c;
--secondary-background-color: #282828;
--clear-background-color: #111111;
--primary-text-color: #e1e1e1;
--secondary-text-color: #9b9b9b;
--disabled-text-color: #6f6f6f;
--switch-unchecked-button-color: #999999;
--switch-unchecked-track-color: #9b9b9b;
--divider-color: rgba(225, 225, 225, 0.12);
--app-header-background-color: #101e24;
--app-header-text-color: #e1e1e1;
--outline-color: rgba(225, 225, 225, 0.12);
--outline-hover-color: rgba(225, 225, 225, 0.24);
--shadow-color: rgba(0, 0, 0, 0.48);
--mdc-ripple-color: #aaaaaa;
--mdc-linear-progress-buffer-color: rgba(255, 255, 255, 0.1);
--input-idle-line-color: rgba(255, 255, 255, 0.42);
--input-hover-line-color: rgba(255, 255, 255, 0.87);
--input-disabled-line-color: rgba(255, 255, 255, 0.06);
--input-outlined-idle-border-color: rgba(255, 255, 255, 0.38);
--input-outlined-hover-border-color: rgba(255, 255, 255, 0.87);
--input-outlined-disabled-border-color: rgba(255, 255, 255, 0.06);
--input-fill-color: rgba(255, 255, 255, 0.05);
--input-disabled-fill-color: rgba(255, 255, 255, 0.02);
--input-ink-color: rgba(255, 255, 255, 0.87);
--input-label-ink-color: rgba(255, 255, 255, 0.6);
--input-disabled-ink-color: rgba(255, 255, 255, 0.37);
--input-dropdown-icon-color: rgba(255, 255, 255, 0.54);
--codemirror-keyword: #c792ea;
--codemirror-operator: #89ddff;
--codemirror-variable: #f07178;
--codemirror-variable-2: #eeffff;
--codemirror-variable-3: #decb6b;
--codemirror-builtin: #ffcb6b;
--codemirror-atom: #f78c6c;
--codemirror-number: #ff5370;
--codemirror-def: #82aaff;
--codemirror-string: #c3e88d;
--codemirror-string-2: #f07178;
--codemirror-comment: #545454;
--codemirror-tag: #ff5370;
--codemirror-meta: #ffcb6b;
--codemirror-attribute: #c792ea;
--codemirror-property: #c792ea;
--codemirror-qualifier: #decb6b;
--codemirror-type: #decb6b;
--energy-grid-return-color: #a280db;
--map-filter: invert(0.9) hue-rotate(170deg) brightness(1.5) contrast(1.2)
saturate(0.3);
--disabled-color: #464646;
--ha-button-primary-light-color: #4082a040;
--ha-button-warning-light-color: #917b54c1;
--ha-button-neutral-color: #d9dae0;
--ha-button-neutral-light-color: #6a7081;
--bar-box-shadow: 0 2px 12px var(--shadow-color);
// =====================
// Semantic color styles
// https://github.com/home-assistant/frontend/blob/dev/src/resources/theme/color/semantic.globals.ts
// =====================
/* text */
--ha-color-text-primary: var(--white-color);
--ha-color-text-secondary: var(--ha-color-neutral-80);
--ha-color-text-link: var(--ha-color-primary-60);
/* border primary */
--ha-color-border-normal: var(--ha-color-primary-50);
/* border neutral */
--ha-color-border-neutral-quiet: var(--ha-color-neutral-40);
--ha-color-border-neutral-normal: var(--ha-color-neutral-50);
--ha-color-border-neutral-loud: var(--ha-color-neutral-70);
/* border danger */
--ha-color-border-danger-normal: var(--ha-color-red-50);
--ha-color-border-danger-loud: var(--ha-color-red-50);
/* border warning */
--ha-color-border-warning-normal: var(--ha-color-orange-50);
--ha-color-border-warning-loud: var(--ha-color-orange-50);
/* fill primary quiet */
--ha-color-fill-primary-quiet-resting: var(--ha-color-primary-05);
--ha-color-fill-primary-quiet-hover: var(--ha-color-primary-10);
--ha-color-fill-primary-quiet-active: var(--ha-color-primary-05);
/* fill primary normal */
--ha-color-fill-primary-normal-resting: var(--ha-color-primary-10);
--ha-color-fill-primary-normal-hover: var(--ha-color-primary-20);
--ha-color-fill-primary-normal-active: var(--ha-color-primary-10);
/* fill neutral quiet */
--ha-color-fill-neutral-quiet-resting: var(--ha-color-neutral-05);
--ha-color-fill-neutral-quiet-hover: var(--ha-color-neutral-10);
--ha-color-fill-neutral-quiet-active: var(--ha-color-neutral-00);
/* fill neutral normal */
--ha-color-fill-neutral-normal-resting: var(--ha-color-neutral-10);
--ha-color-fill-neutral-normal-hover: var(--ha-color-neutral-20);
--ha-color-fill-neutral-normal-active: var(--ha-color-neutral-10);
/* fill disabled quiet */
--ha-color-fill-disabled-quiet-resting: var(--ha-color-neutral-10);
/* fill disabled normal */
--ha-color-fill-disabled-normal-resting: var(--ha-color-neutral-20);
/* fill disabled loud */
--ha-color-fill-disabled-loud-resting: var(--ha-color-neutral-30);
/* fill danger quiet */
--ha-color-fill-danger-quiet-resting: var(--ha-color-red-05);
--ha-color-fill-danger-quiet-hover: var(--ha-color-red-10);
--ha-color-fill-danger-quiet-active: var(--ha-color-red-05);
/* fill danger normal */
--ha-color-fill-danger-normal-resting: var(--ha-color-red-10);
--ha-color-fill-danger-normal-hover: var(--ha-color-red-20);
--ha-color-fill-danger-normal-active: var(--ha-color-red-10);
/* fill danger loud */
--ha-color-fill-danger-loud-resting: var(--ha-color-red-40);
--ha-color-fill-danger-loud-hover: var(--ha-color-red-30);
--ha-color-fill-danger-loud-active: var(--ha-color-red-40);
/* fill warning quiet */
--ha-color-fill-warning-quiet-resting: var(--ha-color-orange-05);
--ha-color-fill-warning-quiet-hover: var(--ha-color-orange-10);
--ha-color-fill-warning-quiet-active: var(--ha-color-orange-05);
/* fill warning normal */
--ha-color-fill-warning-normal-resting: var(--ha-color-orange-10);
--ha-color-fill-warning-normal-hover: var(--ha-color-orange-20);
--ha-color-fill-warning-normal-active: var(--ha-color-orange-10);
/* fill warning loud */
--ha-color-fill-warning-loud-resting: var(--ha-color-orange-40);
--ha-color-fill-warning-loud-hover: var(--ha-color-orange-30);
--ha-color-fill-warning-loud-active: var(--ha-color-orange-40);
/* fill success quiet */
--ha-color-fill-success-quiet-resting: var(--ha-color-green-05);
--ha-color-fill-success-quiet-hover: var(--ha-color-green-10);
--ha-color-fill-success-quiet-active: var(--ha-color-green-05);
/* fill success normal */
--ha-color-fill-success-normal-resting: var(--ha-color-green-10);
--ha-color-fill-success-normal-hover: var(--ha-color-green-20);
--ha-color-fill-success-normal-active: var(--ha-color-green-10);
/* fill success loud */
--ha-color-fill-success-loud-resting: var(--ha-color-green-40);
--ha-color-fill-success-loud-hover: var(--ha-color-green-30);
--ha-color-fill-success-loud-active: var(--ha-color-green-40);
/* on primary */
--ha-color-on-primary-quiet: var(--ha-color-primary-70);
--ha-color-on-primary-normal: var(--ha-color-primary-60);
/* on neutral */
--ha-color-on-neutral-quiet: var(--ha-color-neutral-70);
--ha-color-on-neutral-normal: var(--ha-color-neutral-60);
--ha-color-on-neutral-loud: var(--white-color);
/* on disabled */
--ha-color-on-disabled-quiet: var(--ha-color-neutral-40);
--ha-color-on-disabled-normal: var(--ha-color-neutral-50);
--ha-color-on-disabled-loud: var(--ha-color-neutral-50);
/* on danger */
--ha-color-on-danger-quiet: var(--ha-color-red-70);
--ha-color-on-danger-normal: var(--ha-color-red-60);
--ha-color-on-danger-loud: var(--white-color);
/* on warning */
--ha-color-on-warning-quiet: var(--ha-color-orange-70);
--ha-color-on-warning-normal: var(--ha-color-orange-60);
--ha-color-on-warning-loud: var(--white-color);
/* on success */
--ha-color-on-success-quiet: var(--ha-color-green-70);
--ha-color-on-success-normal: var(--ha-color-green-60);
--ha-color-on-success-loud: var(--white-color);
/* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-10);
--ha-color-on-surface-default: var(--ha-color-neutral-95);
}
+514 -7
View File
@@ -1,18 +1,525 @@
:host([themes~='light']) {
// This file should include only, and exactly match, values from
// https://github.com/home-assistant/frontend/blob/dev/src/resources/ha-style.ts
// =====================
// Core global color styles
// https://github.com/home-assistant/frontend/blob/dev/src/resources/theme/color/color.globals.ts
// =====================
/* text */
--primary-text-color: var(--ha-color-text-primary);
--secondary-text-color: var(--ha-color-text-secondary);
--text-primary-color: #ffffff;
--text-light-primary-color: #212121;
--disabled-text-color: #bdbdbd;
/* main interface colors */
--primary-color: var(--ha-color-primary-40);
--dark-primary-color: #0288d1;
--darker-primary-color: #016194;
--light-primary-color: #b3e5fc;
--accent-color: #ff9800;
--divider-color: rgba(0, 0, 0, 0.12);
--outline-color: rgba(0, 0, 0, 0.12);
--outline-hover-color: rgba(0, 0, 0, 0.24);
--shadow-color: rgba(0, 0, 0, 0.16);
/* rgb */
--rgb-primary-color: 0, 154, 199;
--rgb-accent-color: 255, 152, 0;
--rgb-primary-text-color: 33, 33, 33;
--rgb-secondary-text-color: 114, 114, 114;
--rgb-text-primary-color: 255, 255, 255;
--rgb-card-background-color: 255, 255, 255;
--rgb-warning-color: 255, 166, 0;
--rgb-error-color: 219, 68, 55;
--rgb-success-color: 67, 160, 71;
--rgb-info-color: 3, 155, 229;
--scrollbar-thumb-color: rgb(194, 194, 194);
--error-color: #db4437;
--warning-color: #ffa600;
--success-color: #43a047;
--info-color: #039be5;
/* backgrounds */
--card-background-color: #ffffff;
--primary-background-color: #fafafa;
--secondary-background-color: #e5e5e5;
--primary-text-color: #212121;
--divider-color: rgba(0, 0, 0, 0.12);
--app-header-text-color: #ffffff;
--secondary-background-color: #e5e5e5; /* behind the cards on state */
--clear-background-color: #ffffff;
/* for label-badge */
--label-badge-grey: #9e9e9e;
/* states icon */
--state-icon-color: #44739e;
/* an error state is anything that would be considered an error */
/* --state-icon-error-color: #db4437; derived from error-color */
/* energy */
--energy-grid-consumption-color: #488fc2;
--energy-grid-return-color: #8353d1;
--energy-solar-color: #ff9800;
--energy-non-fossil-color: #0f9d58;
--energy-battery-out-color: #4db6ac;
--energy-battery-in-color: #f06292;
--energy-gas-color: #8e021b;
--energy-water-color: #00bcd4;
/* color */
--disabled-color: #bdbdbd;
--red-color: #f44336;
--pink-color: #e91e63;
--purple-color: #926bc7;
--deep-purple-color: #6e41ab;
--indigo-color: #3f51b5;
--blue-color: #2196f3;
--light-blue-color: #03a9f4;
--cyan-color: #00bcd4;
--teal-color: #009688;
--green-color: #4caf50;
--light-green-color: #8bc34a;
--lime-color: #cddc39;
--yellow-color: #ffeb3b;
--amber-color: #ffc107;
--orange-color: #ff9800;
--deep-orange-color: #ff6f22;
--brown-color: #795548;
--light-grey-color: #bdbdbd;
--grey-color: #9e9e9e;
--dark-grey-color: #606060;
--blue-grey-color: #607d8b;
--black-color: #000000;
--white-color: #ffffff;
/* colors - used for graphs, calendars, maps, etc */
--color-1: #4269d0;
--color-2: #f4bd4a;
--color-3: #ff725c;
--color-4: #6cc5b0;
--color-5: #a463f2;
--color-6: #ff8ab7;
--color-7: #9c6b4e;
--color-8: #97bbf5;
--color-9: #01ab63;
--color-10: #094bad;
--color-11: #c99000;
--color-12: #d84f3e;
--color-13: #49a28f;
--color-14: #048732;
--color-15: #d96895;
--color-16: #8043ce;
--color-17: #7599d1;
--color-18: #7a4c31;
--color-19: #6989f4;
--color-20: #ffd444;
--color-21: #ff957c;
--color-22: #8fe9d3;
--color-23: #62cc71;
--color-24: #ffadda;
--color-25: #c884ff;
--color-26: #badeff;
--color-27: #bf8b6d;
--color-28: #927acc;
--color-29: #97ee3f;
--color-30: #bf3947;
--color-31: #9f5b00;
--color-32: #f48758;
--color-33: #8caed6;
--color-34: #f2b94f;
--color-35: #eff26e;
--color-36: #e43872;
--color-37: #d9b100;
--color-38: #9d7a00;
--color-39: #698cff;
--color-40: #00d27e;
--color-41: #d06800;
--color-42: #009f82;
--color-43: #c49200;
--color-44: #cbe8ff;
--color-45: #fecddf;
--color-46: #c27eb6;
--color-47: #8cd2ce;
--color-48: #c4b8d9;
--color-49: #f883b0;
--color-50: #a49100;
--color-51: #f48800;
--color-52: #27d0df;
--color-53: #a04a9b;
--color-54: #4269d0;
/* history colors */
--history-unavailable-color: transparent;
/* input components */
--input-idle-line-color: rgba(0, 0, 0, 0.42);
--input-hover-line-color: rgba(0, 0, 0, 0.87);
--input-disabled-line-color: rgba(0, 0, 0, 0.06);
--input-outlined-idle-border-color: rgba(0, 0, 0, 0.38);
--input-outlined-hover-border-color: rgba(0, 0, 0, 0.87);
--input-outlined-disabled-border-color: rgba(0, 0, 0, 0.06);
--input-fill-color: rgb(245, 245, 245);
--input-disabled-fill-color: rgb(250, 250, 250);
--input-ink-color: rgba(0, 0, 0, 0.87);
--input-label-ink-color: rgba(0, 0, 0, 0.6);
--input-disabled-ink-color: rgba(0, 0, 0, 0.37);
--input-dropdown-icon-color: rgba(0, 0, 0, 0.54);
/* for label-badge */
--label-badge-red: var(--error-color);
--label-badge-blue: var(--info-color);
--label-badge-green: var(--success-color);
--label-badge-yellow: var(--warning-color);
/* state color */
--state-active-color: var(--amber-color);
--state-inactive-color: var(--grey-color);
--state-unavailable-color: var(
--state-icon-unavailable-color,
var(--disabled-text-color)
);
/* state domain colors */
--state-alarm_control_panel-armed_away-color: var(--green-color);
--state-alarm_control_panel-armed_custom_bypass-color: var(--green-color);
--state-alarm_control_panel-armed_home-color: var(--green-color);
--state-alarm_control_panel-armed_night-color: var(--green-color);
--state-alarm_control_panel-armed_vacation-color: var(--green-color);
--state-alarm_control_panel-arming-color: var(--orange-color);
--state-alarm_control_panel-disarming-color: var(--orange-color);
--state-alarm_control_panel-pending-color: var(--orange-color);
--state-alarm_control_panel-triggered-color: var(--red-color);
--state-alert-off-color: var(--orange-color);
--state-alert-on-color: var(--red-color);
--state-binary_sensor-active-color: var(--amber-color);
--state-binary_sensor-battery-on-color: var(--red-color);
--state-binary_sensor-carbon_monoxide-on-color: var(--red-color);
--state-binary_sensor-gas-on-color: var(--red-color);
--state-binary_sensor-heat-on-color: var(--red-color);
--state-binary_sensor-lock-on-color: var(--red-color);
--state-binary_sensor-moisture-on-color: var(--red-color);
--state-binary_sensor-problem-on-color: var(--red-color);
--state-binary_sensor-safety-on-color: var(--red-color);
--state-binary_sensor-smoke-on-color: var(--red-color);
--state-binary_sensor-sound-on-color: var(--red-color);
--state-binary_sensor-tamper-on-color: var(--red-color);
--state-climate-auto-color: var(--green-color);
--state-climate-cool-color: var(--blue-color);
--state-climate-dry-color: var(--orange-color);
--state-climate-fan_only-color: var(--cyan-color);
--state-climate-heat-color: var(--deep-orange-color);
--state-climate-heat-cool-color: var(--amber-color);
--state-cover-active-color: var(--purple-color);
--state-device_tracker-active-color: var(--blue-color);
--state-device_tracker-home-color: var(--green-color);
--state-fan-active-color: var(--cyan-color);
--state-humidifier-on-color: var(--blue-color);
--state-lawn_mower-error-color: var(--red-color);
--state-lawn_mower-mowing-color: var(--teal-color);
--state-light-active-color: var(--amber-color);
--state-lock-jammed-color: var(--red-color);
--state-lock-locked-color: var(--green-color);
--state-lock-locking-color: var(--orange-color);
--state-lock-unlocked-color: var(--red-color);
--state-lock-unlocking-color: var(--orange-color);
--state-lock-open-color: var(--red-color);
--state-lock-opening-color: var(--orange-color);
--state-media_player-active-color: var(--light-blue-color);
--state-person-active-color: var(--blue-color);
--state-person-home-color: var(--green-color);
--state-plant-active-color: var(--red-color);
--state-siren-active-color: var(--red-color);
--state-sun-above_horizon-color: var(--amber-color);
--state-sun-below_horizon-color: var(--indigo-color);
--state-switch-active-color: var(--amber-color);
--state-update-active-color: var(--orange-color);
--state-vacuum-active-color: var(--teal-color);
--state-valve-active-color: var(--blue-color);
--state-sensor-battery-high-color: var(--green-color);
--state-sensor-battery-low-color: var(--red-color);
--state-sensor-battery-medium-color: var(--orange-color);
--state-water_heater-eco-color: var(--green-color);
--state-water_heater-electric-color: var(--orange-color);
--state-water_heater-gas-color: var(--orange-color);
--state-water_heater-heat_pump-color: var(--orange-color);
--state-water_heater-high_demand-color: var(--deep-orange-color);
--state-water_heater-performance-color: var(--deep-orange-color);
--state-weather-clear_night-color: var(--deep-purple-color);
--state-weather-cloudy-color: var(--light-grey-color);
--state-weather-exceptional-color: var(--red-color);
--state-weather-fog-color: var(--grey-color);
--state-weather-hail-color: var(--cyan-color);
--state-weather-lightning_rainy-color: var(--lime-color);
--state-weather-lightning-color: var(--yellow-color);
--state-weather-partlycloudy-color: var(--blue-grey-color);
--state-weather-pouring-color: var(--indigo-color);
--state-weather-rainy-color: var(--blue-color);
--state-weather-snowy_rainy-color: var(--light-blue-color);
--state-weather-snowy-color: #c0e0ff;
--state-weather-sunny-color: var(--amber-color);
--state-weather-windy_variant-color: var(--green-color);
--state-weather-windy-color: var(--green-color);
/* history colors */
--history-unknown-color: var(--dark-grey-color);
--state-icon-error-color: var(--error-state-color, var(--error-color));
--sidebar-text-color: var(--primary-text-color);
--sidebar-background-color: var(--card-background-color);
--sidebar-selected-text-color: var(--primary-color);
--sidebar-selected-icon-color: var(--primary-color);
--sidebar-icon-color: rgba(var(--rgb-primary-text-color), 0.6);
--switch-checked-color: var(--primary-color);
--switch-checked-button-color: var(
--switch-checked-color,
var(--primary-background-color)
);
--switch-checked-track-color: var(--switch-checked-color, #000000);
--switch-unchecked-button-color: var(
--switch-unchecked-color,
var(--primary-background-color)
);
--switch-unchecked-track-color: var(--switch-unchecked-color, #000000);
--slider-color: var(--primary-color);
--slider-secondary-color: var(--light-primary-color);
--slider-track-color: var(--scrollbar-thumb-color);
--label-badge-background-color: var(--card-background-color);
--label-badge-text-color: rgba(var(--rgb-primary-text-color), 0.8);
--table-header-background-color: var(--input-fill-color);
--table-row-background-color: var(--primary-background-color);
--table-row-alternative-background-color: var(--secondary-background-color);
--data-table-background-color: var(--card-background-color);
--markdown-code-background-color: var(--primary-background-color);
--bar-box-shadow: 0 2px 12px var(--shadow-color);
/* https://github.com/material-components/material-web/blob/master/docs/theming.md */
--mdc-theme-primary: var(--primary-color);
--mdc-theme-secondary: var(--accent-color);
--mdc-theme-background: var(--primary-background-color);
--mdc-theme-surface: var(--card-background-color);
--mdc-theme-on-primary: var(--text-primary-color);
--mdc-theme-on-secondary: var(--text-primary-color);
--mdc-theme-on-surface: var(--primary-text-color);
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
--mdc-theme-text-primary-on-background: var(--primary-text-color);
--mdc-theme-text-secondary-on-background: var(--secondary-text-color);
--mdc-theme-text-hint-on-background: var(--secondary-text-color);
--mdc-theme-text-icon-on-background: var(--secondary-text-color);
--mdc-theme-error: var(--error-color);
--app-header-text-color: var(--sidebar-text-color);
--app-header-background-color: var(--sidebar-background-color);
--app-header-border-bottom: 1px solid var(--divider-color);
--app-theme-color: var(--app-header-background-color);
--mdc-checkbox-unchecked-color: rgba(var(--rgb-primary-text-color), 0.54);
--mdc-checkbox-disabled-color: var(--disabled-text-color);
--mdc-radio-unchecked-color: rgba(var(--rgb-primary-text-color), 0.54);
--mdc-radio-disabled-color: var(--disabled-text-color);
--mdc-tab-text-label-color-default: var(--primary-text-color);
--mdc-button-disabled-ink-color: var(--disabled-text-color);
--mdc-button-outline-color: var(--outline-color);
--mdc-dialog-scroll-divider-color: var(--divider-color);
--mdc-dialog-heading-ink-color: var(--primary-text-color);
--mdc-dialog-content-ink-color: var(--primary-text-color);
--mdc-top-app-bar-fixed-box-shadow: var(--bar-box-shadow);
--mdc-text-field-idle-line-color: var(--input-idle-line-color);
--mdc-text-field-hover-line-color: var(--input-hover-line-color);
--mdc-text-field-disabled-line-color: var(--input-disabled-line-color);
--mdc-text-field-outlined-idle-border-color: var(--input-outlined-idle-border-color);
--mdc-text-field-outlined-hover-border-color: var(--input-outlined-hover-border-color);
--mdc-text-field-outlined-disabled-border-color: var(
--input-outlined-disabled-border-color
);
--mdc-text-field-fill-color: var(--input-fill-color);
--mdc-text-field-disabled-fill-color: var(--input-disabled-fill-color);
--mdc-text-field-ink-color: var(--input-ink-color);
--mdc-text-field-label-ink-color: var(--input-label-ink-color);
--mdc-text-field-disabled-ink-color: var(--input-disabled-ink-color);
--mdc-select-idle-line-color: var(--input-idle-line-color);
--mdc-select-hover-line-color: var(--input-hover-line-color);
--mdc-select-outlined-idle-border-color: var(--input-outlined-idle-border-color);
--mdc-select-outlined-hover-border-color: var(--input-outlined-hover-border-color);
--mdc-select-outlined-disabled-border-color: var(
--input-outlined-disabled-border-color
);
--mdc-select-fill-color: var(--input-fill-color);
--mdc-select-disabled-fill-color: var(--input-disabled-fill-color);
--mdc-select-ink-color: var(--input-ink-color);
--mdc-select-label-ink-color: var(--input-label-ink-color);
--mdc-select-disabled-ink-color: var(--input-disabled-ink-color);
--mdc-select-dropdown-icon-color: var(--input-dropdown-icon-color);
--mdc-select-disabled-dropdown-icon-color: var(--input-disabled-ink-color);
--ha-assist-chip-filled-container-color: rgba(var(--rgb-primary-text-color), 0.15);
--ha-assist-chip-active-container-color: rgba(var(--rgb-primary-color), 0.15);
--chip-background-color: rgba(var(--rgb-primary-text-color), 0.15);
// =====================
// Semantic color styles
// https://github.com/home-assistant/frontend/blob/dev/src/resources/theme/color/semantic.globals.ts
// =====================
--ha-color-focus: var(--ha-color-orange-60);
/* text */
--ha-color-text-primary: var(--ha-color-neutral-05);
--ha-color-text-secondary: var(--ha-color-neutral-40);
--ha-color-text-disabled: var(--ha-color-neutral-60);
--ha-color-text-link: var(--ha-color-primary-40);
/* border primary */
--ha-color-border-primary-quiet: var(--ha-color-primary-90);
--ha-color-border-primary-normal: var(--ha-color-primary-70);
--ha-color-border-primary-loud: var(--ha-color-primary-40);
/* border neutral */
--ha-color-border-neutral-quiet: var(--ha-color-neutral-90);
--ha-color-border-neutral-normal: var(--ha-color-neutral-60);
--ha-color-border-neutral-loud: var(--ha-color-neutral-40);
/* border danger */
--ha-color-border-danger-quiet: var(--ha-color-red-90);
--ha-color-border-danger-normal: var(--ha-color-red-70);
--ha-color-border-danger-loud: var(--ha-color-red-40);
/* border warning */
--ha-color-border-warning-quiet: var(--ha-color-orange-90);
--ha-color-border-warning-normal: var(--ha-color-orange-70);
--ha-color-border-warning-loud: var(--ha-color-orange-40);
/* border success */
--ha-color-border-success-quiet: var(--ha-color-green-90);
--ha-color-border-success-normal: var(--ha-color-green-70);
--ha-color-border-success-loud: var(--ha-color-green-40);
/* fill primary quiet */
--ha-color-fill-primary-quiet-resting: var(--ha-color-primary-95);
--ha-color-fill-primary-quiet-hover: var(--ha-color-primary-90);
--ha-color-fill-primary-quiet-active: var(--ha-color-primary-95);
/* fill primary normal */
--ha-color-fill-primary-normal-resting: var(--ha-color-primary-90);
--ha-color-fill-primary-normal-hover: var(--ha-color-primary-80);
--ha-color-fill-primary-normal-active: var(--ha-color-primary-90);
/* fill primary loud */
--ha-color-fill-primary-loud-resting: var(--ha-color-primary-40);
--ha-color-fill-primary-loud-hover: var(--ha-color-primary-30);
--ha-color-fill-primary-loud-active: var(--ha-color-primary-40);
/* fill neutral quiet */
--ha-color-fill-neutral-quiet-resting: var(--ha-color-neutral-95);
--ha-color-fill-neutral-quiet-hover: var(--ha-color-neutral-90);
--ha-color-fill-neutral-quiet-active: var(--ha-color-neutral-95);
/* fill neutral normal */
--ha-color-fill-neutral-normal-resting: var(--ha-color-neutral-90);
--ha-color-fill-neutral-normal-hover: var(--ha-color-neutral-80);
--ha-color-fill-neutral-normal-active: var(--ha-color-neutral-90);
/* fill neutral loud */
--ha-color-fill-neutral-loud-resting: var(--ha-color-neutral-40);
--ha-color-fill-neutral-loud-hover: var(--ha-color-neutral-30);
--ha-color-fill-neutral-loud-active: var(--ha-color-neutral-40);
/* fill disabled quiet */
--ha-color-fill-disabled-quiet-resting: var(--ha-color-neutral-95);
/* fill disabled normal */
--ha-color-fill-disabled-normal-resting: var(--ha-color-neutral-95);
/* fill disabled loud */
--ha-color-fill-disabled-loud-resting: var(--ha-color-neutral-80);
/* fill danger quiet */
--ha-color-fill-danger-quiet-resting: var(--ha-color-red-95);
--ha-color-fill-danger-quiet-hover: var(--ha-color-red-90);
--ha-color-fill-danger-quiet-active: var(--ha-color-red-95);
/* fill danger normal */
--ha-color-fill-danger-normal-resting: var(--ha-color-red-90);
--ha-color-fill-danger-normal-hover: var(--ha-color-red-80);
--ha-color-fill-danger-normal-active: var(--ha-color-red-90);
/* fill danger loud */
--ha-color-fill-danger-loud-resting: var(--ha-color-red-50);
--ha-color-fill-danger-loud-hover: var(--ha-color-red-40);
--ha-color-fill-danger-loud-active: var(--ha-color-red-50);
/* fill warning quiet */
--ha-color-fill-warning-quiet-resting: var(--ha-color-orange-95);
--ha-color-fill-warning-quiet-hover: var(--ha-color-orange-90);
--ha-color-fill-warning-quiet-active: var(--ha-color-orange-95);
/* fill warning normal */
--ha-color-fill-warning-normal-resting: var(--ha-color-orange-90);
--ha-color-fill-warning-normal-hover: var(--ha-color-orange-80);
--ha-color-fill-warning-normal-active: var(--ha-color-orange-90);
/* fill warning loud */
--ha-color-fill-warning-loud-resting: var(--ha-color-orange-70);
--ha-color-fill-warning-loud-hover: var(--ha-color-orange-50);
--ha-color-fill-warning-loud-active: var(--ha-color-orange-70);
/* fill success quiet */
--ha-color-fill-success-quiet-resting: var(--ha-color-green-95);
--ha-color-fill-success-quiet-hover: var(--ha-color-green-90);
--ha-color-fill-success-quiet-active: var(--ha-color-green-95);
/* fill success normal */
--ha-color-fill-success-normal-resting: var(--ha-color-green-90);
--ha-color-fill-success-normal-hover: var(--ha-color-green-80);
--ha-color-fill-success-normal-active: var(--ha-color-green-90);
/* fill success loud */
--ha-color-fill-success-loud-resting: var(--ha-color-green-50);
--ha-color-fill-success-loud-hover: var(--ha-color-green-40);
--ha-color-fill-success-loud-active: var(--ha-color-green-50);
/* on primary */
--ha-color-on-primary-quiet: var(--ha-color-primary-50);
--ha-color-on-primary-normal: var(--ha-color-primary-40);
--ha-color-on-primary-loud: var(--white-color);
/* on neutral */
--ha-color-on-neutral-quiet: var(--ha-color-neutral-50);
--ha-color-on-neutral-normal: var(--ha-color-neutral-40);
--ha-color-on-neutral-loud: var(--white-color);
/* on disabled */
--ha-color-on-disabled-quiet: var(--ha-color-neutral-80);
--ha-color-on-disabled-normal: var(--ha-color-neutral-70);
--ha-color-on-disabled-loud: var(--ha-color-neutral-95);
/* on danger */
--ha-color-on-danger-quiet: var(--ha-color-red-50);
--ha-color-on-danger-normal: var(--ha-color-red-40);
--ha-color-on-danger-loud: var(--white-color);
/* on warning */
--ha-color-on-warning-quiet: var(--ha-color-orange-50);
--ha-color-on-warning-normal: var(--ha-color-orange-40);
--ha-color-on-warning-loud: var(--white-color);
/* on success */
--ha-color-on-success-quiet: var(--ha-color-green-50);
--ha-color-on-success-normal: var(--ha-color-green-40);
--ha-color-on-success-loud: var(--white-color);
/* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-95);
--ha-color-on-surface-default: var(--ha-color-neutral-05);
/* Scrollable fade */
--ha-color-shadow-scrollable-fade: rgba(0, 0, 0, 0.08);
// ============
// HA dynamically sets these values based on choice of primary/secondary
// colors and hex blending techniques. The card does not currently
// re-implement this logic, rather just uses the default static value.
// See: https://github.com/home-assistant/frontend/blob/dev/src/common/dom/apply_themes_on_element.ts
// ============
--app-header-background-color: #03a9f4;
--app-header-text-color: white;
// Menu border colors need to re-adjust based on redefined variable.
--wa-color-surface-border: var(--ha-color-border-neutral-quiet);
}
+1
View File
@@ -14,6 +14,7 @@
*********/
--advanced-camera-card-control-background: black;
--advanced-camera-card-control-foreground: white;
/******
* Menu
-1
View File
@@ -12,7 +12,6 @@
$z-index-effect: 7;
$z-index-loading: 6;
$z-index-submenu: 5;
// Need menu to render above drawer (so the drawer button is below menu-submenus)
$z-index-menu: 4;
+4 -1
View File
@@ -1,3 +1,4 @@
import { format } from 'date-fns';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { CameraManagerEngine } from '../../../src/camera-manager/engine';
@@ -514,7 +515,9 @@ describe('FrigateCamera', () => {
);
expect(camera.getEndpoints({ view: 'media', media })?.ui).toEqual({
endpoint: 'http://frigate/recording/front_door/2023-01-01/10',
endpoint:
'http://frigate/recording/front_door/' +
format(media.getStartTime(), 'yyyy-MM-dd/HH'),
});
});
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { afterEach, assert, describe, expect, it, vi } from 'vitest';
import { FrigateEventWatcher } from '../../../src/camera-manager/frigate/event-watcher.js';
import { FrigateEventChange } from '../../../src/camera-manager/frigate/types.js';
import { HomeAssistant } from '../../../src/ha/types.js';
@@ -77,6 +77,33 @@ describe('FrigateEventWatcher', () => {
expect(unsubscribeCallback).toBeCalledTimes(1);
});
it('should handle unsubscribe during pending subscription', async () => {
const stateWatcher = new FrigateEventWatcher();
const hass = createHASS();
let resolveSubscription: ((callback: () => Promise<void>) => void) | undefined;
const subscriptionPromise = new Promise<() => Promise<void>>((resolve) => {
resolveSubscription = resolve;
});
vi.mocked(hass.connection.subscribeMessage).mockReturnValue(subscriptionPromise);
const request = {
instanceID: 'frigate',
callback: vi.fn(),
};
// Start subscription (doesn't complete yet as not awaited).
const subscribePromise = stateWatcher.subscribe(hass, request);
// Unsubscribe while subscription is still pending.
await stateWatcher.unsubscribe(request);
// Complete the subscription.
assert(resolveSubscription);
resolveSubscription(vi.fn());
await subscribePromise;
});
describe('should call handler', () => {
afterEach(() => {
vi.resetAllMocks();
+2 -1
View File
@@ -23,11 +23,12 @@
// These tags are provided by the HA frontend and [almost all] cannot be
// imported by a custom card directly.
"globalTags": [
"ha-button-menu",
"ha-button",
"ha-camera-stream",
"ha-card",
"ha-combo-box",
"ha-dropdown-item",
"ha-dropdown",
"ha-hls-player",
"ha-icon-button",
"ha-icon",