diff --git a/src/components-lib/menu-controller.ts b/src/components-lib/menu-controller.ts index 083f17b7..a7749fa5 100644 --- a/src/components-lib/menu-controller.ts +++ b/src/components-lib/menu-controller.ts @@ -1,5 +1,5 @@ import { LitElement } from 'lit'; -import { orderBy } from 'lodash-es'; +import { isEqual, orderBy } from 'lodash-es'; import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js'; import { SubmenuInteraction } from '../components/submenu/types.js'; import { ActionConfig, ActionsConfig } from '../config/schema/actions/types.js'; @@ -46,6 +46,9 @@ export class MenuController { } public setButtons(buttons: MenuItem[]): void { + if (isEqual(buttons, this._buttons)) { + return; + } this._buttons = buttons; this._sortButtons(); this._host.requestUpdate(); diff --git a/src/components/drawer.ts b/src/components/drawer.ts index 2c0aa2bf..c296dffb 100644 --- a/src/components/drawer.ts +++ b/src/components/drawer.ts @@ -12,9 +12,13 @@ import 'side-drawer'; import { SideDrawer } from 'side-drawer'; import drawerInjectStyle from '../scss/drawer-inject.scss'; import drawerStyle from '../scss/drawer.scss'; -import { stopEventFromActivatingCardWideActions } from '../utils/action'; -import { getChildrenFromElement, isHoverableDevice } from '../utils/basic'; -import './icon'; +import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; +import { + contentsChanged, + getChildrenFromElement, + isHoverableDevice, +} from '../utils/basic.js'; +import './icon.js'; export interface DrawerIcons { open?: string; @@ -32,7 +36,7 @@ export class AdvancedCameraCardDrawer extends LitElement { @property({ type: Boolean, reflect: true, attribute: true }) public open = false; - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public icons?: DrawerIcons; // The 'empty' attribute is used in the styling to change the drawer diff --git a/src/components/icon.ts b/src/components/icon.ts index 1e8d97e9..d38fb136 100644 --- a/src/components/icon.ts +++ b/src/components/icon.ts @@ -10,14 +10,15 @@ import { customElement, property } from 'lit/decorators.js'; import { IconController } from '../components-lib/icon-controller'; import { HomeAssistant } from '../ha/types'; import iconStyle from '../scss/icon.scss'; -import { Icon } from '../types'; +import { Icon } from '../types.js'; +import { contentsChanged } from '../utils/basic.js'; @customElement('advanced-camera-card-icon') export class AdvancedCameraCardIcon extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public icon?: Icon; // Note: This attribute will allow non-active entity state styles (e.g. 'off', diff --git a/src/components/live/grid.ts b/src/components/live/grid.ts index 41d13334..6c356bb8 100644 --- a/src/components/live/grid.ts +++ b/src/components/live/grid.ts @@ -16,6 +16,7 @@ import { LiveConfig } from '../../config/schema/live.js'; import { CardWideConfig } from '../../config/schema/types.js'; import { HomeAssistant } from '../../ha/types.js'; import liveGridStyle from '../../scss/live-grid.scss'; +import { contentsChanged } from '../../utils/basic.js'; import './carousel.js'; @customElement('advanced-camera-card-live-grid') @@ -38,7 +39,7 @@ export class AdvancedCameraCardLiveGrid extends LitElement { @property({ attribute: false }) public microphoneState?: MicrophoneState; - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public triggeredCameraIDs?: Set; private _renderCarousel(cameraID?: string): TemplateResult { diff --git a/src/components/live/index.ts b/src/components/live/index.ts index 2b73a962..6361fe3d 100644 --- a/src/components/live/index.ts +++ b/src/components/live/index.ts @@ -8,6 +8,7 @@ import { LiveConfig } from '../../config/schema/live.js'; import { CardWideConfig } from '../../config/schema/types.js'; import { HomeAssistant } from '../../ha/types.js'; import basicBlockStyle from '../../scss/basic-block.scss'; +import { contentsChanged } from '../../utils/basic.js'; import './grid.js'; @customElement('advanced-camera-card-live') @@ -30,7 +31,7 @@ export class AdvancedCameraCardLive extends LitElement { @property({ attribute: false }) public microphoneState?: MicrophoneState; - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public triggeredCameraIDs?: Set; private _controller = new LiveController(this); diff --git a/src/components/status-bar.ts b/src/components/status-bar.ts index edab5077..eb457203 100644 --- a/src/components/status-bar.ts +++ b/src/components/status-bar.ts @@ -14,13 +14,14 @@ import { StatusBarItem } from '../config/schema/actions/types.js'; import { StatusBarConfig } from '../config/schema/status-bar.js'; import statusStyle from '../scss/status.scss'; import { hasAction } from '../utils/action'; +import { contentsChanged } from '../utils/basic.js'; import './icon.js'; @customElement('advanced-camera-card-status-bar') export class AdvancedCameraCardStatusBar extends LitElement { private _controller = new StatusBarController(this); - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public items?: StatusBarItem[]; @property({ attribute: false }) diff --git a/src/components/submenu/index.ts b/src/components/submenu/index.ts index 07692f1f..d64df479 100644 --- a/src/components/submenu/index.ts +++ b/src/components/submenu/index.ts @@ -9,6 +9,7 @@ import { hasAction, stopEventFromActivatingCardWideActions, } from '../../utils/action.js'; +import { contentsChanged } from '../../utils/basic.js'; import '../icon.js'; import { SubmenuInteraction, SubmenuItem } from './types.js'; @@ -17,7 +18,7 @@ export class AdvancedCameraCardSubmenu extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public items?: SubmenuItem[]; private _renderItem(item: SubmenuItem): TemplateResult | void { diff --git a/src/components/surround-basic.ts b/src/components/surround-basic.ts index b0e10e7d..cd24168f 100644 --- a/src/components/surround-basic.ts +++ b/src/components/surround-basic.ts @@ -1,11 +1,10 @@ -import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; -import { createRef, ref, Ref } from 'lit/directives/ref.js'; +import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { DrawerIcons, AdvancedCameraCardDrawer } from './drawer.js'; - -import './drawer.js'; - +import { createRef, ref, Ref } from 'lit/directives/ref.js'; import surroundBasicStyle from '../scss/surround-basic.scss'; +import { contentsChanged } from '../utils/basic.js'; +import './drawer.js'; +import { AdvancedCameraCardDrawer, DrawerIcons } from './drawer.js'; interface AdvancedCameraCardDrawerOpen { drawer: 'left' | 'right'; @@ -13,7 +12,7 @@ interface AdvancedCameraCardDrawerOpen { @customElement('advanced-camera-card-surround-basic') export class AdvancedCameraCardSurroundBasic extends LitElement { - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public drawerIcons?: { left?: DrawerIcons; right?: DrawerIcons; diff --git a/src/components/views.ts b/src/components/views.ts index ddefe7a9..e77ed1a3 100644 --- a/src/components/views.ts +++ b/src/components/views.ts @@ -20,6 +20,7 @@ import { DeviceRegistryManager } from '../ha/registry/device/index.js'; import { ResolvedMediaCache } from '../ha/resolved-media.js'; import { HomeAssistant } from '../ha/types.js'; import viewsStyle from '../scss/views.scss'; +import { contentsChanged } from '../utils/basic.js'; import './surround.js'; // As a special case: The diagnostics view is not dynamically loaded in case @@ -61,7 +62,7 @@ export class AdvancedCameraCardViews extends LitElement { @property({ attribute: false }) public microphoneState?: MicrophoneState; - @property({ attribute: false }) + @property({ attribute: false, hasChanged: contentsChanged }) public triggeredCameraIDs?: Set; @property({ attribute: false }) diff --git a/tests/components-lib/menu-controller.test.ts b/tests/components-lib/menu-controller.test.ts index a3e82dd2..56254bf4 100644 --- a/tests/components-lib/menu-controller.test.ts +++ b/tests/components-lib/menu-controller.test.ts @@ -252,6 +252,29 @@ describe('MenuController', () => { }, ]); }); + + it('with identical buttons to avoid unnecessary updates', () => { + const host = createLitElement(); + const controller = new MenuController(host); + + const buttons = [ + { + type: 'custom:advanced-camera-card-menu-icon' as const, + icon: 'mdi:cow', + }, + ]; + + controller.setButtons(buttons); + expect(host.requestUpdate).toBeCalledTimes(1); + + controller.setButtons([ + { + type: 'custom:advanced-camera-card-menu-icon' as const, + icon: 'mdi:cow', + }, + ]); + expect(host.requestUpdate).toBeCalledTimes(1); + }); }); describe('should get buttons', () => {