perf: Various rendering performance improvements (#2359)

This commit is contained in:
Dermot Duffy
2026-02-19 21:25:33 -08:00
committed by GitHub
parent 0a67df9a25
commit 46868ce08e
10 changed files with 54 additions and 19 deletions
+4 -1
View File
@@ -1,5 +1,5 @@
import { LitElement } from 'lit'; 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 { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js';
import { SubmenuInteraction } from '../components/submenu/types.js'; import { SubmenuInteraction } from '../components/submenu/types.js';
import { ActionConfig, ActionsConfig } from '../config/schema/actions/types.js'; import { ActionConfig, ActionsConfig } from '../config/schema/actions/types.js';
@@ -46,6 +46,9 @@ export class MenuController {
} }
public setButtons(buttons: MenuItem[]): void { public setButtons(buttons: MenuItem[]): void {
if (isEqual(buttons, this._buttons)) {
return;
}
this._buttons = buttons; this._buttons = buttons;
this._sortButtons(); this._sortButtons();
this._host.requestUpdate(); this._host.requestUpdate();
+8 -4
View File
@@ -12,9 +12,13 @@ import 'side-drawer';
import { SideDrawer } from 'side-drawer'; import { SideDrawer } from 'side-drawer';
import drawerInjectStyle from '../scss/drawer-inject.scss'; import drawerInjectStyle from '../scss/drawer-inject.scss';
import drawerStyle from '../scss/drawer.scss'; import drawerStyle from '../scss/drawer.scss';
import { stopEventFromActivatingCardWideActions } from '../utils/action'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
import { getChildrenFromElement, isHoverableDevice } from '../utils/basic'; import {
import './icon'; contentsChanged,
getChildrenFromElement,
isHoverableDevice,
} from '../utils/basic.js';
import './icon.js';
export interface DrawerIcons { export interface DrawerIcons {
open?: string; open?: string;
@@ -32,7 +36,7 @@ export class AdvancedCameraCardDrawer extends LitElement {
@property({ type: Boolean, reflect: true, attribute: true }) @property({ type: Boolean, reflect: true, attribute: true })
public open = false; public open = false;
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public icons?: DrawerIcons; public icons?: DrawerIcons;
// The 'empty' attribute is used in the styling to change the drawer // The 'empty' attribute is used in the styling to change the drawer
+3 -2
View File
@@ -10,14 +10,15 @@ import { customElement, property } from 'lit/decorators.js';
import { IconController } from '../components-lib/icon-controller'; import { IconController } from '../components-lib/icon-controller';
import { HomeAssistant } from '../ha/types'; import { HomeAssistant } from '../ha/types';
import iconStyle from '../scss/icon.scss'; 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') @customElement('advanced-camera-card-icon')
export class AdvancedCameraCardIcon extends LitElement { export class AdvancedCameraCardIcon extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public hass?: HomeAssistant; public hass?: HomeAssistant;
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public icon?: Icon; public icon?: Icon;
// Note: This attribute will allow non-active entity state styles (e.g. 'off', // Note: This attribute will allow non-active entity state styles (e.g. 'off',
+2 -1
View File
@@ -16,6 +16,7 @@ import { LiveConfig } from '../../config/schema/live.js';
import { CardWideConfig } from '../../config/schema/types.js'; import { CardWideConfig } from '../../config/schema/types.js';
import { HomeAssistant } from '../../ha/types.js'; import { HomeAssistant } from '../../ha/types.js';
import liveGridStyle from '../../scss/live-grid.scss'; import liveGridStyle from '../../scss/live-grid.scss';
import { contentsChanged } from '../../utils/basic.js';
import './carousel.js'; import './carousel.js';
@customElement('advanced-camera-card-live-grid') @customElement('advanced-camera-card-live-grid')
@@ -38,7 +39,7 @@ export class AdvancedCameraCardLiveGrid extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public microphoneState?: MicrophoneState; public microphoneState?: MicrophoneState;
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public triggeredCameraIDs?: Set<string>; public triggeredCameraIDs?: Set<string>;
private _renderCarousel(cameraID?: string): TemplateResult { private _renderCarousel(cameraID?: string): TemplateResult {
+2 -1
View File
@@ -8,6 +8,7 @@ import { LiveConfig } from '../../config/schema/live.js';
import { CardWideConfig } from '../../config/schema/types.js'; import { CardWideConfig } from '../../config/schema/types.js';
import { HomeAssistant } from '../../ha/types.js'; import { HomeAssistant } from '../../ha/types.js';
import basicBlockStyle from '../../scss/basic-block.scss'; import basicBlockStyle from '../../scss/basic-block.scss';
import { contentsChanged } from '../../utils/basic.js';
import './grid.js'; import './grid.js';
@customElement('advanced-camera-card-live') @customElement('advanced-camera-card-live')
@@ -30,7 +31,7 @@ export class AdvancedCameraCardLive extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public microphoneState?: MicrophoneState; public microphoneState?: MicrophoneState;
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public triggeredCameraIDs?: Set<string>; public triggeredCameraIDs?: Set<string>;
private _controller = new LiveController(this); private _controller = new LiveController(this);
+2 -1
View File
@@ -14,13 +14,14 @@ import { StatusBarItem } from '../config/schema/actions/types.js';
import { StatusBarConfig } from '../config/schema/status-bar.js'; import { StatusBarConfig } from '../config/schema/status-bar.js';
import statusStyle from '../scss/status.scss'; import statusStyle from '../scss/status.scss';
import { hasAction } from '../utils/action'; import { hasAction } from '../utils/action';
import { contentsChanged } from '../utils/basic.js';
import './icon.js'; import './icon.js';
@customElement('advanced-camera-card-status-bar') @customElement('advanced-camera-card-status-bar')
export class AdvancedCameraCardStatusBar extends LitElement { export class AdvancedCameraCardStatusBar extends LitElement {
private _controller = new StatusBarController(this); private _controller = new StatusBarController(this);
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public items?: StatusBarItem[]; public items?: StatusBarItem[];
@property({ attribute: false }) @property({ attribute: false })
+2 -1
View File
@@ -9,6 +9,7 @@ import {
hasAction, hasAction,
stopEventFromActivatingCardWideActions, stopEventFromActivatingCardWideActions,
} from '../../utils/action.js'; } from '../../utils/action.js';
import { contentsChanged } from '../../utils/basic.js';
import '../icon.js'; import '../icon.js';
import { SubmenuInteraction, SubmenuItem } from './types.js'; import { SubmenuInteraction, SubmenuItem } from './types.js';
@@ -17,7 +18,7 @@ export class AdvancedCameraCardSubmenu extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public hass?: HomeAssistant; public hass?: HomeAssistant;
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public items?: SubmenuItem[]; public items?: SubmenuItem[];
private _renderItem(item: SubmenuItem): TemplateResult | void { private _renderItem(item: SubmenuItem): TemplateResult | void {
+6 -7
View File
@@ -1,11 +1,10 @@
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { createRef, ref, Ref } from 'lit/directives/ref.js';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { DrawerIcons, AdvancedCameraCardDrawer } from './drawer.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js';
import './drawer.js';
import surroundBasicStyle from '../scss/surround-basic.scss'; import surroundBasicStyle from '../scss/surround-basic.scss';
import { contentsChanged } from '../utils/basic.js';
import './drawer.js';
import { AdvancedCameraCardDrawer, DrawerIcons } from './drawer.js';
interface AdvancedCameraCardDrawerOpen { interface AdvancedCameraCardDrawerOpen {
drawer: 'left' | 'right'; drawer: 'left' | 'right';
@@ -13,7 +12,7 @@ interface AdvancedCameraCardDrawerOpen {
@customElement('advanced-camera-card-surround-basic') @customElement('advanced-camera-card-surround-basic')
export class AdvancedCameraCardSurroundBasic extends LitElement { export class AdvancedCameraCardSurroundBasic extends LitElement {
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public drawerIcons?: { public drawerIcons?: {
left?: DrawerIcons; left?: DrawerIcons;
right?: DrawerIcons; right?: DrawerIcons;
+2 -1
View File
@@ -20,6 +20,7 @@ import { DeviceRegistryManager } from '../ha/registry/device/index.js';
import { ResolvedMediaCache } from '../ha/resolved-media.js'; import { ResolvedMediaCache } from '../ha/resolved-media.js';
import { HomeAssistant } from '../ha/types.js'; import { HomeAssistant } from '../ha/types.js';
import viewsStyle from '../scss/views.scss'; import viewsStyle from '../scss/views.scss';
import { contentsChanged } from '../utils/basic.js';
import './surround.js'; import './surround.js';
// As a special case: The diagnostics view is not dynamically loaded in case // 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 }) @property({ attribute: false })
public microphoneState?: MicrophoneState; public microphoneState?: MicrophoneState;
@property({ attribute: false }) @property({ attribute: false, hasChanged: contentsChanged })
public triggeredCameraIDs?: Set<string>; public triggeredCameraIDs?: Set<string>;
@property({ attribute: false }) @property({ attribute: false })
@@ -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', () => { describe('should get buttons', () => {