diff --git a/docs/configuration/elements/custom/README.md b/docs/configuration/elements/custom/README.md
index 0e521389..f9c73814 100644
--- a/docs/configuration/elements/custom/README.md
+++ b/docs/configuration/elements/custom/README.md
@@ -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. |
diff --git a/src/camera-manager/frigate/event-watcher.ts b/src/camera-manager/frigate/event-watcher.ts
index 6a66fc74..b085353c 100644
--- a/src/camera-manager/frigate/event-watcher.ts
+++ b/src/camera-manager/frigate/event-watcher.ts
@@ -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?.();
}
}
diff --git a/src/components/key-assigner.ts b/src/components/key-assigner.ts
index 29275157..a39dd02c 100644
--- a/src/components/key-assigner.ts
+++ b/src/components/key-assigner.ts
@@ -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`
${this.label}
{
this._controller.toggleAssigning();
}}
>
-
-
- ${this._controller.isAssigning() ? '' : localize('key_assigner.assign')}
-
+
+
${
this._controller.hasValue()
? html` {
this._controller.setValue(null);
}}
@@ -66,7 +66,6 @@ export class AdvancedCameraCardKeyAssigner extends LitElement {
- ${localize('key_assigner.unassign')}
`
: ''
}
diff --git a/src/components/submenu/index.ts b/src/components/submenu/index.ts
index f007305e..d567b3c3 100644
--- a/src/components/submenu/index.ts
+++ b/src/components/submenu/index.ts
@@ -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,31 +27,31 @@ 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`
- ) => {
// Attach the item so ascendants have access to it.
ev.detail.item = item;
}}
- .actionHandler=${actionHandler({
- allowPropagation: true,
- hasHold: hasAction(item.hold_action),
- hasDoubleClick: hasAction(item.double_tap_action),
- })}
+ .actionHandler=${disabled
+ ? undefined
+ : actionHandler({
+ allowPropagation: true,
+ hasHold: hasAction(item.hold_action),
+ hasDoubleClick: hasAction(item.double_tap_action),
+ })}
>
${title ?? ''}
${item.subtitle
- ? html`${item.subtitle}`
+ ? html`${item.subtitle}`
: ''}
-
+
`;
}
protected render(): TemplateResult {
return html`
- ev.stopPropagation()
- }
- @click=${(ev: Event) => stopEventFromActivatingCardWideActions(ev)}
- >
+ stopEventFromActivatingCardWideActions(ev)}>
${this.items?.map(this._renderItem.bind(this))}
-
+
`;
}
diff --git a/src/components/submenu/submenu-button.ts b/src/components/submenu/submenu-button.ts
index a98a5df1..9ec60337 100644
--- a/src/components/submenu/submenu-button.ts
+++ b/src/components/submenu/submenu-button.ts
@@ -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
- // trigger slot to open/close the menu. Further
- // propagation is forbidden by the @click handler on
- // .
allowPropagation: true,
hasHold: hasAction(this.submenu.hold_action),
hasDoubleClick: hasAction(this.submenu.double_tap_action),
diff --git a/src/ha/side-load-ha-elements.ts b/src/ha/side-load-ha-elements.ts
index 5671a4c3..36f92625 100644
--- a/src/ha/side-load-ha-elements.ts
+++ b/src/ha/side-load-ha-elements.ts
@@ -8,11 +8,12 @@ import { CardHelpers, LovelaceCardWithEditor } from '../types';
*/
export const sideLoadHomeAssistantElements = async (): Promise => {
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',
diff --git a/src/scss/editor.scss b/src/scss/editor.scss
index 355bd769..17bbeda4 100644
--- a/src/scss/editor.scss
+++ b/src/scss/editor.scss
@@ -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,
diff --git a/src/scss/key-assigner.scss b/src/scss/key-assigner.scss
index 80ff942c..48283936 100644
--- a/src/scss/key-assigner.scss
+++ b/src/scss/key-assigner.scss
@@ -1,5 +1,3 @@
-@use 'dotdotdot.scss';
-
:host {
display: flex;
flex-direction: row;
@@ -13,7 +11,18 @@
:host([assigning]) ha-button.assign span,
:host([assigning]) ha-button.assign advanced-camera-card-icon {
- color: var(--warning-color);
+ animation: pulse 0.5s infinite;
+
+ @keyframes pulse {
+ 50% {
+ color: var(--warning-color);
+ }
+ }
+}
+
+ha-button {
+ padding-left: 4px;
+ padding-right: 4px;
}
advanced-camera-card-icon {
diff --git a/src/scss/submenu.scss b/src/scss/submenu.scss
index 8176ffb9..dca2a862 100644
--- a/src/scss/submenu.scss
+++ b/src/scss/submenu.scss
@@ -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);
}
diff --git a/src/scss/themes/base.scss b/src/scss/themes/base.scss
index 3da18fea..1ba300e5 100644
--- a/src/scss/themes/base.scss
+++ b/src/scss/themes/base.scss
@@ -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
************/
diff --git a/src/scss/themes/dark.scss b/src/scss/themes/dark.scss
index 9ee5408e..1cf7f40d 100644
--- a/src/scss/themes/dark.scss
+++ b/src/scss/themes/dark.scss
@@ -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);
}
diff --git a/src/scss/themes/light.scss b/src/scss/themes/light.scss
index 3f7043c6..d4ad5fe1 100644
--- a/src/scss/themes/light.scss
+++ b/src/scss/themes/light.scss
@@ -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);
}
diff --git a/src/scss/themes/traditional.scss b/src/scss/themes/traditional.scss
index 6d717c47..bd6a2203 100644
--- a/src/scss/themes/traditional.scss
+++ b/src/scss/themes/traditional.scss
@@ -14,6 +14,7 @@
*********/
--advanced-camera-card-control-background: black;
+ --advanced-camera-card-control-foreground: white;
/******
* Menu
diff --git a/src/scss/z-index.scss b/src/scss/z-index.scss
index 2d5139b4..d8b4861f 100644
--- a/src/scss/z-index.scss
+++ b/src/scss/z-index.scss
@@ -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;
diff --git a/tests/camera-manager/frigate/camera.test.ts b/tests/camera-manager/frigate/camera.test.ts
index 6ccd4757..8e566564 100644
--- a/tests/camera-manager/frigate/camera.test.ts
+++ b/tests/camera-manager/frigate/camera.test.ts
@@ -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'),
});
});
diff --git a/tests/camera-manager/frigate/event-watcher.test.ts b/tests/camera-manager/frigate/event-watcher.test.ts
index a7e2eb1b..79ace5f0 100644
--- a/tests/camera-manager/frigate/event-watcher.test.ts
+++ b/tests/camera-manager/frigate/event-watcher.test.ts
@@ -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) | undefined;
+ const subscriptionPromise = new Promise<() => Promise>((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();
diff --git a/tsconfig.json b/tsconfig.json
index f488f60f..de9c8d39 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -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",