diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index d4af4fbeba..ca5bb5e97d 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -2,12 +2,12 @@
"name": "Home Assistant Frontend",
"build": {
"dockerfile": "Dockerfile",
- "context": ".."
+ "context": "..",
},
"appPort": "8124:8123",
"postStartCommand": "script/bootstrap",
"containerEnv": {
- "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
+ "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}",
},
"customizations": {
"vscode": {
@@ -16,7 +16,7 @@
"esbenp.prettier-vscode",
"runem.lit-plugin",
"github.vscode-pull-request-github",
- "eamodio.gitlens"
+ "eamodio.gitlens",
],
"settings": {
"files.eol": "\n",
@@ -27,17 +27,17 @@
"editor.renderWhitespace": "boundary",
"editor.rulers": [80],
"[typescript]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[javascript]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
},
"files.trimTrailingWhitespace": true,
"terminal.integrated.shell.linux": "/usr/bin/zsh",
"gitlens.showWelcomeOnInstall": false,
"gitlens.showWhatsNewAfterUpgrades": false,
- "workbench.startupEditor": "none"
- }
- }
- }
+ "workbench.startupEditor": "none",
+ },
+ },
+ },
}
diff --git a/.github/labeler.yml b/.github/labeler.yml
index b09de205ba..e772cc81a5 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -24,14 +24,20 @@ Design:
- src/fake_data/**
Dependencies:
- - changed-files:
- - any-glob-to-any-file:
- - package.json
- - renovate.json
- - yarn.lock
- - .yarn/**
- - .yarnrc.yml
- - .nvmrc
+ - any:
+ - changed-files:
+ # Match when only these files are changed (i.e. don't match PRs that happen to add or remove packages)
+ - any-glob-to-all-files:
+ - package.json
+ - renovate.json
+ - yarn.lock
+ - .yarn/**
+ - .yarnrc.yml
+ - .nvmrc
+ # Dependabot and Renovate branches always match (i.e. compatibility tweaks by members considered minor)
+ - head-branch:
+ - "^renovate/"
+ - "^dependabot/"
GitHub Actions:
- changed-files:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index af8a338903..2becc88dff 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -37,17 +37,20 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
- uses: actions/cache@v3.3.2
+ uses: actions/cache@v4.0.0
with:
path: |
node_modules/.cache/prettier
node_modules/.cache/eslint
+ node_modules/.cache/typescript
key: lint-${{ github.sha }}
restore-keys: lint-
- name: Run eslint
run: yarn run lint:eslint --quiet
- name: Run tsc
run: yarn run lint:types
+ - name: Run lit-analyzer
+ run: yarn run lint:lit --quiet
- name: Run prettier
run: yarn run lint:prettier
test:
@@ -86,7 +89,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v4.3.0
with:
name: frontend-bundle-stats
path: build/stats/*.json
@@ -110,7 +113,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v4.3.0
with:
name: supervisor-bundle-stats
path: build/stats/*.json
diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml
index 4ff941bb8a..1d62f1071f 100644
--- a/.github/workflows/nightly.yaml
+++ b/.github/workflows/nightly.yaml
@@ -42,7 +42,7 @@ jobs:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Bump version
- run: script/version_bump.cjs nightly
+ run: script/version_bump.js nightly
- name: Build nightly Python wheels
run: |
@@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v4.3.0
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v4.3.0
with:
name: translations
path: translations.tar.gz
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 8bded3fcc5..dc843252dd 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -74,7 +74,7 @@ jobs:
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels
- uses: home-assistant/wheels@2023.10.5
+ uses: home-assistant/wheels@2024.01.0
with:
abi: cp311
tag: musllinux_1_2
diff --git a/.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch b/.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch
deleted file mode 100644
index be555a3f9d..0000000000
--- a/.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-diff --git a/modular/sortable.complete.esm.js b/modular/sortable.complete.esm.js
-index 02e9f2d6bebeb430fe6e7c1cc3f9c3c9df051f14..bb8268b0844a1faa4108cc92c0be2a3dbaf23f83 100644
---- a/modular/sortable.complete.esm.js
-+++ b/modular/sortable.complete.esm.js
-@@ -1657,7 +1657,7 @@ Sortable.prototype =
- target = parent; // store last element
- }
- /* jshint boss:true */
-- while (parent = parent.parentNode);
-+ while (parent = parent.parentNode || parent.getRootNode().host);
- }
-
- _unhideGhostForTarget();
-diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
-index b04c8b4634f7c6b4ef1aadbb48afe6564306dea9..39a107163c8c336ebd669b5ea8a936af87e1c1e7 100644
---- a/modular/sortable.core.esm.js
-+++ b/modular/sortable.core.esm.js
-@@ -1657,7 +1657,7 @@ Sortable.prototype =
- target = parent; // store last element
- }
- /* jshint boss:true */
-- while (parent = parent.parentNode);
-+ while (parent = parent.parentNode || parent.getRootNode().host);
- }
-
- _unhideGhostForTarget();
-diff --git a/modular/sortable.esm.js b/modular/sortable.esm.js
-index 6ec7ed1bb557e21c2578200161e989c65d23150b..0a05475a22904472fac6c13f524c674da76584b0 100644
---- a/modular/sortable.esm.js
-+++ b/modular/sortable.esm.js
-@@ -1657,7 +1657,7 @@ Sortable.prototype =
- target = parent; // store last element
- }
- /* jshint boss:true */
-- while (parent = parent.parentNode);
-+ while (parent = parent.parentNode || parent.getRootNode().host);
- }
-
- _unhideGhostForTarget();
diff --git a/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch b/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch
new file mode 100644
index 0000000000..bef3450d02
--- /dev/null
+++ b/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch
@@ -0,0 +1,73 @@
+diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
+index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b441f523f 100644
+--- a/modular/sortable.core.esm.js
++++ b/modular/sortable.core.esm.js
+@@ -1461,7 +1461,7 @@ Sortable.prototype = /** @lends Sortable.prototype */{
+ }
+ target = parent; // store last element
+ }
+- /* jshint boss:true */ while (parent = parent.parentNode);
++ /* jshint boss:true */ while (parent = parent.parentNode || parent.getRootNode().host);
+ }
+ _unhideGhostForTarget();
+ }
+@@ -1781,11 +1781,16 @@ Sortable.prototype = /** @lends Sortable.prototype */{
+ }
+ if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
+ capture();
+- if (elLastChild && elLastChild.nextSibling) {
+- // the last draggable element is not the last node
+- el.insertBefore(dragEl, elLastChild.nextSibling);
+- } else {
+- el.appendChild(dragEl);
++ try {
++ if (elLastChild && elLastChild.nextSibling) {
++ // the last draggable element is not the last node
++ el.insertBefore(dragEl, elLastChild.nextSibling);
++ } else {
++ el.appendChild(dragEl);
++ }
++ }
++ catch(err) {
++ return completed(false);
+ }
+ parentEl = el; // actualization
+
+@@ -1802,7 +1807,13 @@ Sortable.prototype = /** @lends Sortable.prototype */{
+ targetRect = getRect(target);
+ if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) {
+ capture();
+- el.insertBefore(dragEl, firstChild);
++ try {
++ el.insertBefore(dragEl, firstChild);
++ }
++ catch(err) {
++ return completed(false);
++ }
++
+ parentEl = el; // actualization
+
+ changed();
+@@ -1849,12 +1860,17 @@ Sortable.prototype = /** @lends Sortable.prototype */{
+ _silent = true;
+ setTimeout(_unsilent, 30);
+ capture();
+- if (after && !nextSibling) {
+- el.appendChild(dragEl);
+- } else {
+- target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
+- }
+
++ try {
++ if (after && !nextSibling) {
++ el.appendChild(dragEl);
++ } else {
++ target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
++ }
++ }
++ catch(err) {
++ return completed(false);
++ }
+ // Undo chrome's scroll adjustment (has no effect on other browsers)
+ if (scrolledPastTop) {
+ scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js
index b811c23bea..aa1567b721 100644
--- a/build-scripts/gulp/compress.js
+++ b/build-scripts/gulp/compress.js
@@ -8,7 +8,10 @@ const zopfliOptions = { threshold: 150 };
const compressDist = (rootDir) =>
gulp
- .src([`${rootDir}/**/*.{js,json,css,svg}`])
+ .src([
+ `${rootDir}/**/*.{js,json,css,svg,xml}`,
+ `${rootDir}/{authorize,onboarding}.html`,
+ ])
.pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(rootDir));
diff --git a/build-scripts/gulp/translations.js b/build-scripts/gulp/translations.js
index 967da71efd..fd4cccc013 100755
--- a/build-scripts/gulp/translations.js
+++ b/build-scripts/gulp/translations.js
@@ -426,6 +426,7 @@ gulp.task(
"fetch-nightly-translations",
gulp.series("clean-translations", "ensure-translations-build-dir")
),
+ gulp.parallel("create-test-metadata", "create-test-translation"),
"build-master-translation",
"build-merged-translations",
"build-translation-fragment-supervisor",
diff --git a/build-scripts/webpack.cjs b/build-scripts/webpack.cjs
index 432cca35d2..e1047cadd4 100644
--- a/build-scripts/webpack.cjs
+++ b/build-scripts/webpack.cjs
@@ -7,6 +7,9 @@ const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
+const {
+ TransformAsyncModulesPlugin,
+} = require("transform-async-modules-webpack-plugin");
const paths = require("./paths.cjs");
const bundle = require("./bundle.cjs");
@@ -142,17 +145,6 @@ const createWebpackConfig = ({
),
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
- // See `src/resources/intl-polyfill-legacy.ts` for explanation
- !latestBuild &&
- new webpack.NormalModuleReplacementPlugin(
- new RegExp(
- path.resolve(paths.polymer_dir, "src/resources/intl-polyfill.ts")
- ),
- path.resolve(
- paths.polymer_dir,
- "src/resources/intl-polyfill-legacy.ts"
- )
- ),
!isProdBuild && new LogStartCompilePlugin(),
isProdBuild &&
new StatsWriterPlugin({
@@ -163,6 +155,8 @@ const createWebpackConfig = ({
stats: { assets: true, chunks: true, modules: true },
transform: (stats) => JSON.stringify(filterStats(stats)),
}),
+ !latestBuild &&
+ new TransformAsyncModulesPlugin({ browserslistEnv: "legacy" }),
].filter(Boolean),
resolve: {
extensions: [".ts", ".js", ".json"],
diff --git a/cast/public/images/nabu-loves-hass.png b/cast/public/images/nabu-loves-hass.png
new file mode 100644
index 0000000000..b30f616d55
Binary files /dev/null and b/cast/public/images/nabu-loves-hass.png differ
diff --git a/cast/src/launcher/layout/hc-cast.ts b/cast/src/launcher/layout/hc-cast.ts
index bc61a5cfd7..27de4e0198 100644
--- a/cast/src/launcher/layout/hc-cast.ts
+++ b/cast/src/launcher/layout/hc-cast.ts
@@ -31,11 +31,11 @@ import "./hc-layout";
@customElement("hc-cast")
class HcCast extends LitElement {
- @property() public auth!: Auth;
+ @property({ attribute: false }) public auth!: Auth;
- @property() public connection!: Connection;
+ @property({ attribute: false }) public connection!: Connection;
- @property() public castManager!: CastManager;
+ @property({ attribute: false }) public castManager!: CastManager;
@state() private askWrite = false;
@@ -241,6 +241,8 @@ class HcCast extends LitElement {
mwc-button ha-svg-icon {
margin-right: 8px;
+ margin-inline-end: 8px;
+ margin-inline-start: initial;
height: 18px;
}
diff --git a/cast/src/launcher/layout/hc-layout.ts b/cast/src/launcher/layout/hc-layout.ts
index 69d788fe21..31b336cb51 100644
--- a/cast/src/launcher/layout/hc-layout.ts
+++ b/cast/src/launcher/layout/hc-layout.ts
@@ -10,13 +10,13 @@ import "../../../../src/components/ha-card";
@customElement("hc-layout")
class HcLayout extends LitElement {
- @property() public subtitle?: string | undefined;
+ @property() public subtitle?: string;
- @property() public auth?: Auth;
+ @property({ attribute: false }) public auth?: Auth;
- @property() public connection?: Connection;
+ @property({ attribute: false }) public connection?: Connection;
- @property() public user?: HassUser;
+ @property({ attribute: false }) public user?: HassUser;
protected render(): TemplateResult {
return html`
diff --git a/cast/src/receiver/layout/hc-launch-screen.ts b/cast/src/receiver/layout/hc-launch-screen.ts
index 0b94aacda1..2b212860e8 100644
--- a/cast/src/receiver/layout/hc-launch-screen.ts
+++ b/cast/src/receiver/layout/hc-launch-screen.ts
@@ -12,8 +12,8 @@ class HcLaunchScreen extends LitElement {
return html`
${this.hass ? "Connected" : "Not Connected"}
@@ -45,6 +45,8 @@ class HcLaunchScreen extends LitElement {
}
.status {
padding-right: 54px;
+ padding-inline-end: 54px;
+ padding-inline-start: initial;
}
`;
}
diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts
index 876b68cda3..dcf7a80eec 100644
--- a/cast/src/receiver/layout/hc-main.ts
+++ b/cast/src/receiver/layout/hc-main.ts
@@ -205,7 +205,6 @@ export class HcMain extends HassElement {
expires_in: 0,
}),
});
- this._hassUUID = msg.hassUUID;
} catch (err: any) {
const errorMessage = this._getErrorMessage(err);
this._error = errorMessage;
@@ -225,6 +224,17 @@ export class HcMain extends HassElement {
this.hass.connection.close();
}
this.initializeHass(auth, connection);
+ if (this._hassUUID !== msg.hassUUID) {
+ this._hassUUID = msg.hassUUID;
+ this._lovelacePath = null;
+ this._urlPath = undefined;
+ this._lovelaceConfig = undefined;
+ if (this._unsubLovelace) {
+ this._unsubLovelace();
+ this._unsubLovelace = undefined;
+ }
+ resourcesLoaded = false;
+ }
this._error = undefined;
this._sendStatus();
}
@@ -233,7 +243,7 @@ export class HcMain extends HassElement {
this._showDemo = false;
// We should not get this command before we are connected.
// Means a client got out of sync. Let's send status to them.
- if (!this.hass) {
+ if (!this.hass?.connected) {
this._sendStatus(msg.senderId!);
this._error = "Cannot show Lovelace because we're not connected.";
this._sendError(
@@ -284,6 +294,7 @@ export class HcMain extends HassElement {
this._lovelaceConfig = undefined;
if (this._unsubLovelace) {
this._unsubLovelace();
+ this._unsubLovelace = undefined;
}
const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107)
? getLovelaceCollection(this.hass.connection, msg.urlPath)
diff --git a/gallery/src/components/demo-black-white-row.ts b/gallery/src/components/demo-black-white-row.ts
index 43a58ea59e..30b2c533b7 100644
--- a/gallery/src/components/demo-black-white-row.ts
+++ b/gallery/src/components/demo-black-white-row.ts
@@ -11,7 +11,7 @@ class DemoBlackWhiteRow extends LitElement {
@property() value!: any;
- @property() disabled = false;
+ @property({ type: Boolean }) public disabled = false;
protected render(): TemplateResult {
return html`
diff --git a/gallery/src/components/demo-card.ts b/gallery/src/components/demo-card.ts
index 1c08792952..3e24d1897e 100644
--- a/gallery/src/components/demo-card.ts
+++ b/gallery/src/components/demo-card.ts
@@ -11,11 +11,11 @@ export interface DemoCardConfig {
@customElement("demo-card")
class DemoCard extends LitElement {
- @property() public hass!: HomeAssistant;
+ @property({ attribute: false }) public hass!: HomeAssistant;
- @property() public config!: DemoCardConfig;
+ @property({ attribute: false }) public config!: DemoCardConfig;
- @property() public showConfig = false;
+ @property({ type: Boolean }) public showConfig = false;
@state() private _size?: number;
diff --git a/gallery/src/components/demo-cards.ts b/gallery/src/components/demo-cards.ts
index 0bb36e3309..c2e117ceeb 100644
--- a/gallery/src/components/demo-cards.ts
+++ b/gallery/src/components/demo-cards.ts
@@ -10,9 +10,9 @@ import "../ha-demo-options";
@customElement("demo-cards")
class DemoCards extends LitElement {
- @property() public configs!: DemoCardConfig[];
+ @property({ attribute: false }) public configs!: DemoCardConfig[];
- @property() public hass!: HomeAssistant;
+ @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _showConfig = false;
diff --git a/gallery/src/components/demo-more-info.ts b/gallery/src/components/demo-more-info.ts
index eeb5c92264..dd1b037816 100644
--- a/gallery/src/components/demo-more-info.ts
+++ b/gallery/src/components/demo-more-info.ts
@@ -8,11 +8,11 @@ import { HomeAssistant } from "../../../src/types";
@customElement("demo-more-info")
class DemoMoreInfo extends LitElement {
- @property() public hass!: HomeAssistant;
+ @property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string;
- @property() public showConfig!: boolean;
+ @property({ type: Boolean }) public showConfig = false;
render() {
const state = this._getState(this.entityId, this.hass.states);
diff --git a/gallery/src/components/demo-more-infos.ts b/gallery/src/components/demo-more-infos.ts
index 5d7d928620..af7703050c 100644
--- a/gallery/src/components/demo-more-infos.ts
+++ b/gallery/src/components/demo-more-infos.ts
@@ -1,19 +1,19 @@
import { LitElement, css, html } from "lit";
-import { customElement, property } from "lit/decorators";
+import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
-import "./demo-more-info";
-import "../ha-demo-options";
import { HomeAssistant } from "../../../src/types";
+import "../ha-demo-options";
+import "./demo-more-info";
@customElement("demo-more-infos")
class DemoMoreInfos extends LitElement {
- @property() public hass!: HomeAssistant;
+ @property({ attribute: false }) public hass!: HomeAssistant;
- @property() public entities!: [];
+ @property({ type: Array }) public entities!: string[];
- @property({ attribute: false }) _showConfig: boolean = false;
+ @state() private _showConfig = false;
render() {
return html`
diff --git a/gallery/src/ha-gallery.ts b/gallery/src/ha-gallery.ts
index 31f2337a12..eec45d2b52 100644
--- a/gallery/src/ha-gallery.ts
+++ b/gallery/src/ha-gallery.ts
@@ -1,14 +1,14 @@
-import { mdiMenu } from "@mdi/js";
import "@material/mwc-drawer";
import "@material/mwc-top-app-bar-fixed";
-import { html, css, LitElement, PropertyValues } from "lit";
-import { customElement, property, query } from "lit/decorators";
+import { mdiMenu } from "@mdi/js";
+import { LitElement, PropertyValues, css, html } from "lit";
+import { customElement, query, state } from "lit/decorators";
+import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
+import { HaExpansionPanel } from "../../src/components/ha-expansion-panel";
import "../../src/components/ha-icon-button";
import "../../src/managers/notification-manager";
-import { HaExpansionPanel } from "../../src/components/ha-expansion-panel";
import { haStyle } from "../../src/resources/styles";
import { PAGES, SIDEBAR } from "../build/import-pages";
-import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
import "./components/page-description";
const GITHUB_DEMO_URL =
@@ -24,7 +24,7 @@ const FAKE_HASS = {
@customElement("ha-gallery")
class HaGallery extends LitElement {
- @property() private _page =
+ @state() private _page =
document.location.hash.substring(1) ||
`${SIDEBAR[0].category}/${SIDEBAR[0].pages![0]}`;
diff --git a/gallery/src/pages/automation/editor-condition.ts b/gallery/src/pages/automation/editor-condition.ts
index 3a43eda71e..ed30c74272 100644
--- a/gallery/src/pages/automation/editor-condition.ts
+++ b/gallery/src/pages/automation/editor-condition.ts
@@ -80,7 +80,7 @@ const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
];
@customElement("demo-automation-editor-condition")
-class DemoHaAutomationEditorCondition extends LitElement {
+export class DemoAutomationEditorCondition extends LitElement {
@state() private hass!: HomeAssistant;
@state() private _disabled = false;
@@ -155,6 +155,6 @@ class DemoHaAutomationEditorCondition extends LitElement {
declare global {
interface HTMLElementTagNameMap {
- "demo-ha-automation-editor-condition": DemoHaAutomationEditorCondition;
+ "demo-automation-editor-condition": DemoAutomationEditorCondition;
}
}
diff --git a/gallery/src/pages/automation/editor-trigger.ts b/gallery/src/pages/automation/editor-trigger.ts
index 30c9721256..1ed11eb0a8 100644
--- a/gallery/src/pages/automation/editor-trigger.ts
+++ b/gallery/src/pages/automation/editor-trigger.ts
@@ -126,7 +126,7 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
];
@customElement("demo-automation-editor-trigger")
-class DemoHaAutomationEditorTrigger extends LitElement {
+export class DemoAutomationEditorTrigger extends LitElement {
@state() private hass!: HomeAssistant;
@state() private _disabled = false;
@@ -201,6 +201,6 @@ class DemoHaAutomationEditorTrigger extends LitElement {
declare global {
interface HTMLElementTagNameMap {
- "demo-ha-automation-editor-trigger": DemoHaAutomationEditorTrigger;
+ "demo-automation-editor-trigger": DemoAutomationEditorTrigger;
}
}
diff --git a/gallery/src/pages/components/ha-control-button.ts b/gallery/src/pages/components/ha-control-button.ts
index 0c3ad180be..908ca808d4 100644
--- a/gallery/src/pages/components/ha-control-button.ts
+++ b/gallery/src/pages/components/ha-control-button.ts
@@ -59,7 +59,7 @@ export class DemoHaBarButton extends LitElement {
diff --git a/gallery/src/pages/components/ha-control-select.ts b/gallery/src/pages/components/ha-control-select.ts
index 2f9eda7564..e0d1e0e345 100644
--- a/gallery/src/pages/components/ha-control-select.ts
+++ b/gallery/src/pages/components/ha-control-select.ts
@@ -135,7 +135,7 @@ export class DemoHaControlSelect extends LitElement {
class=${ifDefined(config.class)}
@value-changed=${this.handleValueChanged}
aria-labelledby=${id}
- disabled=${ifDefined(config.disabled)}
+ ?disabled=${config.disabled}
>
@@ -156,7 +156,7 @@ export class DemoHaControlSelect extends LitElement {
class=${ifDefined(config.class)}
@value-changed=${this.handleValueChanged}
aria-labelledby=${id}
- disabled=${ifDefined(config.disabled)}
+ ?disabled=${config.disabled}
>
`;
diff --git a/gallery/src/pages/components/ha-control-switch.ts b/gallery/src/pages/components/ha-control-switch.ts
index 8311e2a0a5..128a23773f 100644
--- a/gallery/src/pages/components/ha-control-switch.ts
+++ b/gallery/src/pages/components/ha-control-switch.ts
@@ -63,8 +63,8 @@ export class DemoHaControlSwitch extends LitElement {
.pathOn=${mdiLightbulb}
.pathOff=${mdiLightbulbOff}
aria-labelledby=${id}
- disabled=${ifDefined(config.disabled)}
- reversed=${ifDefined(config.reversed)}
+ ?disabled=${config.disabled}
+ ?reversed=${config.reversed}
>
@@ -86,8 +86,8 @@ export class DemoHaControlSwitch extends LitElement {
aria-label=${label}
.pathOn=${mdiGarageOpen}
.pathOff=${mdiGarage}
- disabled=${ifDefined(config.disabled)}
- reversed=${ifDefined(config.reversed)}
+ ?disabled=${config.disabled}
+ ?reversed=${config.reversed}
>
`;
diff --git a/gallery/src/pages/components/ha-expansion-panel.ts b/gallery/src/pages/components/ha-expansion-panel.ts
index 781aa063bb..e739ee7ab1 100644
--- a/gallery/src/pages/components/ha-expansion-panel.ts
+++ b/gallery/src/pages/components/ha-expansion-panel.ts
@@ -3,6 +3,7 @@ import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-expansion-panel";
+import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-markdown";
import "../../components/demo-black-white-row";
import { LONG_TEXT } from "../../data/text";
diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts
index dc8fdb5c51..391b01c210 100644
--- a/gallery/src/pages/components/ha-form.ts
+++ b/gallery/src/pages/components/ha-form.ts
@@ -10,6 +10,7 @@ import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervis
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
+import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
@@ -97,22 +98,25 @@ const DEVICES = [
},
];
-const AREAS = [
+const AREAS: AreaRegistryEntry[] = [
{
area_id: "backyard",
name: "Backyard",
+ icon: null,
picture: null,
aliases: [],
},
{
area_id: "bedroom",
name: "Bedroom",
+ icon: "mdi:bed",
picture: null,
aliases: [],
},
{
area_id: "livingroom",
name: "Livingroom",
+ icon: "mdi:sofa",
picture: null,
aliases: [],
},
diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts
index 004bb3f406..78dbe681bb 100644
--- a/gallery/src/pages/components/ha-selector.ts
+++ b/gallery/src/pages/components/ha-selector.ts
@@ -9,6 +9,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import "../../../../src/components/ha-selector/ha-selector";
import "../../../../src/components/ha-settings-row";
+import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
import { BlueprintInput } from "../../../../src/data/blueprint";
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
import { getEntity } from "../../../../src/fake_data/entity";
@@ -93,22 +94,25 @@ const DEVICES = [
},
];
-const AREAS = [
+const AREAS: AreaRegistryEntry[] = [
{
area_id: "backyard",
name: "Backyard",
+ icon: null,
picture: null,
aliases: [],
},
{
area_id: "bedroom",
name: "Bedroom",
+ icon: "mdi:bed",
picture: null,
aliases: [],
},
{
area_id: "livingroom",
name: "Livingroom",
+ icon: "mdi:sofa",
picture: null,
aliases: [],
},
diff --git a/gallery/src/pages/date-time/date-time-numeric.ts b/gallery/src/pages/date-time/date-time-numeric.ts
index 608b3fc152..a90f6a6131 100644
--- a/gallery/src/pages/date-time/date-time-numeric.ts
+++ b/gallery/src/pages/date-time/date-time-numeric.ts
@@ -1,19 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
+import { formatDateTimeNumeric } from "../../../../src/common/datetime/format_date_time";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
-import { formatDateTimeNumeric } from "../../../../src/common/datetime/format_date_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-date-time-numeric")
export class DemoDateTimeDateTimeNumeric extends LitElement {
diff --git a/gallery/src/pages/date-time/date-time-seconds.ts b/gallery/src/pages/date-time/date-time-seconds.ts
index 5f5b48f989..cc1d518184 100644
--- a/gallery/src/pages/date-time/date-time-seconds.ts
+++ b/gallery/src/pages/date-time/date-time-seconds.ts
@@ -1,19 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
+import { formatDateTimeWithSeconds } from "../../../../src/common/datetime/format_date_time";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
-import { formatDateTimeWithSeconds } from "../../../../src/common/datetime/format_date_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-date-time-seconds")
export class DemoDateTimeDateTimeSeconds extends LitElement {
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 f132f2a047..9b6c8a3807 100644
--- a/gallery/src/pages/date-time/date-time-short-year.ts
+++ b/gallery/src/pages/date-time/date-time-short-year.ts
@@ -1,19 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
+import { formatShortDateTimeWithYear } from "../../../../src/common/datetime/format_date_time";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
-import { formatShortDateTimeWithYear } from "../../../../src/common/datetime/format_date_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-date-time-short-year")
export class DemoDateTimeDateTimeShortYear extends LitElement {
diff --git a/gallery/src/pages/date-time/date-time-short.ts b/gallery/src/pages/date-time/date-time-short.ts
index 21f7eb1294..018e3a84e9 100644
--- a/gallery/src/pages/date-time/date-time-short.ts
+++ b/gallery/src/pages/date-time/date-time-short.ts
@@ -1,19 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
+import { formatShortDateTime } from "../../../../src/common/datetime/format_date_time";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
-import { formatShortDateTime } from "../../../../src/common/datetime/format_date_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-date-time-short")
export class DemoDateTimeDateTimeShort extends LitElement {
diff --git a/gallery/src/pages/date-time/date-time.ts b/gallery/src/pages/date-time/date-time.ts
index 4bb4b75865..3900deef4d 100644
--- a/gallery/src/pages/date-time/date-time.ts
+++ b/gallery/src/pages/date-time/date-time.ts
@@ -1,19 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
+import { formatDateTime } from "../../../../src/common/datetime/format_date_time";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
-import { formatDateTime } from "../../../../src/common/datetime/format_date_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-date-time")
export class DemoDateTimeDateTime extends LitElement {
diff --git a/gallery/src/pages/date-time/time-seconds.ts b/gallery/src/pages/date-time/time-seconds.ts
index 761bc6ed58..be6c1ffa41 100644
--- a/gallery/src/pages/date-time/time-seconds.ts
+++ b/gallery/src/pages/date-time/time-seconds.ts
@@ -1,18 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
-import "../../../../src/components/ha-card";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatTimeWithSeconds } from "../../../../src/common/datetime/format_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
+import "../../../../src/components/ha-card";
+import "../../../../src/components/ha-control-select";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-time-seconds")
export class DemoDateTimeTimeSeconds extends LitElement {
diff --git a/gallery/src/pages/date-time/time-weekday.ts b/gallery/src/pages/date-time/time-weekday.ts
index 8ed5c951f5..68b1a75186 100644
--- a/gallery/src/pages/date-time/time-weekday.ts
+++ b/gallery/src/pages/date-time/time-weekday.ts
@@ -1,18 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
-import "../../../../src/components/ha-card";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatTimeWeekday } from "../../../../src/common/datetime/format_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
+import "../../../../src/components/ha-card";
+import "../../../../src/components/ha-control-select";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-time-weekday")
export class DemoDateTimeTimeWeekday extends LitElement {
diff --git a/gallery/src/pages/date-time/time.ts b/gallery/src/pages/date-time/time.ts
index df7b101653..a02ad389ff 100644
--- a/gallery/src/pages/date-time/time.ts
+++ b/gallery/src/pages/date-time/time.ts
@@ -1,19 +1,20 @@
-import { html, css, LitElement } from "lit";
+import "@material/mwc-list/mwc-list";
+import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators";
+import { formatTime } from "../../../../src/common/datetime/format_time";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
-import { translationMetadata } from "../../../../src/resources/translations-metadata";
-import { formatTime } from "../../../../src/common/datetime/format_time";
-import { timeOptions } from "../../data/date-options";
-import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
+ DateFormat,
+ FirstWeekday,
FrontendLocaleData,
NumberFormat,
TimeFormat,
- DateFormat,
- FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
+import { demoConfig } from "../../../../src/fake_data/demo_config";
+import { translationMetadata } from "../../../../src/resources/translations-metadata";
+import { timeOptions } from "../../data/date-options";
@customElement("demo-date-time-time")
export class DemoDateTimeTime extends LitElement {
diff --git a/gallery/src/pages/lovelace/markdown-card.ts b/gallery/src/pages/lovelace/markdown-card.ts
index 700283b6b8..aa47310b52 100644
--- a/gallery/src/pages/lovelace/markdown-card.ts
+++ b/gallery/src/pages/lovelace/markdown-card.ts
@@ -65,15 +65,23 @@ const CONFIGS = [
>> ...by using additional greater-than signs right next to each other...
> > > ...or with spaces between arrows.
- > **Warning** Hey there
- > This is a warning with a title
+ > [!NOTE]
+ > This is a GitHub note alert
- > **Note**
- > This is a note
+ > [!TIP]
+ > This is a GitHub tip alert
- > **Note**
- > This is a multiline note
- > Lorem ipsum...
+ > [!IMPORTANT]
+ > This is a GitHub important alert
+
+ > [!WARNING]
+ > This is a GitHub warning alert
+
+ > [!CAUTION]
+ > This is a GitHub caution alert
+
+ > [!TIP]
+ > - This is a list entry in GitHub tip alert
## Lists
diff --git a/gallery/src/pages/lovelace/media-player-row.ts b/gallery/src/pages/lovelace/media-player-row.ts
index 44433bc089..eeba128c06 100644
--- a/gallery/src/pages/lovelace/media-player-row.ts
+++ b/gallery/src/pages/lovelace/media-player-row.ts
@@ -55,7 +55,7 @@ const CONFIGS = [
];
@customElement("demo-lovelace-media-player-row")
-class DemoHuiMediaPlayerRow extends LitElement {
+export class DemoLovelaceMediaPlayerRow extends LitElement {
@query("#demos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
@@ -73,6 +73,6 @@ class DemoHuiMediaPlayerRow extends LitElement {
declare global {
interface HTMLElementTagNameMap {
- "demo-lovelace-media-player-rows": DemoHuiMediaPlayerRow;
+ "demo-lovelace-media-player-row": DemoLovelaceMediaPlayerRow;
}
}
diff --git a/gallery/src/pages/lovelace/tile-card.ts b/gallery/src/pages/lovelace/tile-card.ts
index b161b51a52..7113f4d048 100644
--- a/gallery/src/pages/lovelace/tile-card.ts
+++ b/gallery/src/pages/lovelace/tile-card.ts
@@ -79,6 +79,18 @@ const CONFIGS = [
color: pink
`,
},
+ {
+ heading: "Whole tile tap action",
+ config: `
+- type: tile
+ entity: switch.tv_outlet
+ color: pink
+ tap_action:
+ action: toggle
+ icon_tap_action:
+ action: none
+ `,
+ },
{
heading: "Unknown entity",
config: `
diff --git a/gallery/src/pages/misc/entity-state.ts b/gallery/src/pages/misc/entity-state.ts
index 1ab1d7df15..4c2194c95c 100644
--- a/gallery/src/pages/misc/entity-state.ts
+++ b/gallery/src/pages/misc/entity-state.ts
@@ -53,6 +53,7 @@ const SENSOR_DEVICE_CLASSES = [
"volatile_organic_compounds_parts",
"voltage",
"volume",
+ "volume_flow_rate",
"water",
"weight",
"wind_speed",
@@ -344,6 +345,7 @@ export class DemoEntityState extends LitElement {
title: "Icon",
template: (entry) => html`
diff --git a/gallery/src/pages/misc/util-long-press.ts b/gallery/src/pages/misc/util-long-press.ts
index 7ce2106ba2..05b4f6291e 100644
--- a/gallery/src/pages/misc/util-long-press.ts
+++ b/gallery/src/pages/misc/util-long-press.ts
@@ -59,3 +59,9 @@ export class DemoUtilLongPress extends LitElement {
}
`;
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "demo-misc-util-long-press": DemoUtilLongPress;
+ }
+}
diff --git a/gallery/src/pages/more-info/climate.ts b/gallery/src/pages/more-info/climate.ts
index f6216a55b1..6af0d9a48e 100644
--- a/gallery/src/pages/more-info/climate.ts
+++ b/gallery/src/pages/more-info/climate.ts
@@ -92,7 +92,7 @@ const ENTITIES = [
@customElement("demo-more-info-climate")
class DemoMoreInfoClimate extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/cover.ts b/gallery/src/pages/more-info/cover.ts
index 65f9ba4a25..6f1f0a4e97 100644
--- a/gallery/src/pages/more-info/cover.ts
+++ b/gallery/src/pages/more-info/cover.ts
@@ -141,7 +141,7 @@ const ENTITIES = [
@customElement("demo-more-info-cover")
class DemoMoreInfoCover extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/humidifier.ts b/gallery/src/pages/more-info/humidifier.ts
index 024b4dae7a..c95b4433d2 100644
--- a/gallery/src/pages/more-info/humidifier.ts
+++ b/gallery/src/pages/more-info/humidifier.ts
@@ -29,7 +29,7 @@ const ENTITIES = [
@customElement("demo-more-info-humidifier")
class DemoMoreInfoHumidifier extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/input-number.ts b/gallery/src/pages/more-info/input-number.ts
index e070b52e8b..af8c1b5be6 100644
--- a/gallery/src/pages/more-info/input-number.ts
+++ b/gallery/src/pages/more-info/input-number.ts
@@ -32,7 +32,7 @@ const ENTITIES = [
@customElement("demo-more-info-input-number")
class DemoMoreInfoInputNumber extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/input-text.ts b/gallery/src/pages/more-info/input-text.ts
index ab2902a4fe..f82549004e 100644
--- a/gallery/src/pages/more-info/input-text.ts
+++ b/gallery/src/pages/more-info/input-text.ts
@@ -18,7 +18,7 @@ const ENTITIES = [
@customElement("demo-more-info-input-text")
class DemoMoreInfoInputText extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/light.ts b/gallery/src/pages/more-info/light.ts
index 336aeb8ca8..47aa0bf5a8 100644
--- a/gallery/src/pages/more-info/light.ts
+++ b/gallery/src/pages/more-info/light.ts
@@ -140,7 +140,7 @@ const ENTITIES = [
@customElement("demo-more-info-light")
class DemoMoreInfoLight extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/lock.ts b/gallery/src/pages/more-info/lock.ts
index 4fe7e46563..03f0fb8087 100644
--- a/gallery/src/pages/more-info/lock.ts
+++ b/gallery/src/pages/more-info/lock.ts
@@ -21,7 +21,7 @@ const ENTITIES = [
@customElement("demo-more-info-lock")
class DemoMoreInfoLock extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/media-player.ts b/gallery/src/pages/more-info/media-player.ts
index f02cc2ec5a..4df8674182 100644
--- a/gallery/src/pages/more-info/media-player.ts
+++ b/gallery/src/pages/more-info/media-player.ts
@@ -13,7 +13,7 @@ const ENTITIES = createMediaPlayerEntities();
@customElement("demo-more-info-media-player")
class DemoMoreInfoMediaPlayer extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/number.ts b/gallery/src/pages/more-info/number.ts
index c38693596a..29766c8a03 100644
--- a/gallery/src/pages/more-info/number.ts
+++ b/gallery/src/pages/more-info/number.ts
@@ -50,7 +50,7 @@ const ENTITIES = [
@customElement("demo-more-info-number")
class DemoMoreInfoNumber extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/scene.ts b/gallery/src/pages/more-info/scene.ts
index aaa1385e6b..2449c251fa 100644
--- a/gallery/src/pages/more-info/scene.ts
+++ b/gallery/src/pages/more-info/scene.ts
@@ -21,7 +21,7 @@ const ENTITIES = [
@customElement("demo-more-info-scene")
class DemoMoreInfoScene extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/timer.ts b/gallery/src/pages/more-info/timer.ts
index 404b31df03..3dad8d48e7 100644
--- a/gallery/src/pages/more-info/timer.ts
+++ b/gallery/src/pages/more-info/timer.ts
@@ -18,7 +18,7 @@ const ENTITIES = [
@customElement("demo-more-info-timer")
class DemoMoreInfoTimer extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/update.ts b/gallery/src/pages/more-info/update.ts
index 43951d4c5e..1d9056e95e 100644
--- a/gallery/src/pages/more-info/update.ts
+++ b/gallery/src/pages/more-info/update.ts
@@ -146,7 +146,7 @@ const ENTITIES = [
@customElement("demo-more-info-update")
class DemoMoreInfoUpdate extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/vacuum.ts b/gallery/src/pages/more-info/vacuum.ts
index e1a62f0a26..3282dd59d5 100644
--- a/gallery/src/pages/more-info/vacuum.ts
+++ b/gallery/src/pages/more-info/vacuum.ts
@@ -22,7 +22,7 @@ const ENTITIES = [
@customElement("demo-more-info-vacuum")
class DemoMoreInfoVacuum extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/gallery/src/pages/more-info/water-heater.ts b/gallery/src/pages/more-info/water-heater.ts
index 8210dcac55..a6f7b6f335 100644
--- a/gallery/src/pages/more-info/water-heater.ts
+++ b/gallery/src/pages/more-info/water-heater.ts
@@ -42,7 +42,7 @@ const ENTITIES = [
@customElement("demo-more-info-water-heater")
class DemoMoreInfoWaterHeater extends LitElement {
- @property() public hass!: MockHomeAssistant;
+ @property({ attribute: false }) public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
diff --git a/hassio/src/addon-store/hassio-addon-repository.ts b/hassio/src/addon-store/hassio-addon-repository.ts
index 477a4361ab..3c7ee01566 100644
--- a/hassio/src/addon-store/hassio-addon-repository.ts
+++ b/hassio/src/addon-store/hassio-addon-repository.ts
@@ -140,3 +140,9 @@ export class HassioAddonRepositoryEl extends LitElement {
];
}
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hassio-addon-repository": HassioAddonRepositoryEl;
+ }
+}
diff --git a/hassio/src/addon-store/hassio-addon-store.ts b/hassio/src/addon-store/hassio-addon-store.ts
index a918c34820..76f0639e49 100644
--- a/hassio/src/addon-store/hassio-addon-store.ts
+++ b/hassio/src/addon-store/hassio-addon-store.ts
@@ -56,7 +56,7 @@ export class HassioAddonStore extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public route!: Route;
@@ -243,8 +243,16 @@ export class HassioAddonStore extends LitElement {
}
.advanced a {
margin-left: 0.5em;
+ margin-inline-start: 0.5em;
+ margin-inline-end: initial;
color: var(--primary-color);
}
`;
}
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hassio-addon-store": HassioAddonStore;
+ }
+}
diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts
index 75ff632c15..9cd32b64f7 100644
--- a/hassio/src/addon-view/config/hassio-addon-config.ts
+++ b/hassio/src/addon-view/config/hassio-addon-config.ts
@@ -65,9 +65,9 @@ class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
- @property({ type: Boolean }) private _configHasChanged = false;
+ @state() private _configHasChanged = false;
- @property({ type: Boolean }) private _valid = true;
+ @state() private _valid = true;
@state() private _canShowSchema = false;
diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts
index 4bb888290f..6c6ec8f12b 100644
--- a/hassio/src/addon-view/hassio-addon-dashboard.ts
+++ b/hassio/src/addon-view/hassio-addon-dashboard.ts
@@ -51,7 +51,7 @@ class HassioAddonDashboard extends LitElement {
| HassioAddonDetails
| StoreAddonDetails;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@state() private _error?: string;
@@ -250,7 +250,9 @@ class HassioAddonDashboard extends LitElement {
}
if (path === "uninstall") {
- window.history.back();
+ if (this.isConnected) {
+ navigate(this._backPath);
+ }
} else if (path === "install") {
this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug);
} else {
diff --git a/hassio/src/addon-view/info/hassio-addon-info-tab.ts b/hassio/src/addon-view/info/hassio-addon-info-tab.ts
index 85bd0f9e50..fa8333d9f7 100644
--- a/hassio/src/addon-view/info/hassio-addon-info-tab.ts
+++ b/hassio/src/addon-view/info/hassio-addon-info-tab.ts
@@ -10,7 +10,7 @@ import "./hassio-addon-info";
@customElement("hassio-addon-info-tab")
class HassioAddonInfoDashboard extends LitElement {
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public route!: Route;
diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts
index ae59d0af6b..8b183fc51d 100644
--- a/hassio/src/addon-view/info/hassio-addon-info.ts
+++ b/hassio/src/addon-view/info/hassio-addon-info.ts
@@ -99,7 +99,7 @@ const RATING_ICON = {
@customElement("hassio-addon-info")
class HassioAddonInfo extends LitElement {
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public route!: Route;
@@ -1188,11 +1188,13 @@ class HassioAddonInfo extends LitElement {
}
.addon-header {
padding-left: 8px;
+ padding-inline-start: 8px;
+ padding-inline-end: initial;
font-size: 24px;
color: var(--ha-card-header-color, --primary-text-color);
}
.addon-version {
- float: right;
+ float: var(--float-end);
font-size: 15px;
vertical-align: middle;
}
diff --git a/hassio/src/backups/hassio-backups.ts b/hassio/src/backups/hassio-backups.ts
index 943e6e6c64..64c735d977 100644
--- a/hassio/src/backups/hassio-backups.ts
+++ b/hassio/src/backups/hassio-backups.ts
@@ -59,11 +59,11 @@ export class HassioBackups extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
- @property({ type: Object }) public route!: Route;
+ @property({ attribute: false }) public route!: Route;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
- @property({ type: Boolean }) public isWide!: boolean;
+ @property({ type: Boolean }) public isWide = false;
@state() private _selectedBackups: string[] = [];
@@ -395,6 +395,8 @@ export class HassioBackups extends LitElement {
.selected-txt {
font-weight: bold;
padding-left: 16px;
+ padding-inline-start: 16px;
+ padding-inline-end: initial;
color: var(--primary-text-color);
}
.table-header .selected-txt {
@@ -405,6 +407,8 @@ export class HassioBackups extends LitElement {
}
.header-toolbar .header-btns {
margin-right: -12px;
+ margin-inline-end: -12px;
+ margin-inline-start: initial;
}
.header-btns > mwc-button,
.header-btns > ha-icon-button {
diff --git a/hassio/src/components/hassio-card-content.ts b/hassio/src/components/hassio-card-content.ts
index d1f804ed60..3222525dd5 100644
--- a/hassio/src/components/hassio-card-content.ts
+++ b/hassio/src/components/hassio-card-content.ts
@@ -60,6 +60,10 @@ class HassioCardContent extends LitElement {
static get styles(): CSSResultGroup {
return css`
+ :host {
+ direction: ltr;
+ }
+
ha-svg-icon {
margin-right: 24px;
margin-left: 8px;
diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts
index 8174debcef..ddbde0f4f3 100644
--- a/hassio/src/components/supervisor-backup-content.ts
+++ b/hassio/src/components/supervisor-backup-content.ts
@@ -1,6 +1,4 @@
import { mdiFolder, mdiPuzzle } from "@mdi/js";
-import "@polymer/paper-input/paper-input";
-import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
CSSResultGroup,
LitElement,
@@ -16,6 +14,7 @@ import { formatDateTime } from "../../../src/common/datetime/format_date_time";
import { LocalizeFunc } from "../../../src/common/translations/localize";
import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-formfield";
+import "../../../src/components/ha-textfield";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
import {
@@ -25,12 +24,9 @@ import {
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { mdiHomeAssistant } from "../../../src/resources/home-assistant-logo-svg";
-import {
- HomeAssistant,
- TranslationDict,
- ValueChangedEvent,
-} from "../../../src/types";
+import { HomeAssistant, TranslationDict } from "../../../src/types";
import "./supervisor-formfield-label";
+import type { HaTextField } from "../../../src/components/ha-textfield";
type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] &
keyof TranslationDict["ui"]["panel"]["page-onboarding"]["restore"];
@@ -76,7 +72,7 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
- @property() public localize?: LocalizeFunc;
+ @property({ attribute: false }) public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor;
@@ -100,7 +96,7 @@ export class SupervisorBackupContent extends LitElement {
@property() public confirmBackupPassword = "";
- @query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
+ @query("ha-textfield, ha-radio, ha-checkbox", true) private _focusTarget;
public willUpdate(changedProps) {
super.willUpdate(changedProps);
@@ -151,13 +147,13 @@ export class SupervisorBackupContent extends LitElement {
)
: this.backup.date}
`
- : html`
- `}
+ `}
${!this.backup || this.backup.type === "full"
? html`
${this._interface![version].method === "static"
? html`
-
-
-
+
-
-
+
-
+
`
: ""}
@@ -517,11 +517,11 @@ export class DialogHassioNetwork
this.requestUpdate("_wifiConfiguration");
}
- private _handleInputValueChanged(ev: CustomEvent): void {
- const value: string | null | undefined = (ev.target as PaperInputElement)
- .value;
+ private _handleInputValueChanged(ev: Event): void {
+ const source = ev.target as HaTextField;
+ const value = source.value;
const version = (ev.target as any).version as "ipv4" | "ipv6";
- const id = (ev.target as PaperInputElement).id;
+ const id = source.id;
if (
!value ||
@@ -535,10 +535,10 @@ export class DialogHassioNetwork
this._interface[version]![id] = value;
}
- private _handleInputValueChangedWifi(ev: CustomEvent): void {
- const value: string | null | undefined = (ev.target as PaperInputElement)
- .value;
- const id = (ev.target as PaperInputElement).id;
+ private _handleInputValueChangedWifi(ev: Event): void {
+ const source = ev.target as HaTextField;
+ const value = source.value;
+ const id = source.id;
if (
!value ||
@@ -597,6 +597,8 @@ export class DialogHassioNetwork
mwc-button.scan {
margin-left: 8px;
+ margin-inline-start: 8px;
+ margin-inline-end: initial;
}
.container {
@@ -630,7 +632,7 @@ export class DialogHassioNetwork
--expansion-panel-summary-padding: 0 16px;
margin: 4px 0;
}
- paper-input {
+ ha-textfield {
padding: 0 14px;
}
mwc-list-item {
diff --git a/hassio/src/dialogs/registries/dialog-hassio-registries.ts b/hassio/src/dialogs/registries/dialog-hassio-registries.ts
index d623ebf3b8..0f670d2bc9 100644
--- a/hassio/src/dialogs/registries/dialog-hassio-registries.ts
+++ b/hassio/src/dialogs/registries/dialog-hassio-registries.ts
@@ -157,11 +157,10 @@ class HassioRegistriesDialog extends LitElement {
}
public focus(): void {
- this.updateComplete.then(
- () =>
- (
- this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
- )?.focus()
+ this.updateComplete.then(() =>
+ (
+ this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
+ )?.focus()
);
}
@@ -230,6 +229,8 @@ class HassioRegistriesDialog extends LitElement {
ha-icon-button {
color: var(--error-color);
margin-right: -10px;
+ margin-inline-end: -10px;
+ margin-inline-start: initial;
}
`,
];
diff --git a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts
index 98dfc32b24..74ce8d5a6a 100644
--- a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts
+++ b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts
@@ -1,7 +1,5 @@
import "@material/mwc-button/mwc-button";
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
-import "@polymer/paper-input/paper-input";
-import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
@@ -27,12 +25,14 @@ import {
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
+import type { HaTextField } from "../../../../src/components/ha-textfield";
+import "../../../../src/components/ha-textfield";
@customElement("dialog-hassio-repositories")
class HassioRepositoriesDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
- @query("#repository_input", true) private _optionInput?: PaperInputElement;
+ @query("#repository_input", true) private _optionInput?: HaTextField;
@state() private _repositories?: HassioAddonRepository[];
@@ -145,7 +145,7 @@ class HassioRepositoriesDialog extends LitElement {
)
: html` No repositories `}
-
+ >
${this._processing
? html`
- (
- this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
- )?.focus()
+ this.updateComplete.then(() =>
+ (
+ this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
+ )?.focus()
);
}
diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts
index e9769e807c..04f2dbf3c0 100644
--- a/hassio/src/hassio-main.ts
+++ b/hassio/src/hassio-main.ts
@@ -21,7 +21,7 @@ export class HassioMain extends SupervisorBaseElement {
@property({ attribute: false }) public panel!: HassioPanelInfo;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
diff --git a/hassio/src/hassio-panel-router.ts b/hassio/src/hassio-panel-router.ts
index 201f3a6c71..310f191e61 100644
--- a/hassio/src/hassio-panel-router.ts
+++ b/hassio/src/hassio-panel-router.ts
@@ -16,7 +16,7 @@ class HassioPanelRouter extends HassRouterPage {
@property({ attribute: false }) public route!: Route;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
protected routerOptions: RouterOptions = {
beforeRender: (page: string) =>
diff --git a/hassio/src/hassio-panel.ts b/hassio/src/hassio-panel.ts
index c2d3295759..36f570fdb3 100644
--- a/hassio/src/hassio-panel.ts
+++ b/hassio/src/hassio-panel.ts
@@ -14,7 +14,7 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public route!: Route;
diff --git a/hassio/src/hassio-router.ts b/hassio/src/hassio-router.ts
index b100fccdbf..a56f8088d5 100644
--- a/hassio/src/hassio-router.ts
+++ b/hassio/src/hassio-router.ts
@@ -18,7 +18,7 @@ class HassioRouter extends HassRouterPage {
@property({ attribute: false }) public panel!: HassioPanelInfo;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
diff --git a/hassio/src/ingress-view/hassio-ingress-view.ts b/hassio/src/ingress-view/hassio-ingress-view.ts
index 45fd998b4b..1dd895bdfe 100644
--- a/hassio/src/ingress-view/hassio-ingress-view.ts
+++ b/hassio/src/ingress-view/hassio-ingress-view.ts
@@ -360,7 +360,7 @@ class HassioIngressView extends LitElement {
}
.main-title {
- margin: 0 0 0 24px;
+ margin: var(--margin-title);
line-height: 20px;
flex-grow: 1;
}
diff --git a/hassio/src/resources/hassio-style.ts b/hassio/src/resources/hassio-style.ts
index 495d5d3e2e..8bfd9f30c6 100644
--- a/hassio/src/resources/hassio-style.ts
+++ b/hassio/src/resources/hassio-style.ts
@@ -19,10 +19,14 @@ export const hassioStyle = css`
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
padding-left: 8px;
+ padding-inline-start: 8px;
+ padding-inline-end: initial;
}
.description {
margin-top: 4px;
padding-left: 8px;
+ padding-inline-start: 8px;
+ padding-inline-end: initial;
}
.card-group {
display: grid;
diff --git a/hassio/src/supervisor-base-element.ts b/hassio/src/supervisor-base-element.ts
index 9402728160..86dc819988 100644
--- a/hassio/src/supervisor-base-element.ts
+++ b/hassio/src/supervisor-base-element.ts
@@ -29,6 +29,10 @@ import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { HomeAssistant, Route } from "../../src/types";
import { getTranslation } from "../../src/util/common-translation";
+import {
+ computeRTLDirection,
+ setDirectionStyles,
+} from "../../src/common/util/compute_rtl";
declare global {
interface HASSDomEvents {
@@ -95,6 +99,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
if (changedProperties.has("_language") || !this.hasUpdated) {
this._initializeLocalize();
+ this._applyDirection(this.hass);
}
}
@@ -215,4 +220,9 @@ export class SupervisorBaseElement extends urlSyncMixin(
);
}
}
+
+ private _applyDirection(hass: HomeAssistant) {
+ const direction = computeRTLDirection(hass);
+ setDirectionStyles(direction, this);
+ }
}
diff --git a/hassio/src/system/hassio-supervisor-log.ts b/hassio/src/system/hassio-supervisor-log.ts
index a2e1d1d75e..6d42656616 100644
--- a/hassio/src/system/hassio-supervisor-log.ts
+++ b/hassio/src/system/hassio-supervisor-log.ts
@@ -1,9 +1,10 @@
-import "../../../src/components/ha-ansi-to-html";
import "@material/mwc-button";
+import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
+import "../../../src/components/ha-ansi-to-html";
import "../../../src/components/ha-card";
import "../../../src/components/ha-select";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
diff --git a/hassio/src/system/hassio-system.ts b/hassio/src/system/hassio-system.ts
index bbf3d72758..1f64271017 100644
--- a/hassio/src/system/hassio-system.ts
+++ b/hassio/src/system/hassio-system.ts
@@ -18,7 +18,7 @@ class HassioSystem extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public route!: Route;
@@ -73,6 +73,8 @@ class HassioSystem extends LitElement {
color: var(--primary-text-color);
font-size: 2em;
padding-left: 8px;
+ padding-inline-start: 8px;
+ padding-inline-end: initial;
margin-bottom: 8px;
}
hassio-supervisor-log {
diff --git a/hassio/src/update-available/update-available-card.ts b/hassio/src/update-available/update-available-card.ts
index b91fe19f81..d2c22ac0e1 100644
--- a/hassio/src/update-available/update-available-card.ts
+++ b/hassio/src/update-available/update-available-card.ts
@@ -94,7 +94,7 @@ class UpdateAvailableCard extends LitElement {
@property({ attribute: false }) public route!: Route;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public addonSlug?: string;
diff --git a/hassio/src/update-available/update-available-dashboard.ts b/hassio/src/update-available/update-available-dashboard.ts
index 9e1598527c..9ebb25a4cd 100644
--- a/hassio/src/update-available/update-available-dashboard.ts
+++ b/hassio/src/update-available/update-available-dashboard.ts
@@ -11,7 +11,7 @@ class UpdateAvailableDashboard extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
- @property({ type: Boolean }) public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public route!: Route;
diff --git a/lint-staged.config.js b/lint-staged.config.js
index 8740aaab19..a1bd97c88d 100644
--- a/lint-staged.config.js
+++ b/lint-staged.config.js
@@ -2,6 +2,7 @@ export default {
"*.?(c|m){js,ts}": [
"eslint --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --fix",
"prettier --cache --write",
+ "lit-analyzer --quiet",
],
"*.{json,css,md,markdown,html,y?aml}": "prettier --cache --write",
"translations/*/*.json": (files) =>
diff --git a/package.json b/package.json
index 0d2a855179..204340c432 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,8 @@
"lint:prettier": "prettier . --cache --check",
"format:prettier": "prettier . --cache --write",
"lint:types": "tsc",
- "lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
- "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
+ "lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"",
+ "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit",
"format": "yarn run format:eslint && yarn run format:prettier",
"postinstall": "husky install",
"prepack": "pinst --disable",
@@ -25,24 +25,24 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
- "@babel/runtime": "7.23.6",
+ "@babel/runtime": "7.23.9",
"@braintree/sanitize-url": "7.0.0",
- "@codemirror/autocomplete": "6.11.1",
- "@codemirror/commands": "6.3.2",
- "@codemirror/language": "6.9.3",
+ "@codemirror/autocomplete": "6.12.0",
+ "@codemirror/commands": "6.3.3",
+ "@codemirror/language": "6.10.0",
"@codemirror/legacy-modes": "6.3.3",
"@codemirror/search": "6.5.5",
- "@codemirror/state": "6.3.3",
- "@codemirror/view": "6.22.3",
+ "@codemirror/state": "6.4.0",
+ "@codemirror/view": "6.23.1",
"@egjs/hammerjs": "2.0.17",
- "@formatjs/intl-datetimeformat": "6.12.0",
- "@formatjs/intl-displaynames": "6.6.4",
+ "@formatjs/intl-datetimeformat": "6.12.2",
+ "@formatjs/intl-displaynames": "6.6.6",
"@formatjs/intl-getcanonicallocales": "2.3.0",
- "@formatjs/intl-listformat": "7.5.3",
- "@formatjs/intl-locale": "3.4.3",
- "@formatjs/intl-numberformat": "8.9.0",
- "@formatjs/intl-pluralrules": "5.2.10",
- "@formatjs/intl-relativetimeformat": "11.2.10",
+ "@formatjs/intl-listformat": "7.5.5",
+ "@formatjs/intl-locale": "3.4.5",
+ "@formatjs/intl-numberformat": "8.10.0",
+ "@formatjs/intl-pluralrules": "5.2.12",
+ "@formatjs/intl-relativetimeformat": "11.2.12",
"@fullcalendar/core": "6.1.10",
"@fullcalendar/daygrid": "6.1.10",
"@fullcalendar/interaction": "6.1.10",
@@ -53,8 +53,8 @@
"@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.6",
"@lit-labs/observers": "2.0.2",
- "@lit-labs/virtualizer": "2.0.11",
- "@lrnwebcomponents/simple-tooltip": "7.0.18",
+ "@lit-labs/virtualizer": "2.0.12",
+ "@lrnwebcomponents/simple-tooltip": "8.0.0",
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
"@material/mwc-base": "0.27.0",
@@ -80,18 +80,17 @@
"@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
- "@material/web": "=1.1.1",
+ "@material/web": "=1.2.0",
"@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47",
- "@polymer/paper-input": "3.2.1",
"@polymer/paper-item": "3.0.1",
"@polymer/paper-listbox": "3.0.1",
"@polymer/paper-tabs": "3.1.0",
"@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
- "@vaadin/combo-box": "24.3.2",
- "@vaadin/vaadin-themable-mixin": "24.3.2",
+ "@vaadin/combo-box": "24.3.4",
+ "@vaadin/vaadin-themable-mixin": "24.3.4",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -101,7 +100,7 @@
"app-datepicker": "5.1.1",
"chart.js": "4.4.1",
"comlink": "4.4.1",
- "core-js": "3.34.0",
+ "core-js": "3.35.1",
"cropperjs": "1.6.1",
"date-fns": "2.30.0",
"date-fns-tz": "2.0.0",
@@ -110,16 +109,16 @@
"element-internals-polyfill": "1.3.10",
"fuse.js": "7.0.0",
"google-timezones-json": "1.2.0",
- "hls.js": "1.4.14",
+ "hls.js": "1.5.2",
"home-assistant-js-websocket": "9.1.0",
"idb-keyval": "6.2.1",
- "intl-messageformat": "10.5.8",
+ "intl-messageformat": "10.5.11",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "1.0.4",
"lit": "2.8.0",
"luxon": "3.4.4",
- "marked": "11.1.1",
+ "marked": "11.2.0",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
@@ -128,7 +127,7 @@
"qrcode": "1.5.3",
"roboto-fontface": "0.10.0",
"rrule": "2.8.1",
- "sortablejs": "1.15.1",
+ "sortablejs": "1.15.2",
"stacktrace-js": "2.0.2",
"superstruct": "1.0.3",
"tinykeys": "2.1.0",
@@ -150,13 +149,13 @@
"xss": "1.0.14"
},
"devDependencies": {
- "@babel/core": "7.23.6",
- "@babel/helper-define-polyfill-provider": "0.4.4",
- "@babel/plugin-proposal-decorators": "7.23.6",
- "@babel/plugin-transform-runtime": "7.23.6",
- "@babel/preset-env": "7.23.6",
+ "@babel/core": "7.23.9",
+ "@babel/helper-define-polyfill-provider": "0.5.0",
+ "@babel/plugin-proposal-decorators": "7.23.9",
+ "@babel/plugin-transform-runtime": "7.23.9",
+ "@babel/preset-env": "7.23.9",
"@babel/preset-typescript": "7.23.3",
- "@bundle-stats/plugin-webpack-filter": "4.8.3",
+ "@bundle-stats/plugin-webpack-filter": "4.9.2",
"@koa/cors": "5.0.0",
"@lokalise/node-api": "12.1.0",
"@octokit/auth-oauth-device": "6.0.1",
@@ -169,28 +168,28 @@
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-replace": "5.0.5",
"@types/babel__plugin-transform-runtime": "7.9.5",
- "@types/chromecast-caf-receiver": "6.0.12",
+ "@types/chromecast-caf-receiver": "6.0.13",
"@types/chromecast-caf-sender": "1.0.8",
"@types/glob": "8.1.0",
"@types/html-minifier-terser": "7.0.2",
"@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.8",
"@types/leaflet-draw": "1.0.11",
- "@types/luxon": "3.3.7",
+ "@types/luxon": "3.4.2",
"@types/mocha": "10.0.6",
"@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4",
"@types/sortablejs": "1.15.7",
- "@types/tar": "6.1.10",
+ "@types/tar": "6.1.11",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
- "@typescript-eslint/eslint-plugin": "6.16.0",
- "@typescript-eslint/parser": "6.16.0",
+ "@typescript-eslint/eslint-plugin": "6.19.1",
+ "@typescript-eslint/parser": "6.19.1",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
"babel-plugin-template-html-minifier": "4.1.0",
- "chai": "5.0.0",
+ "chai": "5.0.3",
"del": "7.1.0",
"eslint": "8.56.0",
"eslint-config-airbnb-base": "15.0.0",
@@ -213,19 +212,19 @@
"gulp-rename": "2.0.0",
"gulp-zopfli-green": "6.0.1",
"html-minifier-terser": "7.2.0",
- "husky": "8.0.3",
+ "husky": "9.0.6",
"instant-mocha": "1.5.2",
"jszip": "3.10.1",
"lint-staged": "15.2.0",
- "lit-analyzer": "2.0.2",
+ "lit-analyzer": "2.0.3",
"lodash.template": "4.5.0",
"magic-string": "0.30.5",
"map-stream": "0.0.7",
"mocha": "10.2.0",
"object-hash": "3.0.0",
- "open": "10.0.2",
+ "open": "10.0.3",
"pinst": "3.0.0",
- "prettier": "3.1.1",
+ "prettier": "3.2.4",
"rollup": "2.79.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
@@ -233,14 +232,15 @@
"serve-handler": "6.1.5",
"sinon": "17.0.1",
"source-map-url": "0.4.1",
- "systemjs": "6.14.2",
+ "systemjs": "6.14.3",
"tar": "6.2.0",
"terser-webpack-plugin": "5.3.10",
- "ts-lit-plugin": "2.0.1",
+ "transform-async-modules-webpack-plugin": "1.0.2",
+ "ts-lit-plugin": "2.0.2",
"typescript": "5.3.3",
"vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0",
- "webpack": "5.89.0",
+ "webpack": "5.90.0",
"webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1",
"webpack-manifest-plugin": "5.0.0",
@@ -255,7 +255,7 @@
"lit": "2.8.0",
"clean-css": "5.3.3",
"@lit/reactive-element": "1.6.3",
- "sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
+ "sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
},
"packageManager": "yarn@4.0.2"
diff --git a/pyproject.toml b/pyproject.toml
index d9414825fa..c1f29c43b5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
-version = "20240104.0"
+version = "20240131.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
diff --git a/script/version_bump.cjs b/script/version_bump.js
similarity index 83%
rename from script/version_bump.cjs
rename to script/version_bump.js
index a1c8f9c9be..fbba50b6a7 100755
--- a/script/version_bump.cjs
+++ b/script/version_bump.js
@@ -1,7 +1,10 @@
#!/usr/bin/env node
-const fs = require("fs");
-const util = require("util");
-const exec = util.promisify(require("child_process").exec);
+/* eslint-disable no-console */
+import fs from "fs";
+import util from "util";
+import child_process from "child_process";
+
+const exec = util.promisify(child_process.exec);
function patch(version) {
const parts = version.split(".");
@@ -18,7 +21,7 @@ function today() {
function auto(version) {
const todayVersion = today();
- if (todayVersion !== version) {
+ if (todayVersion.split(".")[0] !== version.split(".")[0]) {
return todayVersion;
}
return patch(version);
@@ -44,7 +47,7 @@ async function main(args) {
commit = true;
} else {
method = args.length > 0 && methods[args[0]];
- commit = args.length > 1 && args[1] == "--commit";
+ commit = args.length > 1 && args[1] === "--commit";
}
if (!method) {
diff --git a/src/auth/ha-auth-flow.ts b/src/auth/ha-auth-flow.ts
index 00070a57b6..db5b233073 100644
--- a/src/auth/ha-auth-flow.ts
+++ b/src/auth/ha-auth-flow.ts
@@ -21,7 +21,6 @@ import {
DataEntryFlowStepForm,
} from "../data/data_entry_flow";
import "./ha-auth-form";
-import { fireEvent } from "../common/dom/fire_event";
type State = "loading" | "error" | "step";
@@ -35,11 +34,13 @@ export class HaAuthFlow extends LitElement {
@property() public oauth2State?: string;
- @property() public localize!: LocalizeFunc;
+ @property({ attribute: false }) public localize!: LocalizeFunc;
@property({ attribute: false }) public step?: DataEntryFlowStep;
- @property({ type: Boolean }) private storeToken = false;
+ @property({ type: Boolean }) public initStoreToken = false;
+
+ @state() private _storeToken = false;
@state() private _state: State = "loading";
@@ -56,6 +57,10 @@ export class HaAuthFlow extends LitElement {
willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
+ if (!this.hasUpdated) {
+ this._storeToken = this.initStoreToken;
+ }
+
if (!changedProps.has("step")) {
return;
}
@@ -88,6 +93,8 @@ export class HaAuthFlow extends LitElement {
- ${this._error
- ? html`${this._error}`
- : nothing}
- ${this._step
- ? html``
- : this._selectedUser
- ? html``
- : html`
- ${this.localize("ui.panel.page-authorize.welcome_home")}
-
- ${this.localize("ui.panel.page-authorize.who_is_logging_in")}
-
- ${userIds.map((userId) => {
- const person = this._persons![userId];
-
- return html`
`;
- })}
-
-
-
-
`}
- `;
- }
-
- protected firstUpdated(changedProps: PropertyValues) {
- super.firstUpdated(changedProps);
-
- this.addEventListener("keypress", (ev) => {
- if (ev.key === "Enter") {
- this._handleSubmit(ev);
- }
- });
- }
-
- protected updated(changedProps: PropertyValues) {
- if (changedProps.has("_selectedUser") && this._selectedUser) {
- const passwordElement = this.renderRoot.querySelector(
- "#password"
- ) as HaAuthTextField;
- passwordElement.updateComplete.then(() => {
- passwordElement.focus();
- });
- }
- }
-
- private async _load() {
- try {
- this._persons = await listUserPersons();
- } catch {
- this._persons = {};
- this._error = "Failed to fetch persons";
- }
- }
-
- private _restart() {
- this._selectedUser = undefined;
- this._error = undefined;
- }
-
- private _toggleUnmaskedPassword() {
- this._unmaskedPassword = !this._unmaskedPassword;
- }
-
- private _handleKeyUp(ev: KeyboardEvent) {
- if (ev.key === "Enter" || ev.key === " ") {
- this._personSelected(ev);
- }
- }
-
- private async _personSelected(ev) {
- const userId = ev.currentTarget.userId;
- if (
- this.ownInstance &&
- this.authProviders?.find((prv) => prv.type === "trusted_networks")
- ) {
- try {
- const flowResponse = await createLoginFlow(
- this.clientId,
- this.redirectUri,
- ["trusted_networks", null]
- );
-
- const data = await flowResponse.json();
-
- if (data.type === "create_entry") {
- redirectWithAuthCode(
- this.redirectUri!,
- data.result,
- this.oauth2State,
- true
- );
- return;
- }
-
- try {
- if (!data.data_schema[0].options.find((opt) => opt[0] === userId)) {
- throw new Error("User not available");
- }
-
- const postData = { user: userId, client_id: this.clientId };
-
- const response = await submitLoginFlow(data.flow_id, postData);
-
- if (response.ok) {
- const result = await response.json();
-
- if (result.type === "create_entry") {
- redirectWithAuthCode(
- this.redirectUri!,
- result.result,
- this.oauth2State,
- true
- );
- return;
- }
- } else {
- throw new Error("Invalid response");
- }
- } catch {
- deleteLoginFlow(data.flow_id).catch((err) => {
- // eslint-disable-next-line no-console
- console.error("Error delete obsoleted auth flow", err);
- });
- }
- } catch {
- // Ignore
- }
- }
- this._selectedUser = userId;
- }
-
- private async _handleSubmit(ev: Event) {
- ev.preventDefault();
-
- if (!this.authProvider?.users || !this._selectedUser) {
- return;
- }
-
- this._error = undefined;
- this._submitting = true;
-
- const flowResponse = await createLoginFlow(
- this.clientId,
- this.redirectUri,
- ["homeassistant", null]
- );
-
- const data = await flowResponse.json();
-
- const postData = {
- username: this.authProvider.users[this._selectedUser],
- password: (this.renderRoot.querySelector("#password") as HaAuthTextField)
- .value,
- client_id: this.clientId,
- };
-
- try {
- const response = await submitLoginFlow(data.flow_id, postData);
-
- const newStep = await response.json();
-
- if (response.status === 403) {
- this._error = newStep.message;
- return;
- }
-
- if (newStep.type === "create_entry") {
- redirectWithAuthCode(
- this.redirectUri!,
- newStep.result,
- this.oauth2State,
- true
- );
- return;
- }
-
- if (newStep.errors.base) {
- this._error = this.localize(
- `ui.panel.page-authorize.form.providers.homeassistant.error.${newStep.errors.base}`
- );
- throw new Error(this._error);
- }
-
- this._step = newStep;
- } catch {
- deleteLoginFlow(data.flow_id).catch((err) => {
- // eslint-disable-next-line no-console
- console.error("Error delete obsoleted auth flow", err);
- });
- if (!this._error) {
- this._error = this.localize(
- "ui.panel.page-authorize.form.unknown_error"
- );
- }
- } finally {
- this._submitting = false;
- }
- }
-
- private _otherLogin() {
- fireEvent(this, "default-login-flow", { value: true });
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "ha-local-auth-flow": HaLocalAuthFlow;
- }
- interface HASSDomEvents {
- "default-login-flow": { value: boolean };
- }
-}
diff --git a/src/auth/ha-pick-auth-provider.ts b/src/auth/ha-pick-auth-provider.ts
index 90a5c238b6..a814788b0a 100644
--- a/src/auth/ha-pick-auth-provider.ts
+++ b/src/auth/ha-pick-auth-provider.ts
@@ -8,6 +8,9 @@ import "../components/ha-list-item";
import { AuthProvider } from "../data/auth";
declare global {
+ interface HTMLElementTagNameMap {
+ "ha-pick-auth-provider": HaPickAuthProvider;
+ }
interface HASSDomEvents {
"pick-auth-provider": AuthProvider;
}
@@ -15,9 +18,9 @@ declare global {
@customElement("ha-pick-auth-provider")
export class HaPickAuthProvider extends LitElement {
- @property() public authProviders: AuthProvider[] = [];
+ @property({ attribute: false }) public authProviders: AuthProvider[] = [];
- @property() public localize!: LocalizeFunc;
+ @property({ attribute: false }) public localize!: LocalizeFunc;
protected render() {
return html`
diff --git a/src/common/const.ts b/src/common/const.ts
index 2e7d2f7c77..48545d06aa 100644
--- a/src/common/const.ts
+++ b/src/common/const.ts
@@ -1,6 +1,7 @@
/** Constants to be used in the frontend. */
import {
+ mdiAccount,
mdiAirFilter,
mdiAlert,
mdiAngleAcute,
@@ -18,7 +19,6 @@ import {
mdiChatSleep,
mdiClipboardList,
mdiClock,
- mdiCloudUpload,
mdiCog,
mdiCommentAlert,
mdiCounter,
@@ -48,11 +48,14 @@ import {
mdiMoleculeCo2,
mdiPalette,
mdiPh,
+ mdiPipe,
mdiProgressClock,
mdiRayVertex,
mdiRemote,
+ mdiRobot,
mdiRobotMower,
mdiRobotVacuum,
+ mdiRoomService,
mdiScriptText,
mdiSineWave,
mdiSpeakerMessage,
@@ -62,6 +65,7 @@ import {
mdiThermometerLines,
mdiThermostat,
mdiTimerOutline,
+ mdiToggleSwitch,
mdiTransmissionTower,
mdiWater,
mdiWaterPercent,
@@ -70,6 +74,7 @@ import {
mdiWeatherRainy,
mdiWeatherWindy,
mdiWeight,
+ mdiWhiteBalanceSunny,
mdiWifi,
} from "@mdi/js";
@@ -79,6 +84,9 @@ import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
// Arrays with values should be alphabetically sorted if order doesn't matter.
// Each constant should have a description what it is supposed to be used for.
+/** Icon to use when no icon specified for service. */
+export const DEFAULT_SERVICE_ICON = mdiRoomService;
+
/** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = mdiBookmark;
@@ -86,20 +94,23 @@ export const DEFAULT_DOMAIN_ICON = mdiBookmark;
export const FIXED_DOMAIN_ICONS = {
air_quality: mdiAirFilter,
alert: mdiAlert,
+ automation: mdiRobot,
calendar: mdiCalendar,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiMicrophoneMessage,
counter: mdiCounter,
- datetime: mdiCalendarClock,
date: mdiCalendar,
+ datetime: mdiCalendarClock,
demo: mdiHomeAssistant,
+ device_tracker: mdiAccount,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,
homeassistant: mdiHomeAssistant,
homekit: mdiHomeAutomation,
- image: mdiImage,
image_processing: mdiImageFilterFrames,
+ image: mdiImage,
+ input_boolean: mdiToggleSwitch,
input_button: mdiButtonPointer,
input_datetime: mdiCalendarClock,
input_number: mdiRayVertex,
@@ -111,6 +122,7 @@ export const FIXED_DOMAIN_ICONS = {
notify: mdiCommentAlert,
number: mdiRayVertex,
persistent_notification: mdiBell,
+ person: mdiAccount,
plant: mdiFlower,
proximity: mdiAppleSafari,
remote: mdiRemote,
@@ -122,12 +134,12 @@ export const FIXED_DOMAIN_ICONS = {
simple_alarm: mdiBell,
siren: mdiBullhorn,
stt: mdiMicrophoneMessage,
+ sun: mdiWhiteBalanceSunny,
text: mdiFormTextbox,
- todo: mdiClipboardList,
time: mdiClock,
timer: mdiTimerOutline,
+ todo: mdiClipboardList,
tts: mdiSpeakerMessage,
- updater: mdiCloudUpload,
vacuum: mdiRobotVacuum,
wake_word: mdiChatSleep,
weather: mdiWeatherPartlyCloudy,
@@ -180,6 +192,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
volatile_organic_compounds_parts: mdiMolecule,
voltage: mdiSineWave,
volume: mdiCarCoolantLevel,
+ volume_flow_rate: mdiPipe,
water: mdiWater,
weight: mdiWeight,
wind_speed: mdiWeatherWindy,
diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts
index 5f94177db0..2369eb46b6 100644
--- a/src/common/datetime/format_date.ts
+++ b/src/common/datetime/format_date.ts
@@ -1,7 +1,8 @@
import { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
-import { FrontendLocaleData, DateFormat } from "../../data/translation";
+import { DateFormat, FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
+import { resolveTimeZone } from "./resolve-time-zone";
// Tuesday, August 10
export const formatDateWeekdayDay = (
@@ -16,7 +17,7 @@ const formatDateWeekdayDayMem = memoizeOne(
weekday: "long",
month: "long",
day: "numeric",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -33,7 +34,7 @@ const formatDateMem = memoizeOne(
year: "numeric",
month: "long",
day: "numeric",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -50,7 +51,7 @@ const formatDateShortMem = memoizeOne(
year: "numeric",
month: "short",
day: "numeric",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -105,7 +106,7 @@ const formatDateNumericMem = memoizeOne(
year: "numeric",
month: "numeric",
day: "numeric",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
});
}
@@ -113,7 +114,7 @@ const formatDateNumericMem = memoizeOne(
year: "numeric",
month: "numeric",
day: "numeric",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
});
}
);
@@ -130,7 +131,7 @@ const formatDateVeryShortMem = memoizeOne(
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -146,7 +147,7 @@ const formatDateMonthYearMem = memoizeOne(
new Intl.DateTimeFormat(locale.language, {
month: "long",
year: "numeric",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -161,7 +162,7 @@ const formatDateMonthMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -176,7 +177,7 @@ const formatDateYearMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -191,7 +192,7 @@ const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -206,6 +207,6 @@ const formatDateWeekdayShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "short",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
diff --git a/src/common/datetime/format_date_time.ts b/src/common/datetime/format_date_time.ts
index 52700b4a23..76a32e1acc 100644
--- a/src/common/datetime/format_date_time.ts
+++ b/src/common/datetime/format_date_time.ts
@@ -4,6 +4,7 @@ import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { formatDateNumeric } from "./format_date";
import { formatTime } from "./format_time";
+import { resolveTimeZone } from "./resolve-time-zone";
import { useAmPm } from "./use_am_pm";
// August 9, 2021, 8:23 AM
@@ -22,7 +23,7 @@ const formatDateTimeMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -42,7 +43,7 @@ const formatShortDateTimeWithYearMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -61,7 +62,7 @@ const formatShortDateTimeMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -82,7 +83,7 @@ const formatDateTimeWithSecondsMem = memoizeOne(
minute: "2-digit",
second: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts
index 948eb553fe..ed3e6b603d 100644
--- a/src/common/datetime/format_time.ts
+++ b/src/common/datetime/format_time.ts
@@ -2,6 +2,7 @@ import { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
+import { resolveTimeZone } from "./resolve-time-zone";
import { useAmPm } from "./use_am_pm";
// 9:15 PM || 21:15
@@ -17,7 +18,7 @@ const formatTimeMem = memoizeOne(
hour: "numeric",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -35,7 +36,7 @@ const formatTimeWithSecondsMem = memoizeOne(
minute: "2-digit",
second: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -53,7 +54,7 @@ const formatTimeWeekdayMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@@ -71,6 +72,6 @@ const formatTime24hMem = memoizeOne(
hour: "numeric",
minute: "2-digit",
hour12: false,
- timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
+ timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
diff --git a/src/common/datetime/localize_date.ts b/src/common/datetime/localize_date.ts
index 428261801d..9fa851d7c2 100644
--- a/src/common/datetime/localize_date.ts
+++ b/src/common/datetime/localize_date.ts
@@ -1,4 +1,5 @@
import memoizeOne from "memoize-one";
+import "../../resources/intl-polyfill";
export const localizeWeekdays = memoizeOne(
(language: string, short: boolean): string[] => {
diff --git a/src/common/datetime/resolve-time-zone.ts b/src/common/datetime/resolve-time-zone.ts
new file mode 100644
index 0000000000..d085edac6a
--- /dev/null
+++ b/src/common/datetime/resolve-time-zone.ts
@@ -0,0 +1,15 @@
+import { TimeZone } from "../../data/translation";
+
+// Browser time zone can be determined from Intl, with fallback to UTC for polyfill or no support.
+// Alternatively, we could fallback to a fixed offset IANA zone (e.g. "Etc/GMT+5") using
+// Date.prototype.getTimeOffset(), but IANA only has whole hour Etc zones, and problems
+// might occur with relative time due to DST.
+// Use optional chain instead of polyfill import since polyfill will always return UTC
+export const LOCAL_TIME_ZONE =
+ Intl.DateTimeFormat?.().resolvedOptions?.().timeZone ?? "UTC";
+
+// Pick time zone based on user profile option. Core zone is used when local cannot be determined.
+export const resolveTimeZone = (option: TimeZone, serverTimeZone: string) =>
+ option === TimeZone.local && LOCAL_TIME_ZONE !== "UTC"
+ ? LOCAL_TIME_ZONE
+ : serverTimeZone;
diff --git a/src/common/dom/apply_themes_on_element.ts b/src/common/dom/apply_themes_on_element.ts
index a6fd07b5b3..cf431b6cb8 100644
--- a/src/common/dom/apply_themes_on_element.ts
+++ b/src/common/dom/apply_themes_on_element.ts
@@ -41,7 +41,9 @@ export const applyThemesOnElement = (
// If there is no explicitly desired dark mode provided, we automatically
// use the active one from `themes`.
const darkMode =
- themeSettings?.dark !== undefined ? themeSettings.dark : themes.darkMode;
+ themeSettings?.dark !== undefined
+ ? themeSettings.dark
+ : themes?.darkMode || false;
let cacheKey = themeToApply;
let themeRules: Partial = {};
diff --git a/src/common/entity/alarm_panel_icon.ts b/src/common/entity/alarm_panel_icon.ts
deleted file mode 100644
index cdbb736b3a..0000000000
--- a/src/common/entity/alarm_panel_icon.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/** Return an icon representing a alarm panel state. */
-
-import {
- mdiShieldLock,
- mdiShieldAirplane,
- mdiShieldHome,
- mdiShieldMoon,
- mdiSecurity,
- mdiShieldOutline,
- mdiBellRing,
- mdiShieldOff,
- mdiShield,
-} from "@mdi/js";
-
-export const alarmPanelIcon = (state?: string) => {
- switch (state) {
- case "armed_away":
- return mdiShieldLock;
- case "armed_vacation":
- return mdiShieldAirplane;
- case "armed_home":
- return mdiShieldHome;
- case "armed_night":
- return mdiShieldMoon;
- case "armed_custom_bypass":
- return mdiSecurity;
- case "pending":
- return mdiShieldOutline;
- case "triggered":
- return mdiBellRing;
- case "disarmed":
- return mdiShieldOff;
- default:
- return mdiShield;
- }
-};
diff --git a/src/common/entity/battery_icon.ts b/src/common/entity/battery_icon.ts
index d73cdbe489..75509b5d8b 100644
--- a/src/common/entity/battery_icon.ts
+++ b/src/common/entity/battery_icon.ts
@@ -1,92 +1,59 @@
-/** Return an icon representing a battery state. */
-import {
- mdiBattery,
- mdiBattery10,
- mdiBattery20,
- mdiBattery30,
- mdiBattery40,
- mdiBattery50,
- mdiBattery60,
- mdiBattery70,
- mdiBattery80,
- mdiBattery90,
- mdiBatteryAlert,
- mdiBatteryAlertVariantOutline,
- mdiBatteryCharging,
- mdiBatteryCharging10,
- mdiBatteryCharging20,
- mdiBatteryCharging30,
- mdiBatteryCharging40,
- mdiBatteryCharging50,
- mdiBatteryCharging60,
- mdiBatteryCharging70,
- mdiBatteryCharging80,
- mdiBatteryCharging90,
- mdiBatteryChargingOutline,
- mdiBatteryUnknown,
-} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
const BATTERY_ICONS = {
- 10: mdiBattery10,
- 20: mdiBattery20,
- 30: mdiBattery30,
- 40: mdiBattery40,
- 50: mdiBattery50,
- 60: mdiBattery60,
- 70: mdiBattery70,
- 80: mdiBattery80,
- 90: mdiBattery90,
- 100: mdiBattery,
+ 10: "mdi:battery-10",
+ 20: "mdi:battery-20",
+ 30: "mdi:battery-30",
+ 40: "mdi:battery-40",
+ 50: "mdi:battery-50",
+ 60: "mdi:battery-60",
+ 70: "mdi:battery-70",
+ 80: "mdi:battery-80",
+ 90: "mdi:battery-90",
+ 100: "mdi:battery",
};
const BATTERY_CHARGING_ICONS = {
- 10: mdiBatteryCharging10,
- 20: mdiBatteryCharging20,
- 30: mdiBatteryCharging30,
- 40: mdiBatteryCharging40,
- 50: mdiBatteryCharging50,
- 60: mdiBatteryCharging60,
- 70: mdiBatteryCharging70,
- 80: mdiBatteryCharging80,
- 90: mdiBatteryCharging90,
- 100: mdiBatteryCharging,
+ 10: "mdi:battery-charging-10",
+ 20: "mdi:battery-charging-20",
+ 30: "mdi:battery-charging-30",
+ 40: "mdi:battery-charging-40",
+ 50: "mdi:battery-charging-50",
+ 60: "mdi:battery-charging-60",
+ 70: "mdi:battery-charging-70",
+ 80: "mdi:battery-charging-80",
+ 90: "mdi:battery-charging-90",
+ 100: "mdi:battery-charging",
};
-export const batteryStateIcon = (
- batteryState: HassEntity,
- batteryChargingState?: HassEntity
-) => {
- const battery = batteryState.state;
- const batteryCharging =
- batteryChargingState && batteryChargingState.state === "on";
-
- return batteryIcon(battery, batteryCharging);
+export const batteryIcon = (stateObj: HassEntity, state?: string) => {
+ const level = state ?? stateObj.state;
+ return batteryLevelIcon(level);
};
-export const batteryIcon = (
- batteryState: number | string,
- batteryCharging?: boolean
-) => {
- const batteryValue = Number(batteryState);
+export const batteryLevelIcon = (
+ batteryLevel: number | string,
+ isBatteryCharging?: boolean
+): string => {
+ const batteryValue = Number(batteryLevel);
if (isNaN(batteryValue)) {
- if (batteryState === "off") {
- return mdiBattery;
+ if (batteryLevel === "off") {
+ return "mdi:battery";
}
- if (batteryState === "on") {
- return mdiBatteryAlert;
+ if (batteryLevel === "on") {
+ return "mdi:battery-alert";
}
- return mdiBatteryUnknown;
+ return "mdi:battery-unknown";
}
const batteryRound = Math.round(batteryValue / 10) * 10;
- if (batteryCharging && batteryValue >= 10) {
+ if (isBatteryCharging && batteryValue >= 10) {
return BATTERY_CHARGING_ICONS[batteryRound];
}
- if (batteryCharging) {
- return mdiBatteryChargingOutline;
+ if (isBatteryCharging) {
+ return "mdi:battery-charging-outline";
}
if (batteryValue <= 5) {
- return mdiBatteryAlertVariantOutline;
+ return "mdi:battery-alert-variant-outline";
}
return BATTERY_ICONS[batteryRound];
};
diff --git a/src/common/entity/binary_sensor_icon.ts b/src/common/entity/binary_sensor_icon.ts
deleted file mode 100644
index fa2d81bda3..0000000000
--- a/src/common/entity/binary_sensor_icon.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import {
- mdiAlertCircle,
- mdiBattery,
- mdiBatteryCharging,
- mdiBatteryOutline,
- mdiBrightness5,
- mdiBrightness7,
- mdiCheckboxMarkedCircle,
- mdiCheckNetworkOutline,
- mdiCloseNetworkOutline,
- mdiCheckCircle,
- mdiCropPortrait,
- mdiDoorClosed,
- mdiDoorOpen,
- mdiFire,
- mdiGarage,
- mdiGarageOpen,
- mdiHome,
- mdiHomeOutline,
- mdiLock,
- mdiLockOpen,
- mdiMusicNote,
- mdiMusicNoteOff,
- mdiMotionSensor,
- mdiMotionSensorOff,
- mdiPackage,
- mdiPackageUp,
- mdiPlay,
- mdiPowerPlug,
- mdiPowerPlugOff,
- mdiRadioboxBlank,
- mdiSnowflake,
- mdiSmokeDetector,
- mdiSmokeDetectorAlert,
- mdiSmokeDetectorVariant,
- mdiSmokeDetectorVariantAlert,
- mdiSquare,
- mdiSquareOutline,
- mdiStop,
- mdiThermometer,
- mdiVibrate,
- mdiWater,
- mdiWaterOff,
- mdiWindowClosed,
- mdiWindowOpen,
-} from "@mdi/js";
-import { HassEntity } from "home-assistant-js-websocket";
-
-/** Return an icon representing a binary sensor state. */
-
-export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
- const is_off = state === "off";
- switch (stateObj?.attributes.device_class) {
- case "battery":
- return is_off ? mdiBattery : mdiBatteryOutline;
- case "battery_charging":
- return is_off ? mdiBattery : mdiBatteryCharging;
- case "carbon_monoxide":
- return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert;
- case "cold":
- return is_off ? mdiThermometer : mdiSnowflake;
- case "connectivity":
- return is_off ? mdiCloseNetworkOutline : mdiCheckNetworkOutline;
- case "door":
- return is_off ? mdiDoorClosed : mdiDoorOpen;
- case "garage_door":
- return is_off ? mdiGarage : mdiGarageOpen;
- case "power":
- return is_off ? mdiPowerPlugOff : mdiPowerPlug;
- case "gas":
- case "problem":
- case "safety":
- case "tamper":
- return is_off ? mdiCheckCircle : mdiAlertCircle;
- case "smoke":
- return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert;
- case "heat":
- return is_off ? mdiThermometer : mdiFire;
- case "light":
- return is_off ? mdiBrightness5 : mdiBrightness7;
- case "lock":
- return is_off ? mdiLock : mdiLockOpen;
- case "moisture":
- return is_off ? mdiWaterOff : mdiWater;
- case "motion":
- return is_off ? mdiMotionSensorOff : mdiMotionSensor;
- case "occupancy":
- return is_off ? mdiHomeOutline : mdiHome;
- case "opening":
- return is_off ? mdiSquare : mdiSquareOutline;
- case "plug":
- return is_off ? mdiPowerPlugOff : mdiPowerPlug;
- case "presence":
- return is_off ? mdiHomeOutline : mdiHome;
- case "running":
- return is_off ? mdiStop : mdiPlay;
- case "sound":
- return is_off ? mdiMusicNoteOff : mdiMusicNote;
- case "update":
- return is_off ? mdiPackage : mdiPackageUp;
- case "vibration":
- return is_off ? mdiCropPortrait : mdiVibrate;
- case "window":
- return is_off ? mdiWindowClosed : mdiWindowOpen;
- default:
- return is_off ? mdiRadioboxBlank : mdiCheckboxMarkedCircle;
- }
-};
diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts
index 70a41a5893..6d5e36d5c2 100644
--- a/src/common/entity/compute_state_display.ts
+++ b/src/common/entity/compute_state_display.ts
@@ -79,7 +79,8 @@ export const computeStateDisplayFromEntityAttributes = (
if (
attributes.device_class === "duration" &&
attributes.unit_of_measurement &&
- UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement]
+ UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement] &&
+ entity?.display_precision === undefined
) {
try {
return formatDuration(state, attributes.unit_of_measurement);
@@ -113,7 +114,7 @@ export const computeStateDisplayFromEntityAttributes = (
const unit = attributes.unit_of_measurement;
if (unit) {
- return `${value}${blankBeforeUnit(unit)}${unit}`;
+ return `${value}${blankBeforeUnit(unit, locale)}${unit}`;
}
return value;
diff --git a/src/common/entity/cover_icon.ts b/src/common/entity/cover_icon.ts
index bee06d6101..53a30db752 100644
--- a/src/common/entity/cover_icon.ts
+++ b/src/common/entity/cover_icon.ts
@@ -1,132 +1,12 @@
/** Return an icon representing a cover state. */
import {
- mdiArrowUpBox,
- mdiArrowDownBox,
- mdiGarage,
- mdiGarageOpen,
- mdiGateArrowRight,
- mdiGate,
- mdiGateOpen,
- mdiDoorOpen,
- mdiDoorClosed,
- mdiCircle,
- mdiWindowShutter,
- mdiWindowShutterOpen,
- mdiBlindsHorizontal,
- mdiBlindsHorizontalClosed,
- mdiRollerShade,
- mdiRollerShadeClosed,
- mdiWindowClosed,
- mdiWindowOpen,
- mdiArrowExpandHorizontal,
- mdiArrowUp,
mdiArrowCollapseHorizontal,
mdiArrowDown,
- mdiCircleSlice8,
- mdiArrowSplitVertical,
- mdiCurtains,
- mdiCurtainsClosed,
+ mdiArrowExpandHorizontal,
+ mdiArrowUp,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
-export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
- const open = state !== "closed";
-
- switch (stateObj?.attributes.device_class) {
- case "garage":
- switch (state) {
- case "opening":
- return mdiArrowUpBox;
- case "closing":
- return mdiArrowDownBox;
- case "closed":
- return mdiGarage;
- default:
- return mdiGarageOpen;
- }
- case "gate":
- switch (state) {
- case "opening":
- case "closing":
- return mdiGateArrowRight;
- case "closed":
- return mdiGate;
- default:
- return mdiGateOpen;
- }
- case "door":
- return open ? mdiDoorOpen : mdiDoorClosed;
- case "damper":
- return open ? mdiCircle : mdiCircleSlice8;
- case "shutter":
- switch (state) {
- case "opening":
- return mdiArrowUpBox;
- case "closing":
- return mdiArrowDownBox;
- case "closed":
- return mdiWindowShutter;
- default:
- return mdiWindowShutterOpen;
- }
- case "curtain":
- switch (state) {
- case "opening":
- return mdiArrowSplitVertical;
- case "closing":
- return mdiArrowCollapseHorizontal;
- case "closed":
- return mdiCurtainsClosed;
- default:
- return mdiCurtains;
- }
- case "blind":
- switch (state) {
- case "opening":
- return mdiArrowUpBox;
- case "closing":
- return mdiArrowDownBox;
- case "closed":
- return mdiBlindsHorizontalClosed;
- default:
- return mdiBlindsHorizontal;
- }
- case "shade":
- switch (state) {
- case "opening":
- return mdiArrowUpBox;
- case "closing":
- return mdiArrowDownBox;
- case "closed":
- return mdiRollerShadeClosed;
- default:
- return mdiRollerShade;
- }
- case "window":
- switch (state) {
- case "opening":
- return mdiArrowUpBox;
- case "closing":
- return mdiArrowDownBox;
- case "closed":
- return mdiWindowClosed;
- default:
- return mdiWindowOpen;
- }
- }
-
- switch (state) {
- case "opening":
- return mdiArrowUpBox;
- case "closing":
- return mdiArrowDownBox;
- case "closed":
- return mdiWindowClosed;
- default:
- return mdiWindowOpen;
- }
-};
-
export const computeOpenIcon = (stateObj: HassEntity): string => {
switch (stateObj.attributes.device_class) {
case "awning":
diff --git a/src/common/entity/device_tracker_icon.ts b/src/common/entity/device_tracker_icon.ts
new file mode 100644
index 0000000000..0278924239
--- /dev/null
+++ b/src/common/entity/device_tracker_icon.ts
@@ -0,0 +1,16 @@
+import { HassEntity } from "home-assistant-js-websocket";
+
+export const deviceTrackerIcon = (stateObj: HassEntity, state?: string) => {
+ const compareState = state ?? stateObj.state;
+ if (stateObj?.attributes.source_type === "router") {
+ return compareState === "home" ? "mdi:lan-connect" : "mdi:lan-disconnect";
+ }
+ if (
+ ["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type)
+ ) {
+ return compareState === "home" ? "mdi:bluetooth-connect" : "mdi:bluetooth";
+ }
+ return compareState === "not_home"
+ ? "mdi:account-arrow-right"
+ : "mdi:account";
+};
diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts
deleted file mode 100644
index dfbbc69a5e..0000000000
--- a/src/common/entity/domain_icon.ts
+++ /dev/null
@@ -1,301 +0,0 @@
-import {
- mdiAccount,
- mdiAccountArrowRight,
- mdiAirHumidifier,
- mdiAirHumidifierOff,
- mdiAudioVideo,
- mdiAudioVideoOff,
- mdiBluetooth,
- mdiBluetoothConnect,
- mdiButtonPointer,
- mdiCalendar,
- mdiCast,
- mdiCastConnected,
- mdiCastOff,
- mdiChartSankey,
- mdiCheckCircleOutline,
- mdiClock,
- mdiCloseCircleOutline,
- mdiCrosshairsQuestion,
- mdiDoorbell,
- mdiEyeCheck,
- mdiFan,
- mdiFanOff,
- mdiGestureTapButton,
- mdiLanConnect,
- mdiLanDisconnect,
- mdiLock,
- mdiLockAlert,
- mdiLockClock,
- mdiLockOpen,
- mdiMeterGas,
- mdiMotionSensor,
- mdiPackage,
- mdiPackageDown,
- mdiPackageUp,
- mdiPipeValve,
- mdiPowerPlug,
- mdiPowerPlugOff,
- mdiRestart,
- mdiRobot,
- mdiRobotConfused,
- mdiRobotOff,
- mdiSpeaker,
- mdiSpeakerOff,
- mdiSpeakerPause,
- mdiSpeakerPlay,
- mdiSwapHorizontal,
- mdiTelevision,
- mdiTelevisionOff,
- mdiTelevisionPause,
- mdiTelevisionPlay,
- mdiToggleSwitchVariant,
- mdiToggleSwitchVariantOff,
- mdiVideo,
- mdiVideoOff,
- mdiWaterBoiler,
- mdiWaterBoilerOff,
- mdiWeatherNight,
- mdiWhiteBalanceSunny,
-} from "@mdi/js";
-import { HassEntity } from "home-assistant-js-websocket";
-import { UpdateEntity, updateIsInstalling } from "../../data/update";
-import { weatherIcon } from "../../data/weather";
-/**
- * Return the icon to be used for a domain.
- *
- * Optionally pass in a state to influence the domain icon.
- */
-import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const";
-import { alarmPanelIcon } from "./alarm_panel_icon";
-import { binarySensorIcon } from "./binary_sensor_icon";
-import { coverIcon } from "./cover_icon";
-import { numberIcon } from "./number_icon";
-import { sensorIcon } from "./sensor_icon";
-
-export const domainIcon = (
- domain: string,
- stateObj?: HassEntity,
- state?: string
-): string => {
- const icon = domainIconWithoutDefault(domain, stateObj, state);
- if (icon) {
- return icon;
- }
- // eslint-disable-next-line
- console.warn(`Unable to find icon for domain ${domain}`);
- return DEFAULT_DOMAIN_ICON;
-};
-
-export const domainIconWithoutDefault = (
- domain: string,
- stateObj?: HassEntity,
- state?: string
-): string | undefined => {
- const compareState = state !== undefined ? state : stateObj?.state;
-
- switch (domain) {
- case "alarm_control_panel":
- return alarmPanelIcon(compareState);
-
- case "automation":
- return compareState === "unavailable"
- ? mdiRobotConfused
- : compareState === "off"
- ? mdiRobotOff
- : mdiRobot;
-
- case "binary_sensor":
- return binarySensorIcon(compareState, stateObj);
-
- case "button":
- switch (stateObj?.attributes.device_class) {
- case "identify":
- return mdiCrosshairsQuestion;
- case "restart":
- return mdiRestart;
- case "update":
- return mdiPackageUp;
- default:
- return mdiButtonPointer;
- }
-
- case "camera":
- return compareState === "off" ? mdiVideoOff : mdiVideo;
-
- case "cover":
- return coverIcon(compareState, stateObj);
-
- case "device_tracker":
- if (stateObj?.attributes.source_type === "router") {
- return compareState === "home" ? mdiLanConnect : mdiLanDisconnect;
- }
- if (
- ["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type)
- ) {
- return compareState === "home" ? mdiBluetoothConnect : mdiBluetooth;
- }
- return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
-
- case "event":
- switch (stateObj?.attributes.device_class) {
- case "doorbell":
- return mdiDoorbell;
- case "button":
- return mdiGestureTapButton;
- case "motion":
- return mdiMotionSensor;
- default:
- return mdiEyeCheck;
- }
-
- case "fan":
- return compareState === "off" ? mdiFanOff : mdiFan;
-
- case "humidifier":
- return compareState === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;
-
- case "input_boolean":
- return compareState === "on"
- ? mdiCheckCircleOutline
- : mdiCloseCircleOutline;
-
- case "input_datetime":
- if (!stateObj?.attributes.has_date) {
- return mdiClock;
- }
- if (!stateObj.attributes.has_time) {
- return mdiCalendar;
- }
- break;
-
- case "lock":
- switch (compareState) {
- case "unlocked":
- return mdiLockOpen;
- case "jammed":
- return mdiLockAlert;
- case "locking":
- case "unlocking":
- return mdiLockClock;
- default:
- return mdiLock;
- }
-
- case "media_player":
- switch (stateObj?.attributes.device_class) {
- case "speaker":
- switch (compareState) {
- case "playing":
- return mdiSpeakerPlay;
- case "paused":
- return mdiSpeakerPause;
- case "off":
- return mdiSpeakerOff;
- default:
- return mdiSpeaker;
- }
- case "tv":
- switch (compareState) {
- case "playing":
- return mdiTelevisionPlay;
- case "paused":
- return mdiTelevisionPause;
- case "off":
- return mdiTelevisionOff;
- default:
- return mdiTelevision;
- }
- case "receiver":
- switch (compareState) {
- case "off":
- return mdiAudioVideoOff;
- default:
- return mdiAudioVideo;
- }
- default:
- switch (compareState) {
- case "playing":
- case "paused":
- return mdiCastConnected;
- case "off":
- return mdiCastOff;
- default:
- return mdiCast;
- }
- }
-
- case "number": {
- const icon = numberIcon(stateObj);
- if (icon) {
- return icon;
- }
-
- break;
- }
-
- case "person":
- return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
-
- case "switch":
- switch (stateObj?.attributes.device_class) {
- case "outlet":
- return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
- case "switch":
- return compareState === "on"
- ? mdiToggleSwitchVariant
- : mdiToggleSwitchVariantOff;
- default:
- return mdiToggleSwitchVariant;
- }
-
- case "sensor": {
- const icon = sensorIcon(stateObj);
- if (icon) {
- return icon;
- }
-
- break;
- }
-
- case "sun":
- return stateObj?.state === "above_horizon"
- ? mdiWhiteBalanceSunny
- : mdiWeatherNight;
-
- case "switch_as_x":
- return mdiSwapHorizontal;
-
- case "threshold":
- return mdiChartSankey;
-
- case "update":
- return compareState === "on"
- ? updateIsInstalling(stateObj as UpdateEntity)
- ? mdiPackageDown
- : mdiPackageUp
- : mdiPackage;
-
- case "valve":
- switch (stateObj?.attributes.device_class) {
- case "water":
- return mdiPipeValve;
- case "gas":
- return mdiMeterGas;
- default:
- return mdiPipeValve;
- }
-
- case "water_heater":
- return compareState === "off" ? mdiWaterBoilerOff : mdiWaterBoiler;
-
- case "weather":
- return weatherIcon(stateObj?.state);
- }
-
- if (domain in FIXED_DOMAIN_ICONS) {
- return FIXED_DOMAIN_ICONS[domain];
- }
-
- return undefined;
-};
diff --git a/src/common/entity/get_states.ts b/src/common/entity/get_states.ts
index 456ce27bce..465d9c0c76 100644
--- a/src/common/entity/get_states.ts
+++ b/src/common/entity/get_states.ts
@@ -211,6 +211,7 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
"volatile_organic_compounds",
"volatile_organic_compounds_parts",
"voltage",
+ "volume_flow_rate",
],
state_class: ["measurement", "total", "total_increasing"],
},
diff --git a/src/common/entity/number_icon.ts b/src/common/entity/number_icon.ts
deleted file mode 100644
index c468be6fed..0000000000
--- a/src/common/entity/number_icon.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/** Return an icon representing a number state. */
-import { HassEntity } from "home-assistant-js-websocket";
-import { FIXED_DEVICE_CLASS_ICONS } from "../const";
-
-export const numberIcon = (stateObj?: HassEntity): string | undefined => {
- const dclass = stateObj?.attributes.device_class;
-
- if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
- return FIXED_DEVICE_CLASS_ICONS[dclass];
- }
-
- return undefined;
-};
diff --git a/src/common/entity/sensor_icon.ts b/src/common/entity/sensor_icon.ts
deleted file mode 100644
index 9af29ceb3e..0000000000
--- a/src/common/entity/sensor_icon.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/** Return an icon representing a sensor state. */
-import { mdiBattery, mdiThermometer } from "@mdi/js";
-import { HassEntity } from "home-assistant-js-websocket";
-import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
-import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
-import { batteryStateIcon } from "./battery_icon";
-
-export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
- const dclass = stateObj?.attributes.device_class;
-
- if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
- return FIXED_DEVICE_CLASS_ICONS[dclass];
- }
-
- if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
- return stateObj ? batteryStateIcon(stateObj) : mdiBattery;
- }
-
- const unit = stateObj?.attributes.unit_of_measurement;
- if (unit === UNIT_C || unit === UNIT_F) {
- return mdiThermometer;
- }
-
- return undefined;
-};
diff --git a/src/common/entity/state_icon.ts b/src/common/entity/state_icon.ts
new file mode 100644
index 0000000000..b26f4e3267
--- /dev/null
+++ b/src/common/entity/state_icon.ts
@@ -0,0 +1,42 @@
+import { 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,
+ state?: string
+): 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);
+
+ case "sun":
+ return compareState === "above_horizon"
+ ? "mdi:white-balance-sunny"
+ : "mdi:weather-night";
+
+ case "input_datetime":
+ if (!stateObj.attributes.has_date) {
+ return "mdi:clock";
+ }
+ if (!stateObj.attributes.has_time) {
+ return "mdi:calendar";
+ }
+ break;
+ }
+ return undefined;
+};
diff --git a/src/common/entity/state_icon_path.ts b/src/common/entity/state_icon_path.ts
deleted file mode 100644
index 24d842caf6..0000000000
--- a/src/common/entity/state_icon_path.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/** Return an icon representing a state. */
-import { HassEntity } from "home-assistant-js-websocket";
-import { DEFAULT_DOMAIN_ICON } from "../const";
-import { computeDomain } from "./compute_domain";
-import { domainIcon } from "./domain_icon";
-
-export const stateIconPath = (state?: HassEntity) => {
- if (!state) {
- return DEFAULT_DOMAIN_ICON;
- }
- return domainIcon(computeDomain(state.entity_id), state);
-};
diff --git a/src/common/entity/update_icon.ts b/src/common/entity/update_icon.ts
new file mode 100644
index 0000000000..4e1915d7fc
--- /dev/null
+++ b/src/common/entity/update_icon.ts
@@ -0,0 +1,11 @@
+import { HassEntity } from "home-assistant-js-websocket";
+import { UpdateEntity, updateIsInstalling } from "../../data/update";
+
+export const updateIcon = (stateObj: HassEntity, state?: string) => {
+ const compareState = state ?? stateObj.state;
+ return compareState === "on"
+ ? updateIsInstalling(stateObj as UpdateEntity)
+ ? "mdi:package-down"
+ : "mdi:package-up"
+ : "mdi:package";
+};
diff --git a/src/common/translations/blank_before_percent.ts b/src/common/translations/blank_before_percent.ts
index 49fece6675..4c489c96c8 100644
--- a/src/common/translations/blank_before_percent.ts
+++ b/src/common/translations/blank_before_percent.ts
@@ -4,7 +4,7 @@ import { FrontendLocaleData } from "../../data/translation";
export const blankBeforePercent = (
localeOptions: FrontendLocaleData
): string => {
- switch (localeOptions?.language) {
+ switch (localeOptions.language) {
case "cz":
case "de":
case "fi":
diff --git a/src/common/translations/blank_before_unit.ts b/src/common/translations/blank_before_unit.ts
index 34fe8ab2d5..32a430430a 100644
--- a/src/common/translations/blank_before_unit.ts
+++ b/src/common/translations/blank_before_unit.ts
@@ -3,7 +3,7 @@ import { blankBeforePercent } from "./blank_before_percent";
export const blankBeforeUnit = (
unit: string,
- localeOptions?: FrontendLocaleData
+ localeOptions: FrontendLocaleData | undefined
): string => {
if (unit === "°") {
return "";
diff --git a/src/common/url/search-params.ts b/src/common/url/search-params.ts
index f4ebb347e8..a7bd025842 100644
--- a/src/common/url/search-params.ts
+++ b/src/common/url/search-params.ts
@@ -1,6 +1,8 @@
+import { mainWindow } from "../dom/get_main_window";
+
export const extractSearchParamsObject = (): Record => {
const query = {};
- const searchParams = new URLSearchParams(location.search);
+ const searchParams = new URLSearchParams(mainWindow.location.search);
for (const [key, value] of searchParams.entries()) {
query[key] = value;
}
@@ -8,7 +10,7 @@ export const extractSearchParamsObject = (): Record => {
};
export const extractSearchParam = (param: string): string | null => {
- const urlParams = new URLSearchParams(window.location.search);
+ const urlParams = new URLSearchParams(mainWindow.location.search);
return urlParams.get(param);
};
@@ -21,7 +23,7 @@ export const createSearchParam = (params: Record): string => {
};
export const addSearchParam = (params: Record): string => {
- const urlParams = new URLSearchParams(window.location.search);
+ const urlParams = new URLSearchParams(mainWindow.location.search);
Object.entries(params).forEach(([key, value]) => {
urlParams.set(key, value);
});
@@ -29,7 +31,7 @@ export const addSearchParam = (params: Record): string => {
};
export const removeSearchParam = (param: string): string => {
- const urlParams = new URLSearchParams(window.location.search);
+ const urlParams = new URLSearchParams(mainWindow.location.search);
urlParams.delete(param);
return urlParams.toString();
};
diff --git a/src/common/util/array-move.ts b/src/common/util/array-move.ts
new file mode 100644
index 0000000000..36a152a019
--- /dev/null
+++ b/src/common/util/array-move.ts
@@ -0,0 +1,53 @@
+import { ItemPath } from "../../types";
+
+function findNestedItem(
+ obj: any,
+ path: ItemPath,
+ createNonExistingPath?: boolean
+): any {
+ return path.reduce((ac, p, index, array) => {
+ if (ac === undefined) return undefined;
+ if (!ac[p] && createNonExistingPath) {
+ const nextP = array[index + 1];
+ // Create object or array depending on next path
+ if (nextP === undefined || typeof nextP === "number") {
+ ac[p] = [];
+ } else {
+ ac[p] = {};
+ }
+ }
+ return ac[p];
+ }, obj);
+}
+
+export function nestedArrayMove(
+ obj: T | T[],
+ oldIndex: number,
+ newIndex: number,
+ oldPath?: ItemPath,
+ newPath?: ItemPath
+): T | T[] {
+ const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
+ const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
+ const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
+
+ if (!Array.isArray(from) || !Array.isArray(to)) {
+ return obj;
+ }
+
+ const item = from.splice(oldIndex, 1)[0];
+ to.splice(newIndex, 0, item);
+
+ return newObj;
+}
+
+export function arrayMove(
+ array: T[],
+ oldIndex: number,
+ newIndex: number
+): T[] {
+ const newArray = [...array];
+ const [item] = newArray.splice(oldIndex, 1);
+ newArray.splice(newIndex, 0, item);
+ return newArray;
+}
diff --git a/src/common/util/compute_rtl.ts b/src/common/util/compute_rtl.ts
index 39719bee43..0c53e374e9 100644
--- a/src/common/util/compute_rtl.ts
+++ b/src/common/util/compute_rtl.ts
@@ -34,4 +34,8 @@ export function setDirectionStyles(direction: string, element: LitElement) {
"--float-end",
direction === "ltr" ? "right" : "left"
);
+ element.style.setProperty(
+ "--margin-title",
+ direction === "ltr" ? "var(--margin-title-ltr)" : "var(--margin-title-rtl)"
+ );
}
diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts
index e712762470..78ec773719 100644
--- a/src/components/chart/state-history-chart-line.ts
+++ b/src/components/chart/state-history-chart-line.ts
@@ -27,7 +27,7 @@ export class StateHistoryChartLine extends LitElement {
@property({ attribute: false }) public data: LineChartEntity[] = [];
- @property() public names?: Record;
+ @property({ attribute: false }) public names?: Record;
@property() public unit?: string;
@@ -47,6 +47,12 @@ export class StateHistoryChartLine extends LitElement {
@property({ type: Boolean }) public logarithmicScale = false;
+ @property({ type: Number }) public minYAxis?: number;
+
+ @property({ type: Number }) public maxYAxis?: number;
+
+ @property({ type: Boolean }) public fitYData = false;
+
@state() private _chartData?: ChartData<"line">;
@state() private _entityIds: string[] = [];
@@ -84,7 +90,10 @@ export class StateHistoryChartLine extends LitElement {
changedProps.has("startTime") ||
changedProps.has("endTime") ||
changedProps.has("unit") ||
- changedProps.has("logarithmicScale")
+ changedProps.has("logarithmicScale") ||
+ changedProps.has("minYAxis") ||
+ changedProps.has("maxYAxis") ||
+ changedProps.has("fitYData")
) {
this._chartOptions = {
parsing: false,
@@ -121,6 +130,10 @@ export class StateHistoryChartLine extends LitElement {
},
},
y: {
+ suggestedMin: this.fitYData ? this.minYAxis : null,
+ suggestedMax: this.fitYData ? this.maxYAxis : null,
+ min: this.fitYData ? null : this.minYAxis,
+ max: this.fitYData ? null : this.maxYAxis,
ticks: {
maxTicksLimit: 7,
},
diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts
index d1c535e23c..79b51be40e 100644
--- a/src/components/chart/state-history-chart-timeline.ts
+++ b/src/components/chart/state-history-chart-timeline.ts
@@ -23,9 +23,9 @@ export class StateHistoryChartTimeline extends LitElement {
@property({ attribute: false }) public data: TimelineEntity[] = [];
- @property() public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
- @property() public names?: Record;
+ @property({ attribute: false }) public names?: Record;
@property() public unit?: string;
diff --git a/src/components/chart/state-history-charts.ts b/src/components/chart/state-history-charts.ts
index 10bee3c747..7f72aa5f2b 100644
--- a/src/components/chart/state-history-charts.ts
+++ b/src/components/chart/state-history-charts.ts
@@ -52,12 +52,11 @@ export class StateHistoryCharts extends LitElement {
@property({ attribute: false }) public historyData!: HistoryResult;
- @property() public narrow!: boolean;
+ @property({ type: Boolean }) public narrow = false;
- @property() public names?: Record;
+ @property({ attribute: false }) public names?: Record;
- @property({ type: Boolean, attribute: "virtualize", reflect: true })
- public virtualize = false;
+ @property({ type: Boolean, reflect: true }) public virtualize = false;
@property({ attribute: false }) public endTime?: Date;
@@ -65,7 +64,7 @@ export class StateHistoryCharts extends LitElement {
@property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false;
- @property() public hoursToShow?: number;
+ @property({ type: Number }) public hoursToShow?: number;
@property({ type: Boolean }) public showNames = true;
@@ -75,6 +74,12 @@ export class StateHistoryCharts extends LitElement {
@property({ type: Boolean }) public logarithmicScale = false;
+ @property({ type: Number }) public minYAxis?: number;
+
+ @property({ type: Number }) public maxYAxis?: number;
+
+ @property({ type: Boolean }) public fitYData = false;
+
private _computedStartTime!: Date;
private _computedEndTime!: Date;
@@ -162,6 +167,9 @@ export class StateHistoryCharts extends LitElement {
.chartIndex=${index}
.clickForMoreInfo=${this.clickForMoreInfo}
.logarithmicScale=${this.logarithmicScale}
+ .minYAxis=${this.minYAxis}
+ .maxYAxis=${this.maxYAxis}
+ .fitYData=${this.fitYData}
@y-width-changed=${this._yWidthChanged}
>
`;
@@ -301,6 +309,8 @@ export class StateHistoryCharts extends LitElement {
:host([virtualize]) .entry-container {
padding-left: 1px;
padding-right: 1px;
+ padding-inline-start: 1px;
+ padding-inline-end: 1px;
}
.entry-container:not(:first-child) {
diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts
index a0fc6ca53b..0e2fa2a9f5 100644
--- a/src/components/chart/statistics-chart.ts
+++ b/src/components/chart/statistics-chart.ts
@@ -54,7 +54,7 @@ export class StatisticsChart extends LitElement {
StatisticsMetaData
>;
- @property() public names?: Record;
+ @property({ attribute: false }) public names?: Record;
@property() public unit?: string;
diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts
index 10293fc761..7f3d2f61b8 100644
--- a/src/components/data-table/ha-data-table.ts
+++ b/src/components/data-table/ha-data-table.ts
@@ -300,7 +300,7 @@ export class HaDataTable extends LitElement {
};
return html`