diff --git a/.github/workflows/relative-ci.yaml b/.github/workflows/relative-ci.yaml index 627215abb9..e82fe096c5 100644 --- a/.github/workflows/relative-ci.yaml +++ b/.github/workflows/relative-ci.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Send bundle stats and build information to RelativeCI - uses: relative-ci/agent-action@v2.2.0 + uses: relative-ci/agent-action@v3.0.0 with: key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} token: ${{ github.token }} diff --git a/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch b/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch deleted file mode 100644 index 1f2665c2df..0000000000 --- a/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/dist/hls.light.mjs b/dist/hls.light.mjs -index eed9d788fafdb159975e1a2eb08ac88ba9c9ac33..ace881935e6665946f1c8110ebd2f739cde4427e 100644 ---- a/dist/hls.light.mjs -+++ b/dist/hls.light.mjs -@@ -20523,9 +20523,9 @@ class Hls { - } - Hls.defaultConfig = void 0; - --var KeySystemFormats = empty.KeySystemFormats; --var KeySystems = empty.KeySystems; --var SubtitleStreamController = empty.SubtitleStreamController; --var TimelineController = empty.TimelineController; -+var KeySystemFormats = empty; -+var KeySystems = empty; -+var SubtitleStreamController = empty; -+var TimelineController = empty; - export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported }; - //# sourceMappingURL=hls.light.mjs.map diff --git a/.yarn/patches/sortablejs-npm-1.15.3-3235a8f83b.patch b/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch similarity index 100% rename from .yarn/patches/sortablejs-npm-1.15.3-3235a8f83b.patch rename to .yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch diff --git a/cast/src/launcher/layout/hc-connect.ts b/cast/src/launcher/layout/hc-connect.ts index 17dee754c7..31f88ae8eb 100644 --- a/cast/src/launcher/layout/hc-connect.ts +++ b/cast/src/launcher/layout/hc-connect.ts @@ -302,7 +302,7 @@ export class HcConnect extends LitElement { } .error { color: red; - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .error a { diff --git a/cast/src/launcher/layout/hc-layout.ts b/cast/src/launcher/layout/hc-layout.ts index 87cb2cced1..c34a10f874 100644 --- a/cast/src/launcher/layout/hc-layout.ts +++ b/cast/src/launcher/layout/hc-layout.ts @@ -86,9 +86,9 @@ class HcLayout extends LitElement { .card-header { color: var(--ha-card-header-color, var(--primary-text-color)); font-family: var(--ha-card-header-font-family, inherit); - font-size: var(--ha-card-header-font-size, 24px); + font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl)); letter-spacing: -0.012em; - line-height: 32px; + line-height: var(--ha-line-height-condensed); padding: 24px 16px 16px; display: block; margin: 0; @@ -98,7 +98,7 @@ class HcLayout extends LitElement { border-radius: 4px 4px 0 0; } .subtitle { - font-size: 14px; + font-size: var(--ha-font-size-m); color: var(--secondary-text-color); line-height: initial; } @@ -113,7 +113,7 @@ class HcLayout extends LitElement { } :host ::slotted(.section-header) { - font-weight: 500; + font-weight: var(--ha-font-weight-medium); padding: 4px 16px; text-transform: uppercase; } @@ -135,7 +135,7 @@ class HcLayout extends LitElement { .footer { text-align: center; - font-size: 12px; + font-size: var(--ha-font-size-s); padding: 8px 0 24px; color: var(--secondary-text-color); } diff --git a/cast/src/receiver/layout/hc-launch-screen.ts b/cast/src/receiver/layout/hc-launch-screen.ts index cab6840ba4..64b50839ee 100644 --- a/cast/src/receiver/layout/hc-launch-screen.ts +++ b/cast/src/receiver/layout/hc-launch-screen.ts @@ -29,7 +29,7 @@ class HcLaunchScreen extends LitElement { display: block; height: 100vh; background-color: #f2f4f9; - font-size: 24px; + font-size: var(--ha-font-size-2xl); } .container { display: flex; diff --git a/demo/src/html/index.html.template b/demo/src/html/index.html.template index adcae133fb..09a6e7b514 100644 --- a/demo/src/html/index.html.template +++ b/demo/src/html/index.html.template @@ -68,7 +68,7 @@ } #ha-launch-screen .ha-launch-screen-spacer-top { flex: 1; - margin-top: calc( 2 * max(env(safe-area-inset-bottom), 48px) + 46px ); + margin-top: calc( 2 * max(var(--safe-area-inset-bottom), 48px) + 46px ); padding-top: 48px; } #ha-launch-screen .ha-launch-screen-spacer-bottom { @@ -76,7 +76,7 @@ padding-top: 48px; } .ohf-logo { - margin: max(env(safe-area-inset-bottom), 48px) 0; + margin: max(var(--safe-area-inset-bottom), 48px) 0; display: flex; flex-direction: column; align-items: center; diff --git a/demo/src/stubs/frontend.ts b/demo/src/stubs/frontend.ts index ae4ac073fd..70a4d5a0d2 100644 --- a/demo/src/stubs/frontend.ts +++ b/demo/src/stubs/frontend.ts @@ -1,7 +1,30 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; +let changeFunction; + export const mockFrontend = (hass: MockHomeAssistant) => { hass.mockWS("frontend/get_user_data", () => ({ value: null, })); + hass.mockWS("frontend/set_user_data", ({ key, value }) => { + if (key === "sidebar") { + changeFunction?.({ + value: { + panelOrder: value.panelOrder || [], + hiddenPanels: value.hiddenPanels || [], + }, + }); + } + }); + hass.mockWS("frontend/subscribe_user_data", (_msg, _hass, onChange) => { + changeFunction = onChange; + onChange?.({ + value: { + panelOrder: [], + hiddenPanels: [], + }, + }); + // eslint-disable-next-line @typescript-eslint/no-empty-function + return () => {}; + }); }; diff --git a/gallery/src/components/page-description.ts b/gallery/src/components/page-description.ts index 9c98d9b37e..10ad924be9 100644 --- a/gallery/src/components/page-description.ts +++ b/gallery/src/components/page-description.ts @@ -38,12 +38,12 @@ class PageDescription extends HaMarkdown { } .title { font-size: 42px; - line-height: 56px; + line-height: var(--ha-line-height-condensed); padding-bottom: 8px; } .subtitle { - font-size: 18px; - line-height: 24px; + font-size: var(--ha-font-size-l); + line-height: var(--ha-line-height-normal); } .root { max-width: 800px; diff --git a/gallery/src/ha-demo-options.ts b/gallery/src/ha-demo-options.ts index f3565e7891..4ec1927f7b 100644 --- a/gallery/src/ha-demo-options.ts +++ b/gallery/src/ha-demo-options.ts @@ -34,7 +34,7 @@ class HaDemoOptions extends LitElement { height: 64px; padding: 0 16px; pointer-events: none; - font-size: 20px; + font-size: var(--ha-font-size-xl); } `, ]; diff --git a/gallery/src/ha-gallery.ts b/gallery/src/ha-gallery.ts index d1043815a2..b4d227488d 100644 --- a/gallery/src/ha-gallery.ts +++ b/gallery/src/ha-gallery.ts @@ -250,14 +250,14 @@ class HaGallery extends LitElement { } .page-footer .header { - font-size: 16px; - font-weight: 500; - line-height: 28px; + font-size: var(--ha-font-size-l); + font-weight: var(--ha-font-weight-medium); + line-height: var(--ha-line-height-normal); text-align: center; } .page-footer .secondary { - line-height: 23px; + line-height: var(--ha-line-height-normal); text-align: center; } diff --git a/gallery/src/pages/components/ha-control-button.ts b/gallery/src/pages/components/ha-control-button.ts index 96f10d9625..5d2daf09fc 100644 --- a/gallery/src/pages/components/ha-control-button.ts +++ b/gallery/src/pages/components/ha-control-button.ts @@ -150,7 +150,7 @@ export class DemoHaBarButton extends LitElement { margin: 0; } label { - font-weight: 600; + font-weight: var(--ha-font-weight-bold); } .custom { --control-button-icon-color: var(--primary-color); diff --git a/gallery/src/pages/components/ha-control-number-buttons.ts b/gallery/src/pages/components/ha-control-number-buttons.ts index 29496fc689..1b99117a50 100644 --- a/gallery/src/pages/components/ha-control-number-buttons.ts +++ b/gallery/src/pages/components/ha-control-number-buttons.ts @@ -86,7 +86,7 @@ export class DemoHarControlNumberButtons extends LitElement { margin: 0; } label { - font-weight: 600; + font-weight: var(--ha-font-weight-bold); } .custom { color: #2196f3; diff --git a/gallery/src/pages/components/ha-control-select-menu.ts b/gallery/src/pages/components/ha-control-select-menu.ts index 638f682b0c..6050c2639b 100644 --- a/gallery/src/pages/components/ha-control-select-menu.ts +++ b/gallery/src/pages/components/ha-control-select-menu.ts @@ -125,7 +125,7 @@ export class DemoHaControlSelectMenu extends LitElement { margin: 0; } label { - font-weight: 600; + font-weight: var(--ha-font-weight-bold); } .custom { --control-button-icon-color: var(--primary-color); diff --git a/gallery/src/pages/components/ha-control-select.ts b/gallery/src/pages/components/ha-control-select.ts index f3887d0144..8666f42a1f 100644 --- a/gallery/src/pages/components/ha-control-select.ts +++ b/gallery/src/pages/components/ha-control-select.ts @@ -181,7 +181,7 @@ export class DemoHaControlSelect extends LitElement { margin: 0; } label { - font-weight: 600; + font-weight: var(--ha-font-weight-bold); } .custom { --mdc-icon-size: 24px; diff --git a/gallery/src/pages/components/ha-control-slider.ts b/gallery/src/pages/components/ha-control-slider.ts index b4af37cb67..5d8fb36cb5 100644 --- a/gallery/src/pages/components/ha-control-slider.ts +++ b/gallery/src/pages/components/ha-control-slider.ts @@ -144,7 +144,7 @@ export class DemoHaBarSlider extends LitElement { margin: 0; } label { - font-weight: 600; + font-weight: var(--ha-font-weight-bold); } .custom { --control-slider-color: #ffcf4c; diff --git a/gallery/src/pages/components/ha-control-switch.ts b/gallery/src/pages/components/ha-control-switch.ts index 99ec957e9b..e175390948 100644 --- a/gallery/src/pages/components/ha-control-switch.ts +++ b/gallery/src/pages/components/ha-control-switch.ts @@ -112,7 +112,7 @@ export class DemoHaControlSwitch extends LitElement { margin: 0; } label { - font-weight: 600; + font-weight: var(--ha-font-weight-bold); } .custom { --control-switch-on-color: var(--green-color); diff --git a/gallery/src/pages/components/ha-hs-color-picker.ts b/gallery/src/pages/components/ha-hs-color-picker.ts index c97ec7c8df..54c708c9df 100644 --- a/gallery/src/pages/components/ha-hs-color-picker.ts +++ b/gallery/src/pages/components/ha-hs-color-picker.ts @@ -105,8 +105,8 @@ export class DemoHaHsColorPicker extends LitElement { width: 400px; } .value { - font-size: 22px; - font-weight: bold; + font-size: var(--ha-font-size-xl); + font-weight: var(--ha-font-weight-bold); margin: 0 0 12px 0; } `; diff --git a/gallery/src/pages/components/ha-select-box.ts b/gallery/src/pages/components/ha-select-box.ts index ed2d182227..9cc5320424 100644 --- a/gallery/src/pages/components/ha-select-box.ts +++ b/gallery/src/pages/components/ha-select-box.ts @@ -123,7 +123,7 @@ export class DemoHaSelectBox extends LitElement { margin: 0; } label { - font-weight: 600; + font-weight: var(--ha-font-weight-bold); margin-bottom: 8px; display: block; } diff --git a/gallery/src/pages/components/ha-spinner.ts b/gallery/src/pages/components/ha-spinner.ts index d84b13f399..3dc3eaae7b 100644 --- a/gallery/src/pages/components/ha-spinner.ts +++ b/gallery/src/pages/components/ha-spinner.ts @@ -1,6 +1,7 @@ import type { TemplateResult } from "lit"; -import { html, css, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element"; import "../../../../src/components/ha-bar"; import "../../../../src/components/ha-card"; import "../../../../src/components/ha-spinner"; @@ -11,29 +12,66 @@ export class DemoHaSpinner extends LitElement { @property({ attribute: false }) hass!: HomeAssistant; protected render(): TemplateResult { - return html` -
-
- -
- - - -
- -
- -
`; + return html` + ${["light", "dark"].map( + (mode) => html` +
+ +
+ + + + + + + +
+
+
+ ` + )} + `; + } + + firstUpdated(changedProps) { + super.firstUpdated(changedProps); + applyThemesOnElement( + this.shadowRoot!.querySelector(".dark"), + { + default_theme: "default", + default_dark_theme: "default", + themes: {}, + darkMode: true, + theme: "default", + }, + undefined, + undefined, + true + ); } static styles = css` + :host { + display: flex; + justify-content: center; + } + .dark, + .light { + display: block; + background-color: var(--primary-background-color); + padding: 0 50px; + margin: 16px; + border-radius: 8px; + } ha-card { - max-width: 600px; margin: 24px auto; } + .card-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; + } `; } diff --git a/gallery/src/pages/date-time/date-time-numeric.ts b/gallery/src/pages/date-time/date-time-numeric.ts index 0694e6edf0..e5659cdc6a 100644 --- a/gallery/src/pages/date-time/date-time-numeric.ts +++ b/gallery/src/pages/date-time/date-time-numeric.ts @@ -106,7 +106,7 @@ export class DemoDateTimeDateTimeNumeric extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/date-time-seconds.ts b/gallery/src/pages/date-time/date-time-seconds.ts index a2d88a0293..614696fa5b 100644 --- a/gallery/src/pages/date-time/date-time-seconds.ts +++ b/gallery/src/pages/date-time/date-time-seconds.ts @@ -106,7 +106,7 @@ export class DemoDateTimeDateTimeSeconds extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/date-time-short-year.ts b/gallery/src/pages/date-time/date-time-short-year.ts index 9e55a5c2c8..1bb04e54b1 100644 --- a/gallery/src/pages/date-time/date-time-short-year.ts +++ b/gallery/src/pages/date-time/date-time-short-year.ts @@ -106,7 +106,7 @@ export class DemoDateTimeDateTimeShortYear extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/date-time-short.ts b/gallery/src/pages/date-time/date-time-short.ts index 01a32fa32d..55206d928e 100644 --- a/gallery/src/pages/date-time/date-time-short.ts +++ b/gallery/src/pages/date-time/date-time-short.ts @@ -106,7 +106,7 @@ export class DemoDateTimeDateTimeShort extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/date-time.ts b/gallery/src/pages/date-time/date-time.ts index 6a61041d2b..30bac54815 100644 --- a/gallery/src/pages/date-time/date-time.ts +++ b/gallery/src/pages/date-time/date-time.ts @@ -106,7 +106,7 @@ export class DemoDateTimeDateTime extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/date.ts b/gallery/src/pages/date-time/date.ts index 12ee7244cc..3ae66b2325 100644 --- a/gallery/src/pages/date-time/date.ts +++ b/gallery/src/pages/date-time/date.ts @@ -92,7 +92,7 @@ export class DemoDateTimeDate extends LitElement { static styles = css` .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/time-seconds.ts b/gallery/src/pages/date-time/time-seconds.ts index 6a7dc0a8a7..499ca100e7 100644 --- a/gallery/src/pages/date-time/time-seconds.ts +++ b/gallery/src/pages/date-time/time-seconds.ts @@ -106,7 +106,7 @@ export class DemoDateTimeTimeSeconds extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/time-weekday.ts b/gallery/src/pages/date-time/time-weekday.ts index 68f24922ef..3f43b0e84d 100644 --- a/gallery/src/pages/date-time/time-weekday.ts +++ b/gallery/src/pages/date-time/time-weekday.ts @@ -106,7 +106,7 @@ export class DemoDateTimeTimeWeekday extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/gallery/src/pages/date-time/time.ts b/gallery/src/pages/date-time/time.ts index bc2c0135ce..e9a24b7cd6 100644 --- a/gallery/src/pages/date-time/time.ts +++ b/gallery/src/pages/date-time/time.ts @@ -106,7 +106,7 @@ export class DemoDateTimeTime extends LitElement { margin: 12px auto; } .header { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); } .center { text-align: center; diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts index e5afe3ed15..dcc845355e 100644 --- a/hassio/src/addon-view/config/hassio-addon-config.ts +++ b/hassio/src/addon-view/config/hassio-addon-config.ts @@ -428,13 +428,13 @@ class HassioAddonConfig extends LitElement { .header h2 { color: var(--ha-card-header-color, var(--primary-text-color)); font-family: var(--ha-card-header-font-family, inherit); - font-size: var(--ha-card-header-font-size, 24px); + font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl)); letter-spacing: -0.012em; - line-height: 48px; + line-height: var(--ha-line-height-expanded); padding: 12px 16px 16px; display: block; margin-block: 0px; - font-weight: normal; + font-weight: var(--ha-font-weight-normal); } .card-actions.right { justify-content: flex-end; diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 07a92c7b8e..e4c9c2cb17 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -1280,12 +1280,12 @@ class HassioAddonInfo extends LitElement { padding-left: 8px; padding-inline-start: 8px; padding-inline-end: initial; - font-size: 24px; + font-size: var(--ha-font-size-2xl); color: var(--ha-card-header-color, var(--primary-text-color)); } .addon-version { float: var(--float-end); - font-size: 15px; + font-size: var(--ha-font-size-l); vertical-align: middle; } .errors { diff --git a/hassio/src/backups/hassio-backups.ts b/hassio/src/backups/hassio-backups.ts index 05cfe65ea4..acfb5a0ca6 100644 --- a/hassio/src/backups/hassio-backups.ts +++ b/hassio/src/backups/hassio-backups.ts @@ -391,7 +391,7 @@ export class HassioBackups extends LitElement { top: -4px; } .selected-txt { - font-weight: bold; + font-weight: var(--ha-font-weight-bold); padding-left: 16px; padding-inline-start: 16px; padding-inline-end: initial; @@ -401,7 +401,7 @@ export class HassioBackups extends LitElement { margin-top: 20px; } .header-toolbar .selected-txt { - font-size: 16px; + font-size: var(--ha-font-size-l); } .header-toolbar .header-btns { margin-right: -12px; diff --git a/hassio/src/components/hassio-card-content.ts b/hassio/src/components/hassio-card-content.ts index 21027a1007..457df0437f 100644 --- a/hassio/src/components/hassio-card-content.ts +++ b/hassio/src/components/hassio-card-content.ts @@ -101,7 +101,7 @@ class HassioCardContent extends LitElement { overflow: hidden; position: relative; height: 2.4em; - line-height: 1.2em; + line-height: var(--ha-line-height-condensed); } .icon_image img { max-height: 40px; diff --git a/hassio/src/dashboard/hassio-dashboard.ts b/hassio/src/dashboard/hassio-dashboard.ts index 531f171541..4cbf3c97b4 100644 --- a/hassio/src/dashboard/hassio-dashboard.ts +++ b/hassio/src/dashboard/hassio-dashboard.ts @@ -132,9 +132,9 @@ class HassioDashboard extends LitElement { } ha-fab.non-tabs { position: fixed; - right: calc(16px + env(safe-area-inset-right)); - bottom: calc(16px + env(safe-area-inset-bottom)); - inset-inline-end: calc(16px + env(safe-area-inset-right)); + right: calc(16px + var(--safe-area-inset-right)); + bottom: calc(16px + var(--safe-area-inset-bottom)); + inset-inline-end: calc(16px + var(--safe-area-inset-right)); inset-inline-start: initial; z-index: 1; } diff --git a/hassio/src/dashboard/hassio-update.ts b/hassio/src/dashboard/hassio-update.ts index 369a80101d..5e5bd09396 100644 --- a/hassio/src/dashboard/hassio-update.ts +++ b/hassio/src/dashboard/hassio-update.ts @@ -131,7 +131,7 @@ export class HassioUpdate extends LitElement { } .update-heading { font-size: var(--ha-font-size-l); - font-weight: 500; + font-weight: var(--ha-font-weight-medium); margin-bottom: 0.5em; color: var(--primary-text-color); } diff --git a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts index 679014604f..dbf4aee778 100644 --- a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts +++ b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts @@ -173,7 +173,7 @@ class HassioHardwareDialog extends LitElement { font-family: var(--ha-font-family-code); } code { - font-size: 85%; + font-size: var(--ha-font-size-s); padding: 0.2em 0.4em; } search-input { diff --git a/hassio/src/dialogs/network/dialog-hassio-network.ts b/hassio/src/dialogs/network/dialog-hassio-network.ts index f9b3a61613..862b81769a 100644 --- a/hassio/src/dialogs/network/dialog-hassio-network.ts +++ b/hassio/src/dialogs/network/dialog-hassio-network.ts @@ -610,7 +610,7 @@ export class DialogHassioNetwork display: flex; justify-content: space-between; padding: 8px; - padding-bottom: max(env(safe-area-inset-bottom), 8px); + padding-bottom: max(var(--safe-area-inset-bottom), 8px); background-color: var(--mdc-theme-surface, #fff); } .warning { diff --git a/hassio/src/entrypoint.ts b/hassio/src/entrypoint.ts index 7efede19d2..73fa378323 100644 --- a/hassio/src/entrypoint.ts +++ b/hassio/src/entrypoint.ts @@ -1,3 +1,8 @@ +import { + haFontFamilyBody, + haFontSmoothing, + haMozOsxFontSmoothing, +} from "../../src/resources/theme/typography.globals"; import "./hassio-main"; import("../../src/resources/append-ha-style"); @@ -5,10 +10,10 @@ import("../../src/resources/append-ha-style"); const styleEl = document.createElement("style"); styleEl.textContent = ` body { - font-family: Roboto, sans-serif; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - font-weight: 400; + font-family: ${haFontFamilyBody}; + -moz-osx-font-smoothing: ${haMozOsxFontSmoothing}; + -webkit-font-smoothing: ${haFontSmoothing}; + font-weight: var(--ha-font-weight-normal); margin: 0; padding: 0; height: 100vh; diff --git a/hassio/src/ingress-view/hassio-ingress-view.ts b/hassio/src/ingress-view/hassio-ingress-view.ts index 1120936fa6..74545dc471 100644 --- a/hassio/src/ingress-view/hassio-ingress-view.ts +++ b/hassio/src/ingress-view/hassio-ingress-view.ts @@ -340,12 +340,12 @@ class HassioIngressView extends LitElement { .header { display: flex; align-items: center; - font-size: 16px; + font-size: var(--ha-font-size-l); height: 40px; padding: 0 16px; pointer-events: none; background-color: var(--app-header-background-color); - font-weight: 400; + font-weight: var(--ha-font-weight-normal); color: var(--app-header-text-color, white); border-bottom: var(--app-header-border-bottom, none); box-sizing: border-box; @@ -354,7 +354,7 @@ class HassioIngressView extends LitElement { .main-title { margin: var(--margin-title); - line-height: 20px; + line-height: var(--ha-line-height-condensed); flex-grow: 1; } diff --git a/hassio/src/resources/hassio-style.ts b/hassio/src/resources/hassio-style.ts index 1337e51d48..2979349546 100644 --- a/hassio/src/resources/hassio-style.ts +++ b/hassio/src/resources/hassio-style.ts @@ -14,6 +14,7 @@ export const hassioStyle = css` margin-bottom: 8px; font-family: var(--ha-font-family-body); -webkit-font-smoothing: var(--ha-font-smoothing); + -moz-osx-font-smoothing: var(--ha-moz-osx-font-smoothing); font-size: var(--ha-font-size-2xl); font-weight: var(--ha-font-weight-normal); line-height: var(--ha-line-height-condensed); diff --git a/lint-staged.config.js b/lint-staged.config.js index c09b35cc7d..d8e43a6fe7 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -4,7 +4,7 @@ export default { "prettier --cache --write", "lit-analyzer --quiet", ], - "*.{json,css,md,markdown,html,y?aml}": "prettier --cache --write", + "*.{json,css,md,markdown,html,ya?ml}": "prettier --cache --write", "translations/*/*.json": (files) => 'printf "%s\n" "Translation files should not be added or modified here. Instead, make the necessary modifications in src/translations/en.json. Other languages are managed externally. Please see https://developers.home-assistant.io/docs/translations/ for details." ' + files.join(" ") + diff --git a/package.json b/package.json index 8cabc96fab..5f9120e037 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,15 @@ "license": "Apache-2.0", "type": "module", "dependencies": { - "@babel/runtime": "7.27.0", + "@babel/runtime": "7.27.1", "@braintree/sanitize-url": "7.1.1", "@codemirror/autocomplete": "6.18.6", "@codemirror/commands": "6.8.1", "@codemirror/language": "6.11.0", "@codemirror/legacy-modes": "6.5.1", - "@codemirror/search": "6.5.10", + "@codemirror/search": "6.5.11", "@codemirror/state": "6.5.2", - "@codemirror/view": "6.36.6", + "@codemirror/view": "6.36.8", "@egjs/hammerjs": "2.0.17", "@formatjs/intl-datetimeformat": "6.18.0", "@formatjs/intl-displaynames": "6.8.11", @@ -89,17 +89,17 @@ "@thomasloven/round-slider": "0.6.0", "@tsparticles/engine": "3.8.1", "@tsparticles/preset-links": "3.2.0", - "@vaadin/combo-box": "24.7.4", - "@vaadin/vaadin-themable-mixin": "24.7.4", + "@vaadin/combo-box": "24.7.7", + "@vaadin/vaadin-themable-mixin": "24.7.7", "@vibrant/color": "4.0.0", "@vue/web-component-wrapper": "1.3.0", "@webcomponents/scoped-custom-element-registry": "0.0.10", "@webcomponents/webcomponentsjs": "2.8.0", "app-datepicker": "5.1.1", - "barcode-detector": "3.0.1", + "barcode-detector": "3.0.4", "color-name": "2.0.0", "comlink": "4.4.2", - "core-js": "3.41.0", + "core-js": "3.42.0", "cropperjs": "1.6.2", "date-fns": "4.1.0", "date-fns-tz": "3.2.0", @@ -111,9 +111,9 @@ "fuse.js": "7.1.0", "google-timezones-json": "1.2.0", "gulp-zopfli-green": "6.0.2", - "hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch", + "hls.js": "1.6.2", "home-assistant-js-websocket": "9.5.0", - "idb-keyval": "6.2.1", + "idb-keyval": "6.2.2", "intl-messageformat": "10.7.16", "js-yaml": "4.1.0", "leaflet": "1.9.4", @@ -122,7 +122,7 @@ "lit": "3.3.0", "lit-html": "3.3.0", "luxon": "3.6.1", - "marked": "15.0.11", + "marked": "15.0.12", "memoize-one": "6.0.0", "node-vibrant": "4.0.3", "object-hash": "3.0.0", @@ -131,13 +131,12 @@ "qrcode": "1.5.4", "roboto-fontface": "0.10.0", "rrule": "2.8.1", - "sortablejs": "patch:sortablejs@npm%3A1.15.3#~/.yarn/patches/sortablejs-npm-1.15.3-3235a8f83b.patch", + "sortablejs": "patch:sortablejs@npm%3A1.15.6#~/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch", "stacktrace-js": "2.0.2", "superstruct": "2.0.2", "tinykeys": "3.0.0", "ua-parser-js": "2.0.3", "vis-data": "7.1.9", - "vis-network": "9.1.9", "vue": "2.7.16", "vue2-daterange-picker": "0.6.8", "weekstart": "2.0.0", @@ -150,18 +149,18 @@ "xss": "1.0.15" }, "devDependencies": { - "@babel/core": "7.26.10", + "@babel/core": "7.27.1", "@babel/helper-define-polyfill-provider": "0.6.4", - "@babel/plugin-transform-runtime": "7.26.10", - "@babel/preset-env": "7.26.9", - "@bundle-stats/plugin-webpack-filter": "4.19.1", - "@lokalise/node-api": "14.4.0", - "@octokit/auth-oauth-device": "7.1.5", - "@octokit/plugin-retry": "7.2.1", + "@babel/plugin-transform-runtime": "7.27.1", + "@babel/preset-env": "7.27.2", + "@bundle-stats/plugin-webpack-filter": "4.20.1", + "@lokalise/node-api": "14.7.0", + "@octokit/auth-oauth-device": "8.0.1", + "@octokit/plugin-retry": "8.0.1", "@octokit/rest": "21.1.1", - "@rsdoctor/rspack-plugin": "1.0.2", - "@rspack/cli": "1.3.7", - "@rspack/core": "1.3.7", + "@rsdoctor/rspack-plugin": "1.1.2", + "@rspack/cli": "1.3.11", + "@rspack/core": "1.3.11", "@types/babel__plugin-transform-runtime": "7.9.5", "@types/chromecast-caf-receiver": "6.0.21", "@types/chromecast-caf-sender": "1.0.11", @@ -169,8 +168,8 @@ "@types/glob": "8.1.0", "@types/html-minifier-terser": "7.0.2", "@types/js-yaml": "4.0.9", - "@types/leaflet": "1.9.17", - "@types/leaflet-draw": "1.0.11", + "@types/leaflet": "1.9.18", + "@types/leaflet-draw": "1.0.12", "@types/leaflet.markercluster": "1.5.5", "@types/lodash.merge": "4.6.9", "@types/luxon": "3.6.2", @@ -180,20 +179,20 @@ "@types/tar": "6.1.13", "@types/ua-parser-js": "0.7.39", "@types/webspeechapi": "0.0.29", - "@vitest/coverage-v8": "3.1.2", + "@vitest/coverage-v8": "3.1.4", "babel-loader": "10.0.0", "babel-plugin-template-html-minifier": "4.1.0", "browserslist-useragent-regexp": "4.1.3", "del": "8.0.0", - "eslint": "9.25.1", + "eslint": "9.27.0", "eslint-config-airbnb-base": "15.0.0", - "eslint-config-prettier": "10.1.2", + "eslint-config-prettier": "10.1.5", "eslint-import-resolver-webpack": "0.13.10", "eslint-plugin-import": "2.31.0", "eslint-plugin-lit": "2.1.1", "eslint-plugin-lit-a11y": "4.1.4", "eslint-plugin-unused-imports": "4.1.4", - "eslint-plugin-wc": "3.0.0", + "eslint-plugin-wc": "3.0.1", "fancy-log": "2.0.0", "fs-extra": "11.3.0", "glob": "11.0.2", @@ -205,7 +204,7 @@ "husky": "9.1.7", "jsdom": "26.1.0", "jszip": "3.10.1", - "lint-staged": "15.5.1", + "lint-staged": "15.5.2", "lit-analyzer": "2.0.3", "lodash.merge": "4.6.2", "lodash.template": "4.5.0", @@ -219,9 +218,9 @@ "terser-webpack-plugin": "5.3.14", "ts-lit-plugin": "2.0.2", "typescript": "5.8.3", - "typescript-eslint": "8.31.0", + "typescript-eslint": "8.32.1", "vite-tsconfig-paths": "5.1.4", - "vitest": "3.1.2", + "vitest": "3.1.4", "webpack-stats-plugin": "1.1.3", "webpackbar": "7.0.0", "workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch" @@ -233,7 +232,7 @@ "clean-css": "5.3.3", "@lit/reactive-element": "2.1.0", "@fullcalendar/daygrid": "6.1.17", - "globals": "16.0.0", + "globals": "16.1.0", "tslib": "2.8.1", "@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch" }, diff --git a/pyproject.toml b/pyproject.toml index 0a5a6fb847..762f3468b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250516.0" +version = "20250531.1" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" diff --git a/src/auth/ha-authorize.ts b/src/auth/ha-authorize.ts index a8b254a552..dcd3ea4108 100644 --- a/src/auth/ha-authorize.ts +++ b/src/auth/ha-authorize.ts @@ -93,8 +93,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) { background-color: var(--primary-background-color, #fafafa); } p { - font-size: 14px; - line-height: 20px; + font-size: var(--ha-font-size-m); + line-height: var(--ha-line-height-normal); } .card-content { background: var( @@ -151,8 +151,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) { margin-inline-start: initial; } h1 { - font-size: 28px; - font-weight: 400; + font-size: var(--ha-font-size-3xl); + font-weight: var(--ha-font-weight-normal); margin-top: 16px; margin-bottom: 16px; } diff --git a/src/auth/ha-pick-auth-provider.ts b/src/auth/ha-pick-auth-provider.ts index 9f6f11cd7b..2e48d21fb7 100644 --- a/src/auth/ha-pick-auth-provider.ts +++ b/src/auth/ha-pick-auth-provider.ts @@ -57,9 +57,9 @@ export class HaPickAuthProvider extends LitElement { position: relative; z-index: 1; text-align: center; - font-size: 14px; - font-weight: 400; - line-height: 20px; + font-size: var(--ha-font-size-m); + font-weight: var(--ha-font-weight-normal); + line-height: var(--ha-line-height-normal); } h3:before { border-top: 1px solid var(--divider-color); diff --git a/src/common/entity/state_icon.ts b/src/common/entity/state_icon.ts index 58a63e881e..6eaef31725 100644 --- a/src/common/entity/state_icon.ts +++ b/src/common/entity/state_icon.ts @@ -2,7 +2,6 @@ import type { HassEntity } from "home-assistant-js-websocket"; import { computeStateDomain } from "./compute_state_domain"; import { updateIcon } from "./update_icon"; import { deviceTrackerIcon } from "./device_tracker_icon"; -import { batteryIcon } from "./battery_icon"; export const stateIcon = ( stateObj: HassEntity, @@ -10,17 +9,10 @@ export const stateIcon = ( ): string | undefined => { const domain = computeStateDomain(stateObj); const compareState = state ?? stateObj.state; - const dc = stateObj.attributes.device_class; switch (domain) { case "update": return updateIcon(stateObj, compareState); - case "sensor": - if (dc === "battery") { - return batteryIcon(stateObj, compareState); - } - break; - case "device_tracker": return deviceTrackerIcon(stateObj, compareState); diff --git a/src/common/entity/valid_service_id.ts b/src/common/entity/valid_service_id.ts new file mode 100644 index 0000000000..97c88f6907 --- /dev/null +++ b/src/common/entity/valid_service_id.ts @@ -0,0 +1,4 @@ +const validServiceId = /^(\w+)\.(\w+)$/; + +export const isValidServiceId = (actionId: string) => + validServiceId.test(actionId); diff --git a/src/common/string/slugify.ts b/src/common/string/slugify.ts index b7ddfed77c..fe5e0f537c 100644 --- a/src/common/string/slugify.ts +++ b/src/common/string/slugify.ts @@ -1,9 +1,19 @@ // https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1 export const slugify = (value: string, delimiter = "_") => { const a = - "àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìıİłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·"; - const b = `aaaaaaaaaacccddeeeeeeeegghiiiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz${delimiter}`; + "àáâäæãåāăąабçćčđďдèéêëēėęěеёэфğǵгḧхîïíīįìıİийкłлḿмñńǹňнôöòóœøōõőоṕпŕřрßśšşșсťțтûüùúūǘůűųувẃẍÿýыžźżз·"; + const b = `aaaaaaaaaaabcccdddeeeeeeeeeeefggghhiiiiiiiiijkllmmnnnnnoooooooooopprrrsssssstttuuuuuuuuuuvwxyyyzzzz${delimiter}`; const p = new RegExp(a.split("").join("|"), "g"); + const complex_cyrillic = { + ж: "zh", + х: "kh", + ц: "ts", + ч: "ch", + ш: "sh", + щ: "shch", + ю: "iu", + я: "ia", + }; let slugified; @@ -14,6 +24,7 @@ export const slugify = (value: string, delimiter = "_") => { .toString() .toLowerCase() .replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters + .replace(/[а-я]/g, (c) => complex_cyrillic[c] || "") // Replace some cyrillic characters .replace(/(\d),(?=\d)/g, "$1") // Remove Commas between numbers .replace(/[^a-z0-9]+/g, delimiter) // Replace all non-word characters .replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1") // Replace multiple delimiters with single delimiter diff --git a/src/common/style/derived-css-vars.ts b/src/common/style/derived-css-vars.ts index c4eca6e022..65e6f21d06 100644 --- a/src/common/style/derived-css-vars.ts +++ b/src/common/style/derived-css-vars.ts @@ -2,7 +2,7 @@ import type { CSSResult } from "lit"; const _extractCssVars = ( cssString: string, - condition: (string) => boolean = () => true + condition: (string: string) => boolean = () => true ) => { const variables: Record = {}; diff --git a/src/common/translations/markdown_support.ts b/src/common/translations/markdown_support.ts new file mode 100644 index 0000000000..2cf7271a47 --- /dev/null +++ b/src/common/translations/markdown_support.ts @@ -0,0 +1,14 @@ +import { html } from "lit"; +import type { LocalizeFunc } from "./localize"; + +const MARKDOWN_SUPPORT_URL = "https://commonmark.org/help/"; + +export const supportsMarkdownHelper = (localize: LocalizeFunc) => + localize("ui.common.supports_markdown", { + markdown_help_link: html`${localize("ui.common.markdown")}`, + }); diff --git a/src/components/chart/down-sample.ts b/src/components/chart/down-sample.ts new file mode 100644 index 0000000000..9790de2a26 --- /dev/null +++ b/src/components/chart/down-sample.ts @@ -0,0 +1,72 @@ +import type { LineSeriesOption } from "echarts"; + +export function downSampleLineData( + data: LineSeriesOption["data"], + chartWidth: number, + minX?: number, + maxX?: number +) { + if (!data || data.length < 10) { + return data; + } + const width = chartWidth * window.devicePixelRatio; + if (data.length <= width) { + return data; + } + const min = minX ?? getPointData(data[0]!)[0]; + const max = maxX ?? getPointData(data[data.length - 1]!)[0]; + const step = Math.floor((max - min) / width); + const frames = new Map< + number, + { + min: { point: (typeof data)[number]; x: number; y: number }; + max: { point: (typeof data)[number]; x: number; y: number }; + } + >(); + + // Group points into frames + for (const point of data) { + const pointData = getPointData(point); + if (!Array.isArray(pointData)) continue; + const x = Number(pointData[0]); + const y = Number(pointData[1]); + if (isNaN(x) || isNaN(y)) continue; + + const frameIndex = Math.floor((x - min) / step); + const frame = frames.get(frameIndex); + if (!frame) { + frames.set(frameIndex, { min: { point, x, y }, max: { point, x, y } }); + } else { + if (frame.min.y > y) { + frame.min = { point, x, y }; + } + if (frame.max.y < y) { + frame.max = { point, x, y }; + } + } + } + + // Convert frames back to points + const result: typeof data = []; + for (const [_i, frame] of frames) { + // Use min/max points to preserve visual accuracy + // The order of the data must be preserved so max may be before min + if (frame.min.x > frame.max.x) { + result.push(frame.max.point); + } + result.push(frame.min.point); + if (frame.min.x < frame.max.x) { + result.push(frame.max.point); + } + } + + return result; +} + +function getPointData(point: NonNullable[number]) { + const pointData = + point && typeof point === "object" && "value" in point + ? point.value + : point; + return pointData as number[]; +} diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index fab103ebc2..37a12f07e6 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -27,6 +27,7 @@ import "../ha-icon-button"; import { formatTimeLabel } from "./axis-label"; import { ensureArray } from "../../common/array/ensure-array"; import "../chips/ha-assist-chip"; +import { downSampleLineData } from "./down-sample"; export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000; const LEGEND_OVERFLOW_LIMIT = 10; @@ -48,7 +49,8 @@ export class HaChartBase extends LitElement { @property({ attribute: "expand-legend", type: Boolean }) public expandLegend?: boolean; - @property({ attribute: false }) public extraComponents?: any[]; + // extraComponents is not reactive and should not trigger updates + public extraComponents?: any[]; @state() @consume({ context: themesContext, subscribe: true }) @@ -106,48 +108,49 @@ export class HaChartBase extends LitElement { }) ); - // Add keyboard event listeners - const handleKeyDown = (ev: KeyboardEvent) => { - if ( - !this._modifierPressed && - ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) - ) { - this._modifierPressed = true; - if (!this.options?.dataZoom) { - this._setChartOptions({ dataZoom: this._getDataZoomConfig() }); + if (!this.options?.dataZoom) { + // Add keyboard event listeners + const handleKeyDown = (ev: KeyboardEvent) => { + if ( + !this._modifierPressed && + ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) + ) { + this._modifierPressed = true; + if (!this.options?.dataZoom) { + this._setChartOptions({ dataZoom: this._getDataZoomConfig() }); + } + // drag to zoom + this.chart?.dispatchAction({ + type: "takeGlobalCursor", + key: "dataZoomSelect", + dataZoomSelectActive: true, + }); } - // drag to zoom - this.chart?.dispatchAction({ - type: "takeGlobalCursor", - key: "dataZoomSelect", - dataZoomSelectActive: true, - }); - } - }; + }; - const handleKeyUp = (ev: KeyboardEvent) => { - if ( - this._modifierPressed && - ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) - ) { - this._modifierPressed = false; - if (!this.options?.dataZoom) { - this._setChartOptions({ dataZoom: this._getDataZoomConfig() }); + const handleKeyUp = (ev: KeyboardEvent) => { + if ( + this._modifierPressed && + ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) + ) { + this._modifierPressed = false; + if (!this.options?.dataZoom) { + this._setChartOptions({ dataZoom: this._getDataZoomConfig() }); + } + this.chart?.dispatchAction({ + type: "takeGlobalCursor", + key: "dataZoomSelect", + dataZoomSelectActive: false, + }); } - this.chart?.dispatchAction({ - type: "takeGlobalCursor", - key: "dataZoomSelect", - dataZoomSelectActive: false, - }); - } - }; - - window.addEventListener("keydown", handleKeyDown); - window.addEventListener("keyup", handleKeyUp); - this._listeners.push( - () => window.removeEventListener("keydown", handleKeyDown), - () => window.removeEventListener("keyup", handleKeyUp) - ); + }; + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + this._listeners.push( + () => window.removeEventListener("keydown", handleKeyDown), + () => window.removeEventListener("keyup", handleKeyUp) + ); + } } protected firstUpdated() { @@ -191,16 +194,19 @@ export class HaChartBase extends LitElement {
${this._renderLegend()} - ${this._isZoomed - ? html`` - : nothing} +
+ ${this._isZoomed + ? html`` + : nothing} + +
`; } @@ -210,15 +216,15 @@ export class HaChartBase extends LitElement { return nothing; } const legend = ensureArray(this.options.legend)[0] as LegendComponentOption; - if (!legend.show) { + if (!legend.show || legend.type !== "custom") { return nothing; } const datasets = ensureArray(this.data); - const items = (legend.data || - datasets + const items: LegendComponentOption["data"] = + legend.data || + ((datasets .filter((d) => (d.data as any[])?.length && (d.id || d.name)) - .map((d) => d.name ?? d.id) || - []) as string[]; + .map((d) => d.name ?? d.id) || []) as string[]); const isMobile = window.matchMedia( "all and (max-width: 450px), all and (max-height: 500px)" @@ -233,20 +239,32 @@ export class HaChartBase extends LitElement { })} >