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`
Home Assistant logo on left, Nabu Casa logo on right, and red heart in center
${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.backup @@ -265,23 +261,23 @@ export class SupervisorBackupContent extends LitElement { : ""} ${this.backupHasPassword ? html` - - + ${!this.backup - ? html` - ` + ` : ""} ` : ""} @@ -320,6 +316,8 @@ export class SupervisorBackupContent extends LitElement { display: flex; flex-direction: column; margin-left: 30px; + margin-inline-start: 30px; + margin-inline-end: initial; } ha-formfield.password { display: block; @@ -328,6 +326,8 @@ export class SupervisorBackupContent extends LitElement { .backup-types { display: flex; margin-left: -13px; + margin-inline-start: -13px; + margin-inline-end: initial; } .sub-header { margin-top: 8px; @@ -429,9 +429,9 @@ export class SupervisorBackupContent extends LitElement { this[input.name] = input.value; } - private _handleTextValueChanged(ev: ValueChangedEvent) { - const input = ev.currentTarget as PaperInputElement; - this[input.name!] = ev.detail.value; + private _handleTextValueChanged(ev: InputEvent) { + const input = ev.currentTarget as HaTextField; + this[input.name!] = input.value; } private _toggleHasPassword(): void { diff --git a/hassio/src/components/supervisor-formfield-label.ts b/hassio/src/components/supervisor-formfield-label.ts index c6986c714b..8f740185f6 100644 --- a/hassio/src/components/supervisor-formfield-label.ts +++ b/hassio/src/components/supervisor-formfield-label.ts @@ -37,6 +37,8 @@ class SupervisorFormfieldLabel extends LitElement { } .label { margin-right: 4px; + margin-inline-end: 4px; + margin-inline-start: initial; } .version { color: var(--secondary-text-color); @@ -45,6 +47,8 @@ class SupervisorFormfieldLabel extends LitElement { max-height: 22px; max-width: 22px; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } `; } diff --git a/hassio/src/components/supervisor-metric.ts b/hassio/src/components/supervisor-metric.ts index fa0d798bbf..7db487da69 100644 --- a/hassio/src/components/supervisor-metric.ts +++ b/hassio/src/components/supervisor-metric.ts @@ -64,6 +64,8 @@ class SupervisorMetric extends LitElement { .value { width: 48px; padding-right: 4px; + padding-inline-start: initial; + padding-inline-end: 4px; flex-shrink: 0; } `; diff --git a/hassio/src/dashboard/hassio-addons.ts b/hassio/src/dashboard/hassio-addons.ts index 7b3c9d166f..eb9c1f00c1 100644 --- a/hassio/src/dashboard/hassio-addons.ts +++ b/hassio/src/dashboard/hassio-addons.ts @@ -20,7 +20,7 @@ class HassioAddons extends LitElement { @property({ attribute: false }) public supervisor!: Supervisor; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _filter?: string; @@ -128,6 +128,7 @@ class HassioAddons extends LitElement { ha-card { cursor: pointer; overflow: hidden; + direction: ltr; } .search { position: sticky; diff --git a/hassio/src/dashboard/hassio-dashboard.ts b/hassio/src/dashboard/hassio-dashboard.ts index d60819861c..e678a42992 100644 --- a/hassio/src/dashboard/hassio-dashboard.ts +++ b/hassio/src/dashboard/hassio-dashboard.ts @@ -21,7 +21,7 @@ class HassioDashboard 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; @@ -133,6 +133,8 @@ class HassioDashboard extends LitElement { position: fixed; right: calc(16px + env(safe-area-inset-right)); bottom: calc(16px + env(safe-area-inset-bottom)); + inset-inline-end: calc(16px + env(safe-area-inset-right)); + inset-inline-start: initial; z-index: 1; } `, diff --git a/hassio/src/dashboard/hassio-update.ts b/hassio/src/dashboard/hassio-update.ts index b331dc7609..b79aa14f18 100644 --- a/hassio/src/dashboard/hassio-update.ts +++ b/hassio/src/dashboard/hassio-update.ts @@ -151,3 +151,9 @@ export class HassioUpdate extends LitElement { ]; } } + +declare global { + interface HTMLElementTagNameMap { + "hassio-update": HassioUpdate; + } +} diff --git a/hassio/src/dialogs/backup/dialog-hassio-backup-location.ts b/hassio/src/dialogs/backup/dialog-hassio-backup-location.ts index 18292149bd..c5bdde32e5 100644 --- a/hassio/src/dialogs/backup/dialog-hassio-backup-location.ts +++ b/hassio/src/dialogs/backup/dialog-hassio-backup-location.ts @@ -27,8 +27,7 @@ const SCHEMA = memoizeOne( class HassioBackupLocationDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) - public _dialogParams?: HassioBackupLocationDialogParams; + @state() private _dialogParams?: HassioBackupLocationDialogParams; @state() private _data?: { default_backup_mount: string | null }; diff --git a/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts b/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts index c85b360df5..b009043079 100644 --- a/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts +++ b/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts @@ -138,6 +138,9 @@ class HassioCreateBackupDialog extends LitElement { haStyle, haStyleDialog, css` + :host { + direction: var(--direction); + } ha-circular-progress { display: block; text-align: center; diff --git a/hassio/src/dialogs/network/dialog-hassio-network.ts b/hassio/src/dialogs/network/dialog-hassio-network.ts index c831fb6c22..66b381e0c0 100644 --- a/hassio/src/dialogs/network/dialog-hassio-network.ts +++ b/hassio/src/dialogs/network/dialog-hassio-network.ts @@ -4,7 +4,6 @@ import "@material/mwc-list/mwc-list-item"; import "@material/mwc-tab"; import "@material/mwc-tab-bar"; import { mdiClose } from "@mdi/js"; -import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { cache } from "lit/directives/cache"; @@ -14,6 +13,7 @@ import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-expansion-panel"; import "../../../../src/components/ha-formfield"; +import "../../../../src/components/ha-textfield"; import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-icon-button"; import "../../../../src/components/ha-radio"; @@ -34,6 +34,7 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; import { haStyleDialog } from "../../../../src/resources/styles"; import type { HomeAssistant } from "../../../../src/types"; import { HassioNetworkDialogParams } from "./show-dialog-network"; +import type { HaTextField } from "../../../../src/components/ha-textfield"; const IP_VERSIONS = ["ipv4", "ipv6"]; @@ -245,7 +246,7 @@ export class DialogHassioNetwork ${this._wifiConfiguration.auth === "wpa-psk" || this._wifiConfiguration.auth === "wep" ? html` - - + ` : ""} ` @@ -358,33 +358,33 @@ export class DialogHassioNetwork
${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`
- -

${person.name}

-
`; - })} -
-
- -
`} - `; - } - - 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`
+ > `; } } diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts index 21aebdfd2c..b121e34ee4 100644 --- a/src/components/entity/ha-entities-picker.ts +++ b/src/components/entity/ha-entities-picker.ts @@ -14,9 +14,9 @@ class HaEntitiesPickerLight extends LitElement { @property({ type: Array }) public value?: string[]; - @property({ type: Boolean }) public disabled?: boolean; + @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public required?: boolean; + @property({ type: Boolean }) public required = false; @property() public helper?: string; @@ -73,7 +73,8 @@ class HaEntitiesPickerLight extends LitElement { @property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string; - @property() public entityFilter?: HaEntityPickerEntityFilterFunc; + @property({ attribute: false }) + public entityFilter?: HaEntityPickerEntityFilterFunc; protected render() { if (!this.hass) { diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index 4e5995a6bf..e746999d21 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -1,8 +1,8 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { html, LitElement, PropertyValues, nothing } from "lit"; -import { customElement, property, query } from "lit/decorators"; +import { LitElement, PropertyValues, html, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display"; -import { ValueChangedEvent, HomeAssistant } from "../../types"; +import { HomeAssistant, ValueChangedEvent } from "../../types"; import "../ha-combo-box"; import type { HaComboBox } from "../ha-combo-box"; @@ -37,7 +37,7 @@ class HaEntityAttributePicker extends LitElement { @property() public helper?: string; - @property({ type: Boolean }) private _opened = false; + @state() private _opened = false; @query("ha-combo-box", true) private _comboBox!: HaComboBox; @@ -47,15 +47,17 @@ class HaEntityAttributePicker extends LitElement { protected updated(changedProps: PropertyValues) { if (changedProps.has("_opened") && this._opened) { - const state = this.entityId ? this.hass.states[this.entityId] : undefined; - (this._comboBox as any).items = state - ? Object.keys(state.attributes) + const entityState = this.entityId + ? this.hass.states[this.entityId] + : undefined; + (this._comboBox as any).items = entityState + ? Object.keys(entityState.attributes) .filter((key) => !this.hideAttributes?.includes(key)) .map((key) => ({ value: key, label: computeAttributeNameDisplay( this.hass.localize, - state, + entityState, this.hass.entities, key ), diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 115e0795aa..1ae47cb99a 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -25,25 +25,15 @@ interface HassEntityWithCachedName extends HassEntity, ScorableTextItem { export type HaEntityPickerEntityFilterFunc = (entity: HassEntity) => boolean; -// eslint-disable-next-line lit/prefer-static-styles -const rowRenderer: ComboBoxLitRenderer = (item) => - html` - ${item.state - ? html`` - : ""} - ${item.friendly_name} - ${item.entity_id} - `; - @customElement("ha-entity-picker") export class HaEntityPicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean }) public autofocus = false; - @property({ type: Boolean }) public disabled?: boolean; + @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public required?: boolean; + @property({ type: Boolean }) public required = false; @property({ type: Boolean, attribute: "allow-custom-entity" }) public allowCustomEntity; @@ -102,7 +92,8 @@ export class HaEntityPicker extends LitElement { @property({ type: Array, attribute: "exclude-entities" }) public excludeEntities?: string[]; - @property() public entityFilter?: HaEntityPickerEntityFilterFunc; + @property({ attribute: false }) + public entityFilter?: HaEntityPickerEntityFilterFunc; @property({ type: Boolean }) public hideClearIcon = false; @@ -127,6 +118,21 @@ export class HaEntityPicker extends LitElement { private _states: HassEntityWithCachedName[] = []; + private _rowRenderer: ComboBoxLitRenderer = ( + item + ) => + html` + ${item.state + ? html`` + : ""} + ${item.friendly_name} + ${item.entity_id} + `; + private _getStates = memoizeOne( ( _opened: boolean, @@ -326,7 +332,7 @@ export class HaEntityPicker extends LitElement { .helper=${this.helper} .allowCustomValue=${this.allowCustomEntity} .filteredItems=${this._states} - .renderer=${rowRenderer} + .renderer=${this._rowRenderer} .required=${this.required} .disabled=${this.disabled} @opened-changed=${this._openedChanged} diff --git a/src/components/entity/ha-entity-state-picker.ts b/src/components/entity/ha-entity-state-picker.ts index b14cbeaa2f..0c91884573 100644 --- a/src/components/entity/ha-entity-state-picker.ts +++ b/src/components/entity/ha-entity-state-picker.ts @@ -1,6 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import { LitElement, PropertyValues, html, nothing } from "lit"; -import { customElement, property, query } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { getStates } from "../../common/entity/get_states"; import { HomeAssistant, ValueChangedEvent } from "../../types"; @@ -17,7 +17,7 @@ class HaEntityStatePicker extends LitElement { @property() public attribute?: string; - @property() public extraOptions?: any[]; + @property({ attribute: false }) public extraOptions?: any[]; @property({ type: Boolean }) public autofocus = false; @@ -34,7 +34,7 @@ class HaEntityStatePicker extends LitElement { @property() public helper?: string; - @property({ type: Boolean }) private _opened = false; + @state() private _opened = false; @query("ha-combo-box", true) private _comboBox!: HaComboBox; @@ -49,16 +49,18 @@ class HaEntityStatePicker extends LitElement { changedProps.has("attribute") || changedProps.has("extraOptions") ) { - const state = this.entityId ? this.hass.states[this.entityId] : undefined; + const stateObj = this.entityId + ? this.hass.states[this.entityId] + : undefined; (this._comboBox as any).items = [ ...(this.extraOptions ?? []), - ...(this.entityId && state - ? getStates(state, this.attribute).map((key) => ({ + ...(this.entityId && stateObj + ? getStates(stateObj, this.attribute).map((key) => ({ value: key, label: !this.attribute - ? this.hass.formatEntityState(state, key) + ? this.hass.formatEntityState(stateObj, key) : this.hass.formatEntityAttributeValue( - state, + stateObj, this.attribute, key ), diff --git a/src/components/entity/ha-entity-toggle.ts b/src/components/entity/ha-entity-toggle.ts index 223b208e1e..5c4c07afc5 100644 --- a/src/components/entity/ha-entity-toggle.ts +++ b/src/components/entity/ha-entity-toggle.ts @@ -1,18 +1,18 @@ import { mdiFlash, mdiFlashOff } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, TemplateResult, + css, + html, } from "lit"; -import { property, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { STATES_OFF } from "../../common/const"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; -import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity"; +import { UNAVAILABLE, UNKNOWN, isUnavailableState } from "../../data/entity"; import { forwardHaptic } from "../../data/haptics"; import { HomeAssistant } from "../../types"; import "../ha-formfield"; @@ -24,11 +24,12 @@ const isOn = (stateObj?: HassEntity) => !STATES_OFF.includes(stateObj.state) && !isUnavailableState(stateObj.state); +@customElement("ha-entity-toggle") export class HaEntityToggle extends LitElement { // hass is not a property so that we only re-render on stateObj changes public hass?: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; @property() public label?: string; @@ -128,6 +129,9 @@ export class HaEntityToggle extends LitElement { } else if (stateDomain === "cover") { serviceDomain = "cover"; service = turnOn ? "open_cover" : "close_cover"; + } else if (stateDomain === "valve") { + serviceDomain = "valve"; + service = turnOn ? "open_valve" : "close_valve"; } else if (stateDomain === "group") { serviceDomain = "homeassistant"; service = turnOn ? "turn_on" : "turn_off"; @@ -175,4 +179,8 @@ export class HaEntityToggle extends LitElement { } } -customElements.define("ha-entity-toggle", HaEntityToggle); +declare global { + interface HTMLElementTagNameMap { + "ha-entity-toggle": HaEntityToggle; + } +} diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index f735452b6e..48854167a5 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -61,7 +61,7 @@ export class HaStateLabelBadge extends LitElement { @property() public image?: string; - @property() public showName?: boolean; + @property({ type: Boolean }) public showName = false; @state() private _timerTimeRemaining?: number; @@ -140,7 +140,8 @@ export class HaStateLabelBadge extends LitElement { ${!image && showIcon ? html`` : ""} ${value && !image && !showIcon @@ -173,7 +174,6 @@ export class HaStateLabelBadge extends LitElement { case "scene": case "sun": case "timer": - case "updater": return null; // @ts-expect-error we don't break and go to default case "sensor": @@ -207,7 +207,6 @@ export class HaStateLabelBadge extends LitElement { case "alarm_control_panel": case "binary_sensor": case "device_tracker": - case "updater": case "person": case "scene": case "sun": @@ -284,8 +283,7 @@ export class HaStateLabelBadge extends LitElement { --ha-label-badge-label-text-transform: none; } - ha-label-badge.binary_sensor, - ha-label-badge.updater { + ha-label-badge.binary_sensor { --ha-label-badge-color: var(--label-badge-blue); } diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index b2227ee7af..a592937d37 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -44,7 +44,7 @@ export class HaStatisticPicker extends LitElement { @property({ type: Array }) public statisticIds?: StatisticsMetaData[]; - @property({ type: Boolean }) public disabled?: boolean; + @property({ type: Boolean }) public disabled = false; /** * Show only statistics natively stored with these units of measurements. @@ -105,6 +105,7 @@ export class HaStatisticPicker extends LitElement { ? html`` : ""} ${item.name} @@ -129,7 +130,8 @@ export class HaStatisticPicker extends LitElement { includeUnitClass?: string | string[], includeDeviceClass?: string | string[], entitiesOnly?: boolean, - excludeStatistics?: string[] + excludeStatistics?: string[], + value?: string ): StatisticItem[] => { if (!statisticIds.length) { return [ @@ -176,6 +178,7 @@ export class HaStatisticPicker extends LitElement { statisticIds.forEach((meta) => { if ( excludeStatistics && + meta.statistic_id !== value && excludeStatistics.includes(meta.statistic_id) ) { return; @@ -258,7 +261,8 @@ export class HaStatisticPicker extends LitElement { this.includeUnitClass, this.includeDeviceClass, this.entitiesOnly, - this.excludeStatistics + this.excludeStatistics, + this.value ); } else { this.updateComplete.then(() => { @@ -268,7 +272,8 @@ export class HaStatisticPicker extends LitElement { this.includeUnitClass, this.includeDeviceClass, this.entitiesOnly, - this.excludeStatistics + this.excludeStatistics, + this.value ); }); } diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts index 7079e5a6d5..115335094f 100644 --- a/src/components/entity/ha-statistics-picker.ts +++ b/src/components/entity/ha-statistics-picker.ts @@ -1,5 +1,6 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; import { fireEvent } from "../../common/dom/fire_event"; import type { ValueChangedEvent, HomeAssistant } from "../../types"; import "./ha-statistic-picker"; @@ -81,7 +82,9 @@ class HaStatisticsPicker extends LitElement { : this.statisticTypes; return html` - ${this._currentStatistics.map( + ${repeat( + this._currentStatistics, + (statisticId) => statisticId, (statisticId) => html`
@@ -110,6 +114,7 @@ class HaStatisticsPicker extends LitElement { .statisticTypes=${this.statisticTypes} .statisticIds=${this.statisticIds} .label=${this.pickStatisticLabel} + .excludeStatistics=${this.value} .allowCustomEntity=${this.allowCustomEntity} @value-changed=${this._addStatistic} > diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts index 4ef0166fc9..75d077ed51 100644 --- a/src/components/entity/state-badge.ts +++ b/src/components/entity/state-badge.ts @@ -1,11 +1,11 @@ import { mdiAlert } from "@mdi/js"; import type { HassEntity } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, + css, + html, nothing, } from "lit"; import { property, state } from "lit/decorators"; @@ -14,8 +14,8 @@ import { styleMap } from "lit/directives/style-map"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { - stateColorCss, stateColorBrightness, + stateColorCss, } from "../../common/entity/state_color"; import { iconColorCSS } from "../../common/style/icon_color_css"; import { cameraUrlWithWidthHeight } from "../../data/camera"; @@ -26,18 +26,18 @@ import "../ha-state-icon"; export class StateBadge extends LitElement { public hass?: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; @property() public overrideIcon?: string; @property() public overrideImage?: string; - @property({ type: Boolean }) public stateColor?: boolean; + @property({ type: Boolean }) public stateColor = false; @property() public color?: string; - @property({ type: Boolean, reflect: true, attribute: "icon" }) - private _showIcon = true; + // @todo Consider reworking to eliminate need for attribute since it is manipulated internally + @property({ type: Boolean, reflect: true }) public icon = true; @state() private _iconStyle: { [name: string]: string | undefined } = {}; @@ -83,22 +83,23 @@ export class StateBadge extends LitElement {
`; } - if (!this._showIcon) { + if (!this.icon) { return nothing; } const domain = stateObj ? computeStateDomain(stateObj) : undefined; return html``; } - public willUpdate(changedProps: PropertyValues) { + public willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); if ( !changedProps.has("stateObj") && @@ -114,7 +115,7 @@ export class StateBadge extends LitElement { const iconStyle: { [name: string]: string } = {}; let backgroundImage = ""; - this._showIcon = true; + this.icon = true; if (stateObj && this.overrideImage === undefined) { // hide icon if we have entity picture @@ -134,7 +135,7 @@ export class StateBadge extends LitElement { imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80); } backgroundImage = `url(${imageUrl})`; - this._showIcon = false; + this.icon = false; if (domain === "update") { this.style.borderRadius = "0"; } else if (domain === "media_player") { @@ -180,7 +181,7 @@ export class StateBadge extends LitElement { imageUrl = this.hass.hassUrl(imageUrl); } backgroundImage = `url(${imageUrl})`; - this._showIcon = false; + this.icon = false; } this._iconStyle = iconStyle; diff --git a/src/components/entity/state-info.ts b/src/components/entity/state-info.ts index 5e1564ff4b..db631e4496 100644 --- a/src/components/entity/state-info.ts +++ b/src/components/entity/state-info.ts @@ -29,6 +29,7 @@ class StateInfo extends LitElement { const name = computeStateName(this.stateObj); return html` ${this.label} ${description} - + > `; } diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 423de3df7a..b68b08039a 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -1,6 +1,6 @@ import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { HassEntity } from "home-assistant-js-websocket"; -import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; @@ -36,8 +36,12 @@ type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry; const rowRenderer: ComboBoxLitRenderer = (item) => html` + ${item.icon + ? html`` + : nothing} ${item.name} `; @@ -54,7 +58,7 @@ export class HaAreaPicker extends LitElement { @property() public placeholder?: string; @property({ type: Boolean, attribute: "no-add" }) - public noAdd?: boolean; + public noAdd = false; /** * Show only areas with entities from specific domains. @@ -88,13 +92,15 @@ export class HaAreaPicker extends LitElement { @property({ type: Array, attribute: "exclude-areas" }) public excludeAreas?: string[]; - @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; + @property({ attribute: false }) + public deviceFilter?: HaDevicePickerDeviceFilterFunc; - @property() public entityFilter?: (entity: HassEntity) => boolean; + @property({ attribute: false }) + public entityFilter?: (entity: HassEntity) => boolean; - @property({ type: Boolean }) public disabled?: boolean; + @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public required?: boolean; + @property({ type: Boolean }) public required = false; @state() private _opened?: boolean; @@ -133,6 +139,7 @@ export class HaAreaPicker extends LitElement { area_id: "no_areas", name: this.hass.localize("ui.components.area-picker.no_areas"), picture: null, + icon: null, aliases: [], }, ]; @@ -260,7 +267,9 @@ export class HaAreaPicker extends LitElement { } if (areaIds) { - outputAreas = areas.filter((area) => areaIds!.includes(area.area_id)); + outputAreas = outputAreas.filter((area) => + areaIds!.includes(area.area_id) + ); } if (excludeAreas) { @@ -275,6 +284,7 @@ export class HaAreaPicker extends LitElement { area_id: "no_areas", name: this.hass.localize("ui.components.area-picker.no_match"), picture: null, + icon: null, aliases: [], }, ]; @@ -288,6 +298,7 @@ export class HaAreaPicker extends LitElement { area_id: "add_new", name: this.hass.localize("ui.components.area-picker.add_new"), picture: null, + icon: "mdi:plus", aliases: [], }, ]; diff --git a/src/components/ha-areas-picker.ts b/src/components/ha-areas-picker.ts index eff9622a0a..12defd8611 100644 --- a/src/components/ha-areas-picker.ts +++ b/src/components/ha-areas-picker.ts @@ -13,14 +13,14 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) { @property() public label?: string; - @property() public value?: string[]; + @property({ type: Array }) public value?: string[]; @property() public helper?: string; @property() public placeholder?: string; @property({ type: Boolean, attribute: "no-add" }) - public noAdd?: boolean; + public noAdd = false; /** * Show only areas with entities from specific domains. @@ -46,9 +46,11 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) { @property({ type: Array, attribute: "include-device-classes" }) public includeDeviceClasses?: string[]; - @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; + @property({ attribute: false }) + public deviceFilter?: HaDevicePickerDeviceFilterFunc; - @property() public entityFilter?: (entity: HassEntity) => boolean; + @property({ attribute: false }) + public entityFilter?: (entity: HassEntity) => boolean; @property({ attribute: "picked-area-label" }) public pickedAreaLabel?: string; @@ -56,9 +58,9 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) { @property({ attribute: "pick-area-label" }) public pickAreaLabel?: string; - @property({ type: Boolean }) public disabled?: boolean; + @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public required?: boolean; + @property({ type: Boolean }) public required = false; protected render() { if (!this.hass) { diff --git a/src/components/ha-assist-pipeline-picker.ts b/src/components/ha-assist-pipeline-picker.ts index 80a69b2d27..d6cb6c30bb 100644 --- a/src/components/ha-assist-pipeline-picker.ts +++ b/src/components/ha-assist-pipeline-picker.ts @@ -31,7 +31,7 @@ export class HaAssistPipelinePicker extends LitElement { @property({ type: Boolean }) public required = false; - @property() public includeLastUsed = false; + @property({ type: Boolean }) public includeLastUsed = false; @state() _pipelines?: AssistPipeline[]; diff --git a/src/components/ha-attribute-icon.ts b/src/components/ha-attribute-icon.ts new file mode 100644 index 0000000000..ae84900ac5 --- /dev/null +++ b/src/components/ha-attribute-icon.ts @@ -0,0 +1,55 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import { until } from "lit/directives/until"; +import { attributeIcon } from "../data/icons"; +import { HomeAssistant } from "../types"; +import "./ha-icon"; +import "./ha-svg-icon"; + +@customElement("ha-attribute-icon") +export class HaAttributeIcon extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HassEntity; + + @property() public attribute?: string; + + @property() public attributeValue?: string; + + @property() public icon?: string; + + protected render() { + if (this.icon) { + return html``; + } + + if (!this.stateObj || !this.attribute) { + return nothing; + } + + if (!this.hass) { + return nothing; + } + + const icon = attributeIcon( + this.hass, + this.stateObj, + this.attribute, + this.attributeValue + ).then((icn) => { + if (icn) { + return html``; + } + return nothing; + }); + + return html`${until(icon)}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-attribute-icon": HaAttributeIcon; + } +} diff --git a/src/components/ha-attribute-value.ts b/src/components/ha-attribute-value.ts index 3efe39e47b..a56bd7d399 100644 --- a/src/components/ha-attribute-value.ts +++ b/src/components/ha-attribute-value.ts @@ -9,12 +9,11 @@ import { HomeAssistant } from "../types"; class HaAttributeValue extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; @property() public attribute!: string; - @property({ type: Boolean, attribute: "hide-unit" }) - public hideUnit?: boolean; + @property({ type: Boolean, attribute: "hide-unit" }) public hideUnit = false; protected render() { if (!this.stateObj) { diff --git a/src/components/ha-attributes.ts b/src/components/ha-attributes.ts index d63b987a56..326866a5e2 100644 --- a/src/components/ha-attributes.ts +++ b/src/components/ha-attributes.ts @@ -19,7 +19,7 @@ import "./ha-expansion-panel"; class HaAttributes extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; @property({ attribute: "extra-filters" }) public extraFilters?: string; diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index 8db3e132e1..54490e047b 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -353,6 +353,8 @@ export class HaBaseTimeInput extends LitElement { text-transform: var(--mdc-typography-body2-text-transform, inherit); color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87)); padding-left: 4px; + padding-inline-start: 4px; + padding-inline-end: initial; } `; } diff --git a/src/components/ha-big-number.ts b/src/components/ha-big-number.ts index 85a7783c87..f9e3d8e62d 100644 --- a/src/components/ha-big-number.ts +++ b/src/components/ha-big-number.ts @@ -7,15 +7,14 @@ import { HomeAssistant } from "../types"; @customElement("ha-big-number") export class HaBigNumber extends LitElement { - @property() public value!: number; + @property({ type: Number }) public value!: number; @property() public unit?: string; @property({ attribute: "unit-position" }) public unitPosition: "top" | "bottom" = "top"; - @property({ attribute: false }) - public hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public formatOptions: Intl.NumberFormatOptions = {}; @@ -33,7 +32,9 @@ export class HaBigNumber extends LitElement { const temperatureDecimal = formatted.replace(integer, ""); const formattedValue = `${this.value}${ - this.unit ? `${blankBeforeUnit(this.unit)}${this.unit}` : "" + this.unit + ? `${blankBeforeUnit(this.unit, this.hass?.locale)}${this.unit}` + : "" }`; const unitBottom = this.unitPosition === "bottom"; diff --git a/src/components/ha-blueprint-picker.ts b/src/components/ha-blueprint-picker.ts index 214f9599c3..7ddbd3c0c8 100644 --- a/src/components/ha-blueprint-picker.ts +++ b/src/components/ha-blueprint-picker.ts @@ -24,7 +24,7 @@ class HaBluePrintPicker extends LitElement { @property() public domain: BlueprintDomain = "automation"; - @property() public blueprints?: Blueprints; + @property({ attribute: false }) public blueprints?: Blueprints; @property({ type: Boolean }) public disabled = false; diff --git a/src/components/ha-button-related-filter-menu.ts b/src/components/ha-button-related-filter-menu.ts index 0225f77cab..77c7848baa 100644 --- a/src/components/ha-button-related-filter-menu.ts +++ b/src/components/ha-button-related-filter-menu.ts @@ -33,7 +33,7 @@ interface FilterValue { @customElement("ha-button-related-filter-menu") export class HaRelatedFilterButtonMenu extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @property() public corner: Corner = "BOTTOM_START"; diff --git a/src/components/ha-check-list-item.ts b/src/components/ha-check-list-item.ts index 19f04feda9..0152f19fcc 100644 --- a/src/components/ha-check-list-item.ts +++ b/src/components/ha-check-list-item.ts @@ -37,6 +37,10 @@ export class HaCheckListItem extends CheckListItemBase { .mdc-deprecated-list-item__graphic { margin-top: var(--check-list-item-graphic-margin-top); } + :host([graphic="icon"]) .mdc-deprecated-list-item__graphic { + margin-inline-start: 0; + margin-inline-end: var(--mdc-list-item-graphic-margin, 32px); + } `, ]; } diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index ce6d54f415..01b33c9a06 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -95,14 +95,13 @@ export class HaComboBox extends LitElement { @property({ attribute: "item-id-path" }) public itemIdPath?: string; - @property() public renderer?: ComboBoxLitRenderer; + @property({ attribute: false }) public renderer?: ComboBoxLitRenderer; @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public required = false; - @property({ type: Boolean, reflect: true, attribute: "opened" }) - public opened?: boolean; + @property({ type: Boolean, reflect: true }) public opened = false; @query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight; @@ -145,6 +144,7 @@ export class HaComboBox extends LitElement { protected render(): TemplateResult { return html` + { const bound = this._slider.getBoundingClientRect(); @@ -201,8 +197,8 @@ export class HaControlCircularSlider extends LitElement { @query("#slider") private _slider; - @query("#interaction") - private _interaction; + @queryAll("[data-interaction]") + private _interactions?: HTMLElement[]; private _findActiveSlider(value: number): ActiveSlider { if (!this.dual) return "value"; @@ -245,135 +241,148 @@ export class HaControlCircularSlider extends LitElement { return undefined; } - _setupListeners() { - if (this._interaction && !this._mc) { - this._mc = new Manager(this._interaction, { - inputClass: TouchMouseInput, - }); + private _setupListeners() { + if (this._interactions && this._managers.length === 0) { + this._interactions.forEach((interaction) => { + const mc = new Manager(interaction, { + inputClass: TouchMouseInput, + }); - const pressToActivate = - this.preventInteractionOnScroll && "ontouchstart" in window; + this._managers.push(mc); - // If press to activate is true, a 60ms press is required to activate the slider - this._mc.add( - new Press({ - enable: pressToActivate, - pointers: 1, - time: 60, - }) - ); + const pressToActivate = this.preventInteractionOnScroll && isTouch; - const panRecognizer = new Pan({ - direction: DIRECTION_ALL, - enable: !pressToActivate, - threshold: 0, - }); + // If press to activate is true, a 50ms press is required to activate the slider + mc.add( + new Press({ + enable: pressToActivate, + pointers: 1, + time: 50, + }) + ); - this._mc.add(panRecognizer); + const panRecognizer = new Pan({ + direction: DIRECTION_ALL, + enable: !pressToActivate, + threshold: 0, + }); - this._mc.add(new Tap({ event: "singletap" })); + mc.add(panRecognizer); - this._mc.on("press", (e) => { - e.srcEvent.stopPropagation(); - e.srcEvent.preventDefault(); - if (this.disabled || this.readonly) return; - const percentage = this._getPercentageFromEvent(e); - const raw = this._percentageToValue(percentage); - this._activeSlider = this._findActiveSlider(raw); - const bounded = this._boundedValue(raw); - this._setActiveValue(bounded); - const stepped = this._steppedValue(bounded); - if (this._activeSlider) { - fireEvent(this, `${this._activeSlider}-changing`, { value: stepped }); - } - panRecognizer.set({ enable: true }); - }); + mc.add(new Tap({ event: "singletap" })); - this._mc.on("pressup", (e) => { - e.srcEvent.stopPropagation(); - e.srcEvent.preventDefault(); - const percentage = this._getPercentageFromEvent(e); - const raw = this._percentageToValue(percentage); - const bounded = this._boundedValue(raw); - const stepped = this._steppedValue(bounded); - this._setActiveValue(stepped); - if (this._activeSlider) { - fireEvent(this, `${this._activeSlider}-changing`, { - value: undefined, - }); - fireEvent(this, `${this._activeSlider}-changed`, { value: stepped }); - } - this._activeSlider = undefined; - }); + mc.on("press", (e) => { + e.srcEvent.stopPropagation(); + e.srcEvent.preventDefault(); + if (this.disabled || this.readonly) return; + const percentage = this._getPercentageFromEvent(e); + const raw = this._percentageToValue(percentage); + this._activeSlider = this._findActiveSlider(raw); + const bounded = this._boundedValue(raw); + this._setActiveValue(bounded); + const stepped = this._steppedValue(bounded); + if (this._activeSlider) { + fireEvent(this, `${this._activeSlider}-changing`, { + value: stepped, + }); + } + panRecognizer.set({ enable: true }); + }); - this._mc.on("pan", (e) => { - e.srcEvent.stopPropagation(); - e.srcEvent.preventDefault(); - }); - this._mc.on("panstart", (e) => { - if (this.disabled || this.readonly) return; - const percentage = this._getPercentageFromEvent(e); - const raw = this._percentageToValue(percentage); - this._activeSlider = this._findActiveSlider(raw); - this._lastSlider = this._activeSlider; - this.shadowRoot?.getElementById("#slider")?.focus(); - }); - this._mc.on("pancancel", () => { - if (this.disabled || this.readonly) return; - this._activeSlider = undefined; - if (pressToActivate) { - panRecognizer.set({ enable: false }); - } - }); - this._mc.on("panmove", (e) => { - if (this.disabled || this.readonly) return; - const percentage = this._getPercentageFromEvent(e); - const raw = this._percentageToValue(percentage); - const bounded = this._boundedValue(raw); - this._setActiveValue(bounded); - const stepped = this._steppedValue(bounded); - if (this._activeSlider) { - fireEvent(this, `${this._activeSlider}-changing`, { value: stepped }); - } - }); - this._mc.on("panend", (e) => { - if (this.disabled || this.readonly) return; - const percentage = this._getPercentageFromEvent(e); - const raw = this._percentageToValue(percentage); - const bounded = this._boundedValue(raw); - const stepped = this._steppedValue(bounded); - this._setActiveValue(stepped); - if (this._activeSlider) { - fireEvent(this, `${this._activeSlider}-changing`, { - value: undefined, - }); - fireEvent(this, `${this._activeSlider}-changed`, { value: stepped }); - } - this._activeSlider = undefined; - if (pressToActivate) { - panRecognizer.set({ enable: false }); - } - }); - this._mc.on("singletap", (e) => { - if (this.disabled || this.readonly) return; - const percentage = this._getPercentageFromEvent(e); - const raw = this._percentageToValue(percentage); - this._activeSlider = this._findActiveSlider(raw); - const bounded = this._boundedValue(raw); - const stepped = this._steppedValue(bounded); - this._setActiveValue(stepped); - if (this._activeSlider) { - fireEvent(this, `${this._activeSlider}-changing`, { - value: undefined, - }); - fireEvent(this, `${this._activeSlider}-changed`, { value: stepped }); - } - this._lastSlider = this._activeSlider; - this.shadowRoot?.getElementById("#slider")?.focus(); - this._activeSlider = undefined; - if (pressToActivate) { - panRecognizer.set({ enable: false }); - } + mc.on("pressup", (e) => { + e.srcEvent.stopPropagation(); + e.srcEvent.preventDefault(); + const percentage = this._getPercentageFromEvent(e); + const raw = this._percentageToValue(percentage); + const bounded = this._boundedValue(raw); + const stepped = this._steppedValue(bounded); + this._setActiveValue(stepped); + if (this._activeSlider) { + fireEvent(this, `${this._activeSlider}-changing`, { + value: undefined, + }); + fireEvent(this, `${this._activeSlider}-changed`, { + value: stepped, + }); + } + this._activeSlider = undefined; + }); + + mc.on("pan", (e) => { + e.srcEvent.stopPropagation(); + e.srcEvent.preventDefault(); + }); + mc.on("panstart", (e) => { + if (this.disabled || this.readonly) return; + const percentage = this._getPercentageFromEvent(e); + const raw = this._percentageToValue(percentage); + this._activeSlider = this._findActiveSlider(raw); + this._lastSlider = this._activeSlider; + this.shadowRoot?.getElementById("#slider")?.focus(); + }); + mc.on("pancancel", () => { + if (this.disabled || this.readonly) return; + this._activeSlider = undefined; + if (pressToActivate) { + panRecognizer.set({ enable: false }); + } + }); + mc.on("panmove", (e) => { + if (this.disabled || this.readonly) return; + const percentage = this._getPercentageFromEvent(e); + const raw = this._percentageToValue(percentage); + const bounded = this._boundedValue(raw); + this._setActiveValue(bounded); + const stepped = this._steppedValue(bounded); + if (this._activeSlider) { + fireEvent(this, `${this._activeSlider}-changing`, { + value: stepped, + }); + } + }); + mc.on("panend", (e) => { + if (this.disabled || this.readonly) return; + const percentage = this._getPercentageFromEvent(e); + const raw = this._percentageToValue(percentage); + const bounded = this._boundedValue(raw); + const stepped = this._steppedValue(bounded); + this._setActiveValue(stepped); + if (this._activeSlider) { + fireEvent(this, `${this._activeSlider}-changing`, { + value: undefined, + }); + fireEvent(this, `${this._activeSlider}-changed`, { + value: stepped, + }); + } + this._activeSlider = undefined; + if (pressToActivate) { + panRecognizer.set({ enable: false }); + } + }); + mc.on("singletap", (e) => { + if (this.disabled || this.readonly) return; + const percentage = this._getPercentageFromEvent(e); + const raw = this._percentageToValue(percentage); + this._activeSlider = this._findActiveSlider(raw); + const bounded = this._boundedValue(raw); + const stepped = this._steppedValue(bounded); + this._setActiveValue(stepped); + if (this._activeSlider) { + fireEvent(this, `${this._activeSlider}-changing`, { + value: undefined, + }); + fireEvent(this, `${this._activeSlider}-changed`, { + value: stepped, + }); + } + this._lastSlider = this._activeSlider; + this.shadowRoot?.getElementById("#slider")?.focus(); + this._activeSlider = undefined; + if (pressToActivate) { + panRecognizer.set({ enable: false }); + } + }); }); } } @@ -447,10 +456,10 @@ export class HaControlCircularSlider extends LitElement { this._activeSlider = undefined; } - destroyListeners() { - if (this._mc) { - this._mc.destroy(); - this._mc = undefined; + private _destroyListeners() { + if (this._managers.length > 0) { + this._managers.forEach((manager) => manager.destroy()); + this._managers = []; } } @@ -486,6 +495,9 @@ export class HaControlCircularSlider extends LitElement { r: RADIUS, }); + const angle = + value != null ? this._valueToPercentage(value) * MAX_ANGLE : undefined; + const limit = mode === "end" ? this.max : this.min; const current = this.current ?? limit; @@ -527,6 +539,9 @@ export class HaControlCircularSlider extends LitElement { ? this._strokeCircleDashArc(this.current) : undefined; + const onlyDotInteraction = + (this.preventInteractionOnScroll && isTouch) || false; + return svg` + + - - - - - - ${currentStroke - ? svg` + + + ${currentStroke + ? svg` ` - : nothing} - ${lowValue != null || this.mode === "full" - ? this.renderArc( - this.dual ? "low" : "value", - lowValue, - (!this.dual && this.mode) || "start" - ) - : nothing} - ${this.dual && highValue != null - ? this.renderArc("high", highValue, "end") - : nothing} - + : nothing} + ${lowValue != null || this.mode === "full" + ? this.renderArc( + this.dual ? "low" : "value", + lowValue, + (!this.dual && this.mode) || "start" + ) + : nothing} + ${this.dual && highValue != null + ? this.renderArc("high", highValue, "end") + : nothing} `; @@ -684,26 +710,34 @@ export class HaControlCircularSlider extends LitElement { svg { width: 100%; display: block; + pointer-events: none; + } + g { + fill: none; } #slider { outline: none; } - #interaction { - display: flex; + path[data-interaction] { fill: none; + cursor: pointer; + pointer-events: auto; stroke: transparent; stroke-linecap: round; stroke-width: calc( 24px + 2 * var(--control-circular-slider-interaction-margin) ); + } + circle[data-interaction] { + r: calc(12px + var(--control-circular-slider-interaction-margin)); + fill: transparent; cursor: pointer; + pointer-events: auto; } - #display { - pointer-events: none; - } - :host([disabled]) #interaction, - :host([readonly]) #interaction { + :host([disabled]) [data-interaction], + :host([readonly]) [data-interaction] { cursor: initial; + pointer-events: none; } .background { diff --git a/src/components/ha-control-number-buttons.ts b/src/components/ha-control-number-buttons.ts index 1311b244b3..36172f4e08 100644 --- a/src/components/ha-control-number-buttons.ts +++ b/src/components/ha-control-number-buttons.ts @@ -15,6 +15,7 @@ import { conditionalClamp } from "../common/number/clamp"; import { formatNumber } from "../common/number/format_number"; import { blankBeforeUnit } from "../common/translations/blank_before_unit"; import { FrontendLocaleData } from "../data/translation"; +import "./ha-svg-icon"; const A11Y_KEY_CODES = new Set([ "ArrowRight", @@ -136,7 +137,9 @@ export class HaControlNumberButton extends LitElement { this.value != null ? formatNumber(this.value, this.locale, this.formatOptions) : ""; - const unit = this.unit ? `${blankBeforeUnit(this.unit)}${this.unit}` : ""; + const unit = this.unit + ? `${blankBeforeUnit(this.unit, this.locale)}${this.unit}` + : ""; return html`
@@ -144,11 +147,11 @@ export class HaControlNumberButton extends LitElement { id="input" class="value" role="spinbutton" - .tabIndex=${this.disabled ? "-1" : "0"} - aria-valuenow=${this.value} + tabindex=${this.disabled ? "-1" : "0"} + aria-valuenow=${ifDefined(this.value)} aria-valuetext=${`${value}${unit}`} - aria-valuemin=${this.min} - aria-valuemax=${this.max} + aria-valuemin=${ifDefined(this.min)} + aria-valuemax=${ifDefined(this.max)} aria-label=${ifDefined(this.label)} ?disabled=${this.disabled} @keydown=${this._handleKeyDown} @@ -167,7 +170,7 @@ export class HaControlNumberButton extends LitElement { .disabled=${this.disabled || (this.min != null && this._value <= this.min)} > - +
`; diff --git a/src/components/ha-control-select-menu.ts b/src/components/ha-control-select-menu.ts index 78ffa238db..c26d1cee5b 100644 --- a/src/components/ha-control-select-menu.ts +++ b/src/components/ha-control-select-menu.ts @@ -27,10 +27,10 @@ export class HaControlSelectMenu extends SelectBase { @query(".select-anchor") protected anchorElement!: HTMLDivElement | null; @property({ type: Boolean, attribute: "show-arrow" }) - public showArrow?: boolean; + public showArrow = false; @property({ type: Boolean, attribute: "hide-label" }) - public hideLabel?: boolean; + public hideLabel = false; @queryAsync("mwc-ripple") private _ripple!: Promise; @@ -126,9 +126,9 @@ export class HaControlSelectMenu extends SelectBase { return html`
- ${icon && "path" in icon + ${icon && icon.localName === "ha-svg-icon" && "path" in icon ? html`` - : icon && "icon" in icon + : icon && icon.localName === "ha-icon" && "icon" in icon ? html`` : html``}
diff --git a/src/components/ha-control-select.ts b/src/components/ha-control-select.ts index 91c740d0e5..4cacb34fd2 100644 --- a/src/components/ha-control-select.ts +++ b/src/components/ha-control-select.ts @@ -5,6 +5,7 @@ import { LitElement, nothing, PropertyValues, + TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -17,7 +18,7 @@ import "./ha-svg-icon"; export type ControlSelectOption = { value: string; label?: string; - icon?: string; + icon?: TemplateResult; path?: string; }; @@ -25,7 +26,7 @@ export type ControlSelectOption = { export class HaControlSelect extends LitElement { @property({ type: Boolean, reflect: true }) disabled = false; - @property() public options?: ControlSelectOption[]; + @property({ attribute: false }) public options?: ControlSelectOption[]; @property() public value?: string; @@ -183,9 +184,7 @@ export class HaControlSelect extends LitElement {
${option.path ? html`` - : option.icon - ? html` ` - : nothing} + : option.icon || nothing} ${option.label && !this.hideLabel ? html`${option.label}` : nothing} diff --git a/src/components/ha-control-switch.ts b/src/components/ha-control-switch.ts index ef2968b69a..2626b2ea20 100644 --- a/src/components/ha-control-switch.ts +++ b/src/components/ha-control-switch.ts @@ -19,17 +19,13 @@ import "./ha-svg-icon"; @customElement("ha-control-switch") export class HaControlSwitch extends LitElement { - @property({ type: Boolean, reflect: true }) - public disabled = false; + @property({ type: Boolean, reflect: true }) public disabled = false; - @property({ type: Boolean }) - public vertical = false; + @property({ type: Boolean }) public vertical = false; - @property({ type: Boolean }) - public reversed = false; + @property({ type: Boolean }) public reversed = false; - @property({ type: Boolean, reflect: true }) - public checked?: boolean; + @property({ type: Boolean, reflect: true }) public checked = false; // SVG icon path (if you need a non SVG icon instead, use the provided on icon slot to pass an in) @property({ type: String }) pathOn?: string; diff --git a/src/components/ha-country-picker.ts b/src/components/ha-country-picker.ts index fea293a2b4..55b7768c40 100644 --- a/src/components/ha-country-picker.ts +++ b/src/components/ha-country-picker.ts @@ -269,7 +269,7 @@ export class HaCountryPicker extends LitElement { @property() public label?: string; - @property() public countries?: string[]; + @property({ type: Array }) public countries?: string[]; @property() public helper?: string; diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index d4d9922639..80237bd3aa 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -50,7 +50,7 @@ export class HaDateInput extends LitElement { @property() public helper?: string; - @property({ type: Boolean }) public canClear?: boolean; + @property({ type: Boolean }) public canClear = false; render() { return html` diff --git a/src/components/ha-dialog-date-picker.ts b/src/components/ha-dialog-date-picker.ts index b71e063ae4..63d78f3155 100644 --- a/src/components/ha-dialog-date-picker.ts +++ b/src/components/ha-dialog-date-picker.ts @@ -12,7 +12,7 @@ import "./ha-dialog"; @customElement("ha-dialog-date-picker") export class HaDialogDatePicker extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @property() public value?: string; diff --git a/src/components/ha-domain-icon.ts b/src/components/ha-domain-icon.ts new file mode 100644 index 0000000000..15659cb8c2 --- /dev/null +++ b/src/components/ha-domain-icon.ts @@ -0,0 +1,84 @@ +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import { until } from "lit/directives/until"; +import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../common/const"; +import { domainIcon } from "../data/icons"; +import { HomeAssistant } from "../types"; +import { brandsUrl } from "../util/brands-url"; +import "./ha-icon"; + +@customElement("ha-domain-icon") +export class HaDomainIcon extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public domain?: string; + + @property() public deviceClass?: string; + + @property() public icon?: string; + + @property({ type: Boolean }) public brandFallback?: boolean; + + protected render() { + if (this.icon) { + return html``; + } + + if (!this.domain) { + return nothing; + } + + if (!this.hass) { + return this._renderFallback(); + } + + const icon = domainIcon(this.hass, this.domain, this.deviceClass).then( + (icn) => { + if (icn) { + return html``; + } + return this._renderFallback(); + } + ); + + return html`${until(icon)}`; + } + + private _renderFallback() { + if (this.domain! in FIXED_DOMAIN_ICONS) { + return html` + + `; + } + if (this.brandFallback) { + const image = brandsUrl({ + domain: this.domain!, + type: "icon", + darkOptimized: this.hass.themes?.darkMode, + }); + return html` + + `; + } + return html``; + } + + static get styles(): CSSResultGroup { + return css` + img { + width: var(--mdc-icon-size, 24px); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-domain-icon": HaDomainIcon; + } +} diff --git a/src/components/ha-expansion-panel.ts b/src/components/ha-expansion-panel.ts index abd0a81ac4..a148c37f1b 100644 --- a/src/components/ha-expansion-panel.ts +++ b/src/components/ha-expansion-panel.ts @@ -168,12 +168,18 @@ export class HaExpansionPanel extends LitElement { } .summary-icon { + transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); + direction: var(--direction); margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } :host([leftchevron]) .summary-icon { margin-left: 0; margin-right: 8px; + margin-inline-start: 0; + margin-inline-end: 8px; } #summary { @@ -188,11 +194,6 @@ export class HaExpansionPanel extends LitElement { outline: none; } - .summary-icon { - transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); - direction: var(--direction); - } - .summary-icon.expanded { transform: rotate(180deg); } diff --git a/src/components/ha-file-upload.ts b/src/components/ha-file-upload.ts index 29e8b07ed4..8b03bb8417 100644 --- a/src/components/ha-file-upload.ts +++ b/src/components/ha-file-upload.ts @@ -31,18 +31,18 @@ export class HaFileUpload extends LitElement { @property() public supports?: string; - @property() public value?: File | File[] | FileList | string; + @property({ type: Object }) public value?: File | File[] | FileList | string; - @property({ type: Boolean }) private multiple = false; + @property({ type: Boolean }) public multiple = false; - @property({ type: Boolean, reflect: true }) public disabled: boolean = false; + @property({ type: Boolean, reflect: true }) public disabled = false; - @property({ type: Boolean }) private uploading = false; + @property({ type: Boolean }) public uploading = false; - @property({ type: Number }) private progress?: number; + @property({ type: Number }) public progress?: number; @property({ type: Boolean, attribute: "auto-open-file-dialog" }) - private autoOpenFileDialog = false; + public autoOpenFileDialog = false; @state() private _drag = false; @@ -282,6 +282,8 @@ export class HaFileUpload extends LitElement { } .value ha-svg-icon { margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } .big-icon { --mdc-icon-size: 48px; diff --git a/src/components/ha-form/ha-form-boolean.ts b/src/components/ha-form/ha-form-boolean.ts index 1e62d3fd13..171739e844 100644 --- a/src/components/ha-form/ha-form-boolean.ts +++ b/src/components/ha-form/ha-form-boolean.ts @@ -12,9 +12,9 @@ import "../ha-checkbox"; @customElement("ha-form-boolean") export class HaFormBoolean extends LitElement implements HaFormElement { - @property() public schema!: HaFormBooleanSchema; + @property({ attribute: false }) public schema!: HaFormBooleanSchema; - @property() public data!: HaFormBooleanData; + @property({ attribute: false }) public data!: HaFormBooleanData; @property() public label!: string; diff --git a/src/components/ha-form/ha-form-expandable.ts b/src/components/ha-form/ha-form-expandable.ts index 79ed2162dd..f8e88c43aa 100644 --- a/src/components/ha-form/ha-form-expandable.ts +++ b/src/components/ha-form/ha-form-expandable.ts @@ -19,12 +19,14 @@ export class HaFormExpendable extends LitElement implements HaFormElement { @property({ type: Boolean }) public disabled = false; - @property() public computeLabel?: ( + @property({ attribute: false }) public computeLabel?: ( schema: HaFormSchema, data?: HaFormDataContainer ) => string; - @property() public computeHelper?: (schema: HaFormSchema) => string; + @property({ attribute: false }) public computeHelper?: ( + schema: HaFormSchema + ) => string; protected render() { return html` diff --git a/src/components/ha-form/ha-form-float.ts b/src/components/ha-form/ha-form-float.ts index 2ed59db428..cfee72ebe1 100644 --- a/src/components/ha-form/ha-form-float.ts +++ b/src/components/ha-form/ha-form-float.ts @@ -4,9 +4,12 @@ import { fireEvent } from "../../common/dom/fire_event"; import type { HaTextField } from "../ha-textfield"; import "../ha-textfield"; import { HaFormElement, HaFormFloatData, HaFormFloatSchema } from "./types"; +import { LocalizeFunc } from "../../common/translations/localize"; @customElement("ha-form-float") export class HaFormFloat extends LitElement implements HaFormElement { + @property({ attribute: false }) public localize?: LocalizeFunc; + @property({ attribute: false }) public schema!: HaFormFloatSchema; @property({ attribute: false }) public data!: HaFormFloatData; @@ -38,7 +41,9 @@ export class HaFormFloat extends LitElement implements HaFormElement { .required=${this.schema.required} .autoValidate=${this.schema.required} .suffix=${this.schema.description?.suffix} - .validationMessage=${this.schema.required ? "Required" : undefined} + .validationMessage=${this.schema.required + ? this.localize?.("ui.common.error_required") + : undefined} @input=${this._valueChanged} > `; diff --git a/src/components/ha-form/ha-form-grid.ts b/src/components/ha-form/ha-form-grid.ts index a3fbcb079a..27c6025312 100644 --- a/src/components/ha-form/ha-form-grid.ts +++ b/src/components/ha-form/ha-form-grid.ts @@ -26,12 +26,14 @@ export class HaFormGrid extends LitElement implements HaFormElement { @property({ type: Boolean }) public disabled = false; - @property() public computeLabel?: ( + @property({ attribute: false }) public computeLabel?: ( schema: HaFormSchema, data?: HaFormDataContainer ) => string; - @property() public computeHelper?: (schema: HaFormSchema) => string; + @property({ attribute: false }) public computeHelper?: ( + schema: HaFormSchema + ) => string; public async focus() { await this.updateComplete; diff --git a/src/components/ha-form/ha-form-integer.ts b/src/components/ha-form/ha-form-integer.ts index 1b311769db..2ce103b6bd 100644 --- a/src/components/ha-form/ha-form-integer.ts +++ b/src/components/ha-form/ha-form-integer.ts @@ -12,9 +12,12 @@ import { HaCheckbox } from "../ha-checkbox"; import "../ha-slider"; import { HaTextField } from "../ha-textfield"; import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types"; +import { LocalizeFunc } from "../../common/translations/localize"; @customElement("ha-form-integer") export class HaFormInteger extends LitElement implements HaFormElement { + @property({ attribute: false }) public localize?: LocalizeFunc; + @property({ attribute: false }) public schema!: HaFormIntegerSchema; @property({ attribute: false }) public data?: HaFormIntegerData; @@ -85,7 +88,9 @@ export class HaFormInteger extends LitElement implements HaFormElement { .required=${this.schema.required} .autoValidate=${this.schema.required} .suffix=${this.schema.description?.suffix} - .validationMessage=${this.schema.required ? "Required" : undefined} + .validationMessage=${this.schema.required + ? this.localize?.("ui.common.error_required") + : undefined} @input=${this._valueChanged} > `; diff --git a/src/components/ha-form/ha-form-multi_select.ts b/src/components/ha-form/ha-form-multi_select.ts index 66c7d60ca6..3a76d87f77 100644 --- a/src/components/ha-form/ha-form-multi_select.ts +++ b/src/components/ha-form/ha-form-multi_select.ts @@ -35,9 +35,9 @@ const SHOW_ALL_ENTRIES_LIMIT = 6; @customElement("ha-form-multi_select") export class HaFormMultiSelect extends LitElement implements HaFormElement { - @property() public schema!: HaFormMultiSelectSchema; + @property({ attribute: false }) public schema!: HaFormMultiSelectSchema; - @property() public data!: HaFormMultiSelectData; + @property({ attribute: false }) public data!: HaFormMultiSelectData; @property() public label!: string; diff --git a/src/components/ha-form/ha-form-select.ts b/src/components/ha-form/ha-form-select.ts index 86a5db98a6..83a2c7525a 100644 --- a/src/components/ha-form/ha-form-select.ts +++ b/src/components/ha-form/ha-form-select.ts @@ -17,7 +17,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { @property({ attribute: false }) public schema!: HaFormSelectSchema; - @property() public data!: HaFormSelectData; + @property({ attribute: false }) public data!: HaFormSelectData; @property() public label?: string; diff --git a/src/components/ha-form/ha-form-string.ts b/src/components/ha-form/ha-form-string.ts index ca9b7b9858..dc9c4305e2 100644 --- a/src/components/ha-form/ha-form-string.ts +++ b/src/components/ha-form/ha-form-string.ts @@ -19,15 +19,17 @@ import type { HaFormStringData, HaFormStringSchema, } from "./types"; -import { HomeAssistant } from "../../types"; +import { LocalizeFunc, LocalizeKeys } from "../../common/translations/localize"; const MASKED_FIELDS = ["password", "secret", "token"]; @customElement("ha-form-string") export class HaFormString extends LitElement implements HaFormElement { - @property() public hass?: HomeAssistant; + @property({ attribute: false }) public localize?: LocalizeFunc; - @property() public schema!: HaFormStringSchema; + @property() public localizeBaseKey = "ui.components.selectors.text"; + + @property({ attribute: false }) public schema!: HaFormStringSchema; @property() public data!: HaFormStringData; @@ -68,7 +70,9 @@ export class HaFormString extends LitElement implements HaFormElement { ? // reserve some space for the icon. html`
` : this.schema.description?.suffix} - .validationMessage=${this.schema.required ? "Required" : undefined} + .validationMessage=${this.schema.required + ? this.localize?.("ui.common.error_required") + : undefined} @input=${this._valueChanged} @change=${this._valueChanged} > @@ -81,11 +85,11 @@ export class HaFormString extends LitElement implements HaFormElement { return html` diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 9fcc82aff6..0d460098b0 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -39,30 +39,47 @@ const getWarning = (obj, item) => (obj && item.name ? obj[item.name] : null); @customElement("ha-form") export class HaForm extends LitElement implements HaFormElement { - @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public data!: HaFormDataContainer; @property({ attribute: false }) public schema!: readonly HaFormSchema[]; - @property() public error?: Record; + @property({ attribute: false }) public error?: Record< + string, + string | string[] + >; - @property() public warning?: Record; + @property({ attribute: false }) public warning?: Record; @property({ type: Boolean }) public disabled = false; - @property() public computeError?: (schema: any, error) => string; + @property({ attribute: false }) public computeError?: ( + schema: any, + error + ) => string; - @property() public computeWarning?: (schema: any, warning) => string; + @property({ attribute: false }) public computeWarning?: ( + schema: any, + warning + ) => string; - @property() public computeLabel?: ( + @property({ attribute: false }) public computeLabel?: ( schema: any, data: HaFormDataContainer ) => string; - @property() public computeHelper?: (schema: any) => string | undefined; + @property({ attribute: false }) public computeHelper?: ( + schema: any + ) => string | undefined; - @property() public localizeValue?: (key: string) => string; + @property({ attribute: false }) public localizeValue?: ( + key: string + ) => string; + + protected getFormProperties(): Record { + return {}; + } public async focus() { await this.updateComplete; @@ -143,9 +160,11 @@ export class HaForm extends LitElement implements HaFormElement { helper: this._computeHelper(item), disabled: this.disabled || item.disabled || false, hass: this.hass, + localize: this.hass?.localize, computeLabel: this.computeLabel, computeHelper: this.computeHelper, context: this._generateContext(item), + ...this.getFormProperties(), })} `; })} @@ -212,7 +231,20 @@ export class HaForm extends LitElement implements HaFormElement { return this.computeHelper ? this.computeHelper(schema) : ""; } - private _computeError(error, schema: HaFormSchema | readonly HaFormSchema[]) { + private _computeError( + error: string | string[], + schema: HaFormSchema | readonly HaFormSchema[] + ): string | TemplateResult { + if (Array.isArray(error)) { + return html`
    + ${error.map( + (err) => + html`
  • + ${this.computeError ? this.computeError(err, schema) : err} +
  • ` + )} +
`; + } return this.computeError ? this.computeError(error, schema) : error; } diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index 53b573dc47..9ec23558ff 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -19,7 +19,7 @@ export interface LevelDefinition { } @customElement("ha-gauge") -export class Gauge extends LitElement { +export class HaGauge extends LitElement { @property({ type: Number }) public min = 0; @property({ type: Number }) public max = 100; @@ -31,11 +31,11 @@ export class Gauge extends LitElement { @property({ type: String }) public valueText?: string; - @property() public locale!: FrontendLocaleData; + @property({ attribute: false }) public locale!: FrontendLocaleData; - @property({ type: Boolean }) public needle?: boolean; + @property({ type: Boolean }) public needle = false; - @property() public levels?: LevelDefinition[]; + @property({ type: Array }) public levels?: LevelDefinition[]; @property() public label = ""; @@ -216,3 +216,9 @@ export class Gauge extends LitElement { `; } } + +declare global { + interface HTMLElementTagNameMap { + "ha-gauge": HaGauge; + } +} diff --git a/src/components/ha-header-bar.ts b/src/components/ha-header-bar.ts index 3bb3ed1bd6..aef90d6352 100644 --- a/src/components/ha-header-bar.ts +++ b/src/components/ha-header-bar.ts @@ -45,6 +45,10 @@ export class HaHeaderBar extends LitElement { .mdc-top-app-bar__section.mdc-top-app-bar__section--align-end { flex: none; } + .mdc-top-app-bar__title { + padding-inline-start: 20px; + padding-inline-end: initial; + } `, ]; } diff --git a/src/components/ha-hs-color-picker.ts b/src/components/ha-hs-color-picker.ts index f492be2d7c..b332e0fd63 100644 --- a/src/components/ha-hs-color-picker.ts +++ b/src/components/ha-hs-color-picker.ts @@ -115,7 +115,7 @@ class HaHsColorPicker extends LitElement { @property({ type: Number, attribute: false }) public renderSize?: number; - @property({ type: Number }) + @property({ type: Array }) public value?: [number, number]; @property({ type: Number }) diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 786618cc40..871adf5380 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -1,14 +1,15 @@ +import "@material/mwc-list/mwc-list-item"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxDataProviderCallback, ComboBoxDataProviderParams, } from "@vaadin/combo-box/vaadin-combo-box-light"; -import { css, html, LitElement, TemplateResult } from "lit"; +import { LitElement, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { customIcons } from "../data/custom_icons"; -import { ValueChangedEvent, HomeAssistant } from "../types"; +import { HomeAssistant, ValueChangedEvent } from "../types"; import "./ha-combo-box"; import "./ha-icon"; @@ -73,7 +74,7 @@ const rowRenderer: ComboBoxLitRenderer = (item) => @customElement("ha-icon-picker") export class HaIconPicker extends LitElement { - @property() public hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @property() public value?: string; @@ -83,8 +84,6 @@ export class HaIconPicker extends LitElement { @property() public placeholder?: string; - @property() public fallbackPath?: string; - @property({ attribute: "error-message" }) public errorMessage?: string; @property({ type: Boolean }) public disabled = false; @@ -119,12 +118,7 @@ export class HaIconPicker extends LitElement { ` - : this.fallbackPath - ? html`` - : ""} + : html``} `; } @@ -212,6 +206,8 @@ export class HaIconPicker extends LitElement { } *[slot="prefix"] { margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } `; } diff --git a/src/components/ha-input-helper-text.ts b/src/components/ha-input-helper-text.ts index a6c34ccdc2..f40bb53320 100644 --- a/src/components/ha-input-helper-text.ts +++ b/src/components/ha-input-helper-text.ts @@ -14,6 +14,8 @@ class InputHelperText extends LitElement { font-size: 0.75rem; padding-left: 16px; padding-right: 16px; + padding-inline-start: 16px; + padding-inline-end: 16px; } `; } diff --git a/src/components/ha-label.ts b/src/components/ha-label.ts index ffb4d06126..10293c88a9 100644 --- a/src/components/ha-label.ts +++ b/src/components/ha-label.ts @@ -5,11 +5,11 @@ import { customElement } from "lit/decorators"; class HaLabel extends LitElement { protected render(): TemplateResult { return html` - - - -
- `; + + + + + `; } static get styles(): CSSResultGroup { @@ -42,6 +42,8 @@ class HaLabel extends LitElement { ::slotted([slot="icon"]) { margin-right: 8px; margin-left: -8px; + margin-inline-start: -8px; + margin-inline-end: 8px; display: flex; color: var(--ha-label-icon-color); } diff --git a/src/components/ha-labeled-slider.ts b/src/components/ha-labeled-slider.ts index 5faf7a8b96..99805f3f8c 100644 --- a/src/components/ha-labeled-slider.ts +++ b/src/components/ha-labeled-slider.ts @@ -1,12 +1,13 @@ import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; -import "./ha-icon"; -import "./ha-slider"; import { fireEvent } from "../common/dom/fire_event"; +import "./ha-icon"; +import "./ha-input-helper-text"; +import "./ha-slider"; @customElement("ha-labeled-slider") class HaLabeledSlider extends LitElement { - @property() public labeled? = false; + @property({ type: Boolean }) public labeled = false; @property() public caption?: string; @@ -14,19 +15,19 @@ class HaLabeledSlider extends LitElement { @property({ type: Boolean }) public required = true; - @property() public min: number = 0; + @property({ type: Number }) public min = 0; - @property() public max: number = 100; + @property({ type: Number }) public max = 100; - @property() public step: number = 1; + @property({ type: Number }) public step = 1; @property() public helper?: string; - @property() public extra = false; + @property({ type: Boolean }) public extra = false; @property() public icon?: string; - @property() public value?: number; + @property({ type: Number }) public value?: number; protected render() { return html` @@ -38,7 +39,7 @@ class HaLabeledSlider extends LitElement { .min=${this.min} .max=${this.max} .step=${this.step} - labeled=${this.labeled} + .labeled=${this.labeled} .disabled=${this.disabled} .value=${this.value} @change=${this._inputChanged} diff --git a/src/components/ha-language-picker.ts b/src/components/ha-language-picker.ts index dcf0f7312d..c8a1e9d15b 100644 --- a/src/components/ha-language-picker.ts +++ b/src/components/ha-language-picker.ts @@ -19,7 +19,7 @@ export class HaLanguagePicker extends LitElement { @property() public label?: string; - @property() public languages?: string[]; + @property({ type: Array }) public languages?: string[]; @property({ attribute: false }) public hass?: HomeAssistant; diff --git a/src/components/ha-lawn_mower-action-button.ts b/src/components/ha-lawn_mower-action-button.ts index cdf3d35a5c..a142067348 100644 --- a/src/components/ha-lawn_mower-action-button.ts +++ b/src/components/ha-lawn_mower-action-button.ts @@ -75,6 +75,8 @@ class HaLawnMowerActionButton extends LitElement { top: 3px; height: 37px; margin-right: -0.57em; + margin-inline-end: -0.57em; + margin-inline-start: initial; } mwc-button[disabled] { background-color: transparent; diff --git a/src/components/ha-list-item-new.ts b/src/components/ha-list-item-new.ts new file mode 100644 index 0000000000..980c28479a --- /dev/null +++ b/src/components/ha-list-item-new.ts @@ -0,0 +1,29 @@ +import { customElement } from "lit/decorators"; +import "element-internals-polyfill"; +import { MdListItem } from "@material/web/list/list-item"; +import { CSSResult, css } from "lit"; + +@customElement("ha-list-item-new") +export class HaListItemNew extends MdListItem { + static get styles(): CSSResult[] { + return [ + ...MdListItem.styles, + css` + :host { + --ha-icon-display: block; + --md-sys-color-primary: var(--primary-text-color); + --md-sys-color-secondary: var(--secondary-text-color); + --md-sys-color-surface: var(--card-background-color); + --md-sys-color-on-surface: var(--primary-text-color); + --md-sys-color-on-surface-variant: var(--secondary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-list-item-new": HaListItemNew; + } +} diff --git a/src/components/ha-list-item.ts b/src/components/ha-list-item.ts index dcd13f0f70..8afbe56bcc 100644 --- a/src/components/ha-list-item.ts +++ b/src/components/ha-list-item.ts @@ -21,10 +21,18 @@ export class HaListItem extends ListItemBase { --mdc-list-side-padding-left, var(--mdc-list-side-padding, 20px) ); + padding-inline-start: var( + --mdc-list-side-padding-left, + var(--mdc-list-side-padding, 20px) + ); padding-right: var( --mdc-list-side-padding-right, var(--mdc-list-side-padding, 20px) ); + padding-inline-end: var( + --mdc-list-side-padding-right, + var(--mdc-list-side-padding, 20px) + ); } :host([graphic="avatar"]:not([twoLine])), :host([graphic="icon"]:not([twoLine])) { diff --git a/src/components/ha-list-new.ts b/src/components/ha-list-new.ts new file mode 100644 index 0000000000..b88ef32e1a --- /dev/null +++ b/src/components/ha-list-new.ts @@ -0,0 +1,24 @@ +import { customElement } from "lit/decorators"; +import "element-internals-polyfill"; +import { MdList } from "@material/web/list/list"; +import { CSSResult, css } from "lit"; + +@customElement("ha-list-new") +export class HaListNew extends MdList { + static get styles(): CSSResult[] { + return [ + ...MdList.styles, + css` + :host { + --md-sys-color-surface: var(--card-background-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-list-new": HaListNew; + } +} diff --git a/src/components/ha-markdown-element.ts b/src/components/ha-markdown-element.ts index 0e806b7c7d..5ca7fa66b2 100644 --- a/src/components/ha-markdown-element.ts +++ b/src/components/ha-markdown-element.ts @@ -3,7 +3,17 @@ import { customElement, property } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { renderMarkdown } from "../resources/render-markdown"; -const _blockQuoteToAlert = { Note: "info", Warning: "warning" }; +const _gitHubMarkdownAlerts = { + reType: + /(?(\[!(?caution|important|note|tip|warning)\])(?:\s|\\n)?)/i, + typeToHaAlert: { + caution: "error", + important: "info", + note: "info", + tip: "success", + warning: "warning", + }, +}; @customElement("ha-markdown-element") class HaMarkdownElement extends ReactiveElement { @@ -68,32 +78,33 @@ class HaMarkdownElement extends ReactiveElement { } node.addEventListener("load", this._resize); } else if (node instanceof HTMLQuoteElement) { - // Map GitHub blockquote elements to our ha-alert element - const firstElementChild = node.firstElementChild; - const quoteTitleElement = firstElementChild?.firstElementChild; - const quoteType = - quoteTitleElement?.textContent && - _blockQuoteToAlert[quoteTitleElement.textContent]; + /** + * Map GitHub blockquote elements to our ha-alert element + * https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts + */ + const gitHubAlertMatch = + node.firstElementChild?.firstChild?.textContent && + _gitHubMarkdownAlerts.reType.exec( + node.firstElementChild.firstChild.textContent + ); - // GitHub is strict on how these are defined, we need to make sure we know what we have before starting to replace it - if (quoteTitleElement?.nodeName === "STRONG" && quoteType) { - const alertNote = document.createElement("ha-alert"); - alertNote.alertType = quoteType; - alertNote.title = - (firstElementChild!.childNodes[1].nodeName === "#text" && - firstElementChild!.childNodes[1].textContent?.trimStart()) || - ""; + if (gitHubAlertMatch) { + const { type: alertType } = gitHubAlertMatch.groups!; + const haAlertNode = document.createElement("ha-alert"); + haAlertNode.alertType = + _gitHubMarkdownAlerts.typeToHaAlert[alertType.toLowerCase()]; - const childNodes = Array.from(firstElementChild!.childNodes); - for (const child of childNodes.slice( - childNodes.findIndex( - // There is always a line break between the title and the content, we want to skip that - (childNode) => childNode instanceof HTMLBRElement - ) + 1 - )) { - alertNote.appendChild(child); - } - node.firstElementChild!.replaceWith(alertNote); + haAlertNode.append( + ...Array.from(node.childNodes) + .map((child) => Array.from(child.childNodes)) + .reduce((acc, val) => acc.concat(val), []) + .filter( + (childNode) => + childNode.textContent && + childNode.textContent !== gitHubAlertMatch.input + ) + ); + walker.parentNode()!.replaceChild(haAlertNode, node); } } else if ( node instanceof HTMLElement && diff --git a/src/components/ha-markdown.ts b/src/components/ha-markdown.ts index f81ddd2860..d8f0ba2270 100644 --- a/src/components/ha-markdown.ts +++ b/src/components/ha-markdown.ts @@ -42,6 +42,10 @@ export class HaMarkdown extends LitElement { ha-markdown-element > *:last-child { margin-bottom: 0; } + ha-alert { + display: block; + margin: 4px 0; + } a { color: var(--primary-color); } diff --git a/src/components/ha-menu-button.ts b/src/components/ha-menu-button.ts index 0e7fac5aa3..63481ad1fd 100644 --- a/src/components/ha-menu-button.ts +++ b/src/components/ha-menu-button.ts @@ -11,7 +11,7 @@ import "./ha-icon-button"; class HaMenuButton extends LitElement { @property({ type: Boolean }) public hassio = false; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ attribute: false }) public hass!: HomeAssistant; diff --git a/src/components/ha-metric.ts b/src/components/ha-metric.ts index 77bf266941..4a33812929 100644 --- a/src/components/ha-metric.ts +++ b/src/components/ha-metric.ts @@ -66,6 +66,8 @@ class HaMetric extends LitElement { .value { width: 48px; padding-right: 4px; + padding-inline-end: 4px; + padding-inline-start: initial; flex-shrink: 0; } `; diff --git a/src/components/ha-multi-textfield.ts b/src/components/ha-multi-textfield.ts index c16bfb02ad..1988b46cc9 100644 --- a/src/components/ha-multi-textfield.ts +++ b/src/components/ha-multi-textfield.ts @@ -5,6 +5,7 @@ import { fireEvent } from "../common/dom/fire_event"; import { haStyle } from "../resources/styles"; import type { HomeAssistant } from "../types"; import "./ha-button"; +import "./ha-icon-button"; import "./ha-textfield"; import type { HaTextField } from "./ha-textfield"; @@ -31,7 +32,7 @@ class HaMultiTextField extends LitElement { @property() public removeLabel?: string; @property({ attribute: "item-index", type: Boolean }) - public itemIndex?: boolean; + public itemIndex = false; protected render() { return html` @@ -131,6 +132,8 @@ class HaMultiTextField extends LitElement { } ha-button { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } `, ]; diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index cb8c99d8d2..5b7b8ebed7 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -13,7 +13,7 @@ import "./ha-svg-icon"; class HaNavigationList extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ attribute: false }) public pages!: PageNavigation[]; diff --git a/src/components/ha-navigation-picker.ts b/src/components/ha-navigation-picker.ts index 7758ec9882..105d052d06 100644 --- a/src/components/ha-navigation-picker.ts +++ b/src/components/ha-navigation-picker.ts @@ -1,3 +1,4 @@ +import "@material/mwc-list/mwc-list-item"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; @@ -50,7 +51,7 @@ const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({ @customElement("ha-navigation-picker") export class HaNavigationPicker extends LitElement { - @property() public hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @property() public label?: string; @@ -205,6 +206,8 @@ export class HaNavigationPicker extends LitElement { } *[slot="prefix"] { margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } `; } diff --git a/src/components/ha-qr-code.ts b/src/components/ha-qr-code.ts index f6a686d068..47190c180c 100644 --- a/src/components/ha-qr-code.ts +++ b/src/components/ha-qr-code.ts @@ -1,6 +1,7 @@ import { LitElement, PropertyValues, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import QRCode from "qrcode"; +import "./ha-alert"; @customElement("ha-qr-code") export class HaQrCode extends LitElement { @@ -66,7 +67,8 @@ export class HaQrCode extends LitElement { const computedStyles = getComputedStyle(this); QRCode.toCanvas(canvas, this.data, { - errorCorrectionLevel: this.errorCorrectionLevel, + errorCorrectionLevel: + this.errorCorrectionLevel || (this.centerImage ? "Q" : "M"), width: this.width, scale: this.scale, margin: this.margin, @@ -112,3 +114,9 @@ export class HaQrCode extends LitElement { } `; } + +declare global { + interface HTMLElementTagNameMap { + "ha-qr-code": HaQrCode; + } +} diff --git a/src/components/ha-qr-scanner.ts b/src/components/ha-qr-scanner.ts index fe3bc8f0f6..9d437164cc 100644 --- a/src/components/ha-qr-scanner.ts +++ b/src/components/ha-qr-scanner.ts @@ -14,7 +14,7 @@ import type { HaTextField } from "./ha-textfield"; @customElement("ha-qr-scanner") class HaQrScanner extends LitElement { - @property() localize!: LocalizeFunc; + @property({ attribute: false }) public localize!: LocalizeFunc; @state() private _cameras?: QrScanner.Camera[]; @@ -197,6 +197,8 @@ class HaQrScanner extends LitElement { ha-textfield { flex: 1; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } `; } diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts index b843cd2ba9..be59a28b88 100644 --- a/src/components/ha-related-items.ts +++ b/src/components/ha-related-items.ts @@ -246,7 +246,8 @@ export class HaRelatedItems extends LitElement { graphic="icon" > ${entity.attributes.friendly_name || entity.entity_id} @@ -270,7 +271,8 @@ export class HaRelatedItems extends LitElement { graphic="icon" > ${group.attributes.friendly_name || group.entity_id} @@ -294,7 +296,8 @@ export class HaRelatedItems extends LitElement { graphic="icon" > ${scene.attributes.friendly_name || scene.entity_id} @@ -349,7 +352,8 @@ export class HaRelatedItems extends LitElement { graphic="icon" > ${automation.attributes.friendly_name || @@ -403,7 +407,8 @@ export class HaRelatedItems extends LitElement { graphic="icon" > ${script.attributes.friendly_name || script.entity_id} diff --git a/src/components/ha-select.ts b/src/components/ha-select.ts index e1454795c2..6a78155694 100644 --- a/src/components/ha-select.ts +++ b/src/components/ha-select.ts @@ -10,9 +10,9 @@ import "./ha-icon-button"; @customElement("ha-select") export class HaSelect extends SelectBase { // @ts-ignore - @property({ type: Boolean }) public icon?: boolean; + @property({ type: Boolean }) public icon = false; - @property({ type: Boolean, reflect: true }) public clearable?: boolean; + @property({ type: Boolean, reflect: true }) public clearable = false; protected override render() { return html` diff --git a/src/components/ha-selector/ha-selector-action.ts b/src/components/ha-selector/ha-selector-action.ts index b8a083a536..2e203a62da 100644 --- a/src/components/ha-selector/ha-selector-action.ts +++ b/src/components/ha-selector/ha-selector-action.ts @@ -7,11 +7,11 @@ import { HomeAssistant } from "../../types"; @customElement("ha-selector-action") export class HaActionSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: ActionSelector; + @property({ attribute: false }) public selector!: ActionSelector; - @property() public value?: Action; + @property({ attribute: false }) public value?: Action; @property() public label?: string; @@ -24,8 +24,7 @@ export class HaActionSelector extends LitElement { .disabled=${this.disabled} .actions=${this.value || []} .hass=${this.hass} - .nested=${this.selector.action?.nested} - .reOrderMode=${this.selector.action?.reorder_mode} + .path=${this.selector.action?.path} > `; } diff --git a/src/components/ha-selector/ha-selector-addon.ts b/src/components/ha-selector/ha-selector-addon.ts index 91aee42777..ef61da0575 100644 --- a/src/components/ha-selector/ha-selector-addon.ts +++ b/src/components/ha-selector/ha-selector-addon.ts @@ -6,9 +6,9 @@ import "../ha-addon-picker"; @customElement("ha-selector-addon") export class HaAddonSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: AddonSelector; + @property({ attribute: false }) public selector!: AddonSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-area-filter.ts b/src/components/ha-selector/ha-selector-area-filter.ts index 7efd243a8c..460972b639 100644 --- a/src/components/ha-selector/ha-selector-area-filter.ts +++ b/src/components/ha-selector/ha-selector-area-filter.ts @@ -6,9 +6,9 @@ import "../ha-area-filter"; @customElement("ha-selector-area_filter") export class HaAreaFilterSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: AreaFilterSelector; + @property({ attribute: false }) public selector!: AreaFilterSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-area.ts b/src/components/ha-selector/ha-selector-area.ts index def6fa11a5..e746314231 100644 --- a/src/components/ha-selector/ha-selector-area.ts +++ b/src/components/ha-selector/ha-selector-area.ts @@ -21,9 +21,9 @@ import "../ha-areas-picker"; @customElement("ha-selector-area") export class HaAreaSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: AreaSelector; + @property({ attribute: false }) public selector!: AreaSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-assist-pipeline.ts b/src/components/ha-selector/ha-selector-assist-pipeline.ts index 9a00519f07..c84d037769 100644 --- a/src/components/ha-selector/ha-selector-assist-pipeline.ts +++ b/src/components/ha-selector/ha-selector-assist-pipeline.ts @@ -6,9 +6,9 @@ import "../ha-assist-pipeline-picker"; @customElement("ha-selector-assist_pipeline") export class HaAssistPipelineSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: AssistPipelineSelector; + @property({ attribute: false }) public selector!: AssistPipelineSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-backup-location.ts b/src/components/ha-selector/ha-selector-backup-location.ts index f20c447a7b..1499c642fc 100644 --- a/src/components/ha-selector/ha-selector-backup-location.ts +++ b/src/components/ha-selector/ha-selector-backup-location.ts @@ -6,9 +6,9 @@ import "../ha-mount-picker"; @customElement("ha-selector-backup_location") export class HaBackupLocationSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: BackupLocationSelector; + @property({ attribute: false }) public selector!: BackupLocationSelector; @property() public value?: any; @@ -41,6 +41,6 @@ export class HaBackupLocationSelector extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-selector-backup-location": HaBackupLocationSelector; + "ha-selector-backup_location": HaBackupLocationSelector; } } diff --git a/src/components/ha-selector/ha-selector-boolean.ts b/src/components/ha-selector/ha-selector-boolean.ts index d99f980d8b..a6e0584f07 100644 --- a/src/components/ha-selector/ha-selector-boolean.ts +++ b/src/components/ha-selector/ha-selector-boolean.ts @@ -8,9 +8,9 @@ import "../ha-input-helper-text"; @customElement("ha-selector-boolean") export class HaBooleanSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public value?: number; + @property({ type: Boolean }) public value = false; @property() public label?: string; diff --git a/src/components/ha-selector/ha-selector-color-temp.ts b/src/components/ha-selector/ha-selector-color-temp.ts index f9017ece31..ef59e77996 100644 --- a/src/components/ha-selector/ha-selector-color-temp.ts +++ b/src/components/ha-selector/ha-selector-color-temp.ts @@ -15,9 +15,9 @@ import { @customElement("ha-selector-color_temp") export class HaColorTempSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: ColorTempSelector; + @property({ attribute: false }) public selector!: ColorTempSelector; @property() public value?: string; diff --git a/src/components/ha-selector/ha-selector-condition.ts b/src/components/ha-selector/ha-selector-condition.ts index 29fbaa49e6..756d43b1e4 100644 --- a/src/components/ha-selector/ha-selector-condition.ts +++ b/src/components/ha-selector/ha-selector-condition.ts @@ -7,11 +7,11 @@ import { HomeAssistant } from "../../types"; @customElement("ha-selector-condition") export class HaConditionSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: ConditionSelector; + @property({ attribute: false }) public selector!: ConditionSelector; - @property() public value?: Condition; + @property({ attribute: false }) public value?: Condition; @property() public label?: string; @@ -24,8 +24,7 @@ export class HaConditionSelector extends LitElement { .disabled=${this.disabled} .conditions=${this.value || []} .hass=${this.hass} - .nested=${this.selector.condition?.nested} - .reOrderMode=${this.selector.condition?.reorder_mode} + .path=${this.selector.condition?.path} > `; } diff --git a/src/components/ha-selector/ha-selector-constant.ts b/src/components/ha-selector/ha-selector-constant.ts index 27fcc8f7f6..9ea7f1ec2e 100644 --- a/src/components/ha-selector/ha-selector-constant.ts +++ b/src/components/ha-selector/ha-selector-constant.ts @@ -4,11 +4,12 @@ import { ConstantSelector } from "../../data/selector"; @customElement("ha-selector-constant") export class HaSelectorConstant extends LitElement { - @property() public selector!: ConstantSelector; + @property({ attribute: false }) public selector!: ConstantSelector; @property({ type: Boolean }) public disabled = false; - @property() public localizeValue?: (key: string) => string; + @property({ attribute: false }) + public localizeValue?: (key: string) => string; protected render() { if (this.disabled) { diff --git a/src/components/ha-selector/ha-selector-conversation-agent.ts b/src/components/ha-selector/ha-selector-conversation-agent.ts index 14b85bfad0..8ff2d76f30 100644 --- a/src/components/ha-selector/ha-selector-conversation-agent.ts +++ b/src/components/ha-selector/ha-selector-conversation-agent.ts @@ -6,9 +6,9 @@ import "../ha-conversation-agent-picker"; @customElement("ha-selector-conversation_agent") export class HaConversationAgentSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: ConversationAgentSelector; + @property({ attribute: false }) public selector!: ConversationAgentSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-country.ts b/src/components/ha-selector/ha-selector-country.ts index 56e54a5875..85ba84d067 100644 --- a/src/components/ha-selector/ha-selector-country.ts +++ b/src/components/ha-selector/ha-selector-country.ts @@ -6,9 +6,9 @@ import "../ha-country-picker"; @customElement("ha-selector-country") export class HaCountrySelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: CountrySelector; + @property({ attribute: false }) public selector!: CountrySelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-date.ts b/src/components/ha-selector/ha-selector-date.ts index 8484e29a76..f1fa8b6bf1 100644 --- a/src/components/ha-selector/ha-selector-date.ts +++ b/src/components/ha-selector/ha-selector-date.ts @@ -6,9 +6,9 @@ import "../ha-date-input"; @customElement("ha-selector-date") export class HaDateSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: DateSelector; + @property({ attribute: false }) public selector!: DateSelector; @property() public value?: string; diff --git a/src/components/ha-selector/ha-selector-datetime.ts b/src/components/ha-selector/ha-selector-datetime.ts index 0ad02213f9..1c5e2bf8db 100644 --- a/src/components/ha-selector/ha-selector-datetime.ts +++ b/src/components/ha-selector/ha-selector-datetime.ts @@ -78,6 +78,8 @@ export class HaDateTimeSelector extends LitElement { ha-date-input { min-width: 150px; margin-right: 4px; + margin-inline-end: 4px; + margin-inline-start: initial; } `; } diff --git a/src/components/ha-selector/ha-selector-device.ts b/src/components/ha-selector/ha-selector-device.ts index ab79b2c1d1..b3ffebd4b6 100644 --- a/src/components/ha-selector/ha-selector-device.ts +++ b/src/components/ha-selector/ha-selector-device.ts @@ -21,9 +21,9 @@ import "../device/ha-devices-picker"; @customElement("ha-selector-device") export class HaDeviceSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: DeviceSelector; + @property({ attribute: false }) public selector!: DeviceSelector; @state() private _entitySources?: EntitySources; diff --git a/src/components/ha-selector/ha-selector-entity.ts b/src/components/ha-selector/ha-selector-entity.ts index 5e44ab2175..ff7b4da106 100644 --- a/src/components/ha-selector/ha-selector-entity.ts +++ b/src/components/ha-selector/ha-selector-entity.ts @@ -15,9 +15,9 @@ import "../entity/ha-entity-picker"; @customElement("ha-selector-entity") export class HaEntitySelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: EntitySelector; + @property({ attribute: false }) public selector!: EntitySelector; @state() private _entitySources?: EntitySources; diff --git a/src/components/ha-selector/ha-selector-file.ts b/src/components/ha-selector/ha-selector-file.ts index 2024d04b54..3ae286581a 100644 --- a/src/components/ha-selector/ha-selector-file.ts +++ b/src/components/ha-selector/ha-selector-file.ts @@ -10,9 +10,9 @@ import "../ha-file-upload"; @customElement("ha-selector-file") export class HaFileSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: FileSelector; + @property({ attribute: false }) public selector!: FileSelector; @property() public value?: string; diff --git a/src/components/ha-selector/ha-selector-icon.ts b/src/components/ha-selector/ha-selector-icon.ts index 1a5d10cf72..3e517d61e4 100644 --- a/src/components/ha-selector/ha-selector-icon.ts +++ b/src/components/ha-selector/ha-selector-icon.ts @@ -1,17 +1,18 @@ -import { html, LitElement } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; +import { until } from "lit/directives/until"; import { fireEvent } from "../../common/dom/fire_event"; -import { computeDomain } from "../../common/entity/compute_domain"; -import { domainIcon } from "../../common/entity/domain_icon"; +import { entityIcon } from "../../data/icons"; import { IconSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "../ha-icon-picker"; +import "../ha-state-icon"; @customElement("ha-selector-icon") export class HaIconSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: IconSelector; + @property({ attribute: false }) public selector!: IconSelector; @property() public value?: string; @@ -23,7 +24,7 @@ export class HaIconSelector extends LitElement { @property({ type: Boolean }) public required = true; - @property() public context?: { + @property({ attribute: false }) public context?: { icon_entity?: string; }; @@ -33,11 +34,9 @@ export class HaIconSelector extends LitElement { const stateObj = iconEntity ? this.hass.states[iconEntity] : undefined; const placeholder = - this.selector.icon?.placeholder || stateObj?.attributes.icon; - const fallbackPath = - !placeholder && stateObj - ? domainIcon(computeDomain(iconEntity!), stateObj) - : undefined; + this.selector.icon?.placeholder || + stateObj?.attributes.icon || + (stateObj && until(entityIcon(this.hass, stateObj))); return html` + > + ${!placeholder && stateObj + ? html` + + ` + : nothing} + `; } diff --git a/src/components/ha-selector/ha-selector-language.ts b/src/components/ha-selector/ha-selector-language.ts index 79b6dc69f2..a4f9727fb8 100644 --- a/src/components/ha-selector/ha-selector-language.ts +++ b/src/components/ha-selector/ha-selector-language.ts @@ -6,9 +6,9 @@ import "../ha-language-picker"; @customElement("ha-selector-language") export class HaLanguageSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: LanguageSelector; + @property({ attribute: false }) public selector!: LanguageSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-location.ts b/src/components/ha-selector/ha-selector-location.ts index bf08b181e4..96d9d66298 100644 --- a/src/components/ha-selector/ha-selector-location.ts +++ b/src/components/ha-selector/ha-selector-location.ts @@ -16,7 +16,7 @@ export class HaLocationSelector extends LitElement { @property({ attribute: false }) public selector!: LocationSelector; - @property() public value?: LocationSelectorValue; + @property({ type: Object }) public value?: LocationSelectorValue; @property() public label?: string; @@ -51,8 +51,14 @@ export class HaLocationSelector extends LitElement { return [ { id: "location", - latitude: value?.latitude || this.hass.config.latitude, - longitude: value?.longitude || this.hass.config.longitude, + latitude: + !value || isNaN(value.latitude) + ? this.hass.config.latitude + : value.latitude, + longitude: + !value || isNaN(value.longitude) + ? this.hass.config.longitude + : value.longitude, radius: selector.location?.radius ? value?.radius || 1000 : undefined, radius_color: zoneRadiusColor, icon: diff --git a/src/components/ha-selector/ha-selector-media.ts b/src/components/ha-selector/ha-selector-media.ts index e9149a6b1c..86fa91335d 100644 --- a/src/components/ha-selector/ha-selector-media.ts +++ b/src/components/ha-selector/ha-selector-media.ts @@ -254,6 +254,8 @@ export class HaMediaSelector extends LitElement { margin-bottom: 16px; padding-left: 16px; padding-right: 4px; + padding-inline-start: 16px; + padding-inline-end: 4px; white-space: nowrap; } .image { diff --git a/src/components/ha-selector/ha-selector-navigation.ts b/src/components/ha-selector/ha-selector-navigation.ts index e275c47d3a..a3f61fc68d 100644 --- a/src/components/ha-selector/ha-selector-navigation.ts +++ b/src/components/ha-selector/ha-selector-navigation.ts @@ -7,9 +7,9 @@ import "../ha-navigation-picker"; @customElement("ha-selector-navigation") export class HaNavigationSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: NavigationSelector; + @property({ attribute: false }) public selector!: NavigationSelector; @property() public value?: string; diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 1d4d2b98d4..ba60452493 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -10,13 +10,13 @@ import "../ha-textfield"; @customElement("ha-selector-number") export class HaNumberSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: NumberSelector; + @property({ attribute: false }) public selector!: NumberSelector; - @property() public value?: number; + @property({ type: Number }) public value?: number; - @property() public placeholder?: number; + @property({ type: Number }) public placeholder?: number; @property() public label?: string; diff --git a/src/components/ha-selector/ha-selector-object.ts b/src/components/ha-selector/ha-selector-object.ts index 42bc771de7..55737e3ca2 100644 --- a/src/components/ha-selector/ha-selector-object.ts +++ b/src/components/ha-selector/ha-selector-object.ts @@ -8,7 +8,7 @@ import type { HaYamlEditor } from "../ha-yaml-editor"; @customElement("ha-selector-object") export class HaObjectSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-qr-code.ts b/src/components/ha-selector/ha-selector-qr-code.ts new file mode 100644 index 0000000000..91361be7c0 --- /dev/null +++ b/src/components/ha-selector/ha-selector-qr-code.ts @@ -0,0 +1,30 @@ +import { LitElement, css, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { QRCodeSelector } from "../../data/selector"; +import "../ha-qr-code"; + +@customElement("ha-selector-qr_code") +export class HaSelectorQRCode extends LitElement { + @property({ attribute: false }) public selector!: QRCodeSelector; + + protected render() { + return html``; + } + + static styles = css` + ha-qr-code { + text-align: center; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-qr_code": HaSelectorQRCode; + } +} diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index a20559f91b..069dd157d4 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -1,16 +1,13 @@ import "@material/mwc-list/mwc-list-item"; import { mdiDrag } from "@mdi/js"; -import { LitElement, PropertyValues, css, html, nothing } from "lit"; +import { LitElement, css, html, nothing } from "lit"; import { customElement, property, query } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import { SortableEvent } from "sortablejs"; import { ensureArray } from "../../common/array/ensure-array"; import { fireEvent } from "../../common/dom/fire_event"; import { stopPropagation } from "../../common/dom/stop_propagation"; import { caseInsensitiveStringCompare } from "../../common/string/compare"; import type { SelectOption, SelectSelector } from "../../data/selector"; -import { sortableStyles } from "../../resources/ha-sortable-style"; -import { SortableInstance } from "../../resources/sortable"; import type { HomeAssistant } from "../../types"; import "../chips/ha-chip-set"; import "../chips/ha-input-chip"; @@ -21,6 +18,7 @@ import "../ha-formfield"; import "../ha-input-helper-text"; import "../ha-radio"; import "../ha-select"; +import "../ha-sortable"; @customElement("ha-selector-select") export class HaSelectSelector extends LitElement { @@ -34,7 +32,8 @@ export class HaSelectSelector extends LitElement { @property() public helper?: string; - @property() public localizeValue?: (key: string) => string; + @property({ attribute: false }) + public localizeValue?: (key: string) => string; @property({ type: Boolean }) public disabled = false; @@ -42,50 +41,10 @@ export class HaSelectSelector extends LitElement { @query("ha-combo-box", true) private comboBox!: HaComboBox; - private _sortable?: SortableInstance; - - protected updated(changedProps: PropertyValues): void { - if (changedProps.has("value") || changedProps.has("selector")) { - const sortableNeeded = - this.selector.select?.multiple && - this.selector.select.reorder && - this.value?.length; - if (!this._sortable && sortableNeeded) { - this._createSortable(); - } else if (this._sortable && !sortableNeeded) { - this._destroySortable(); - } - } - } - - private async _createSortable() { - const Sortable = (await import("../../resources/sortable")).default; - this._sortable = new Sortable( - this.shadowRoot!.querySelector("ha-chip-set")!, - { - animation: 150, - fallbackClass: "sortable-fallback", - draggable: "ha-input-chip", - onChoose: (evt: SortableEvent) => { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._dragged(evt); - }, - } - ); - } - - private _dragged(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) return; - this._move(ev.oldIndex!, ev.newIndex!); + private _itemMoved(ev: CustomEvent): void { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; + this._move(oldIndex!, newIndex); } private _move(index: number, newIndex: number) { @@ -99,11 +58,6 @@ export class HaSelectSelector extends LitElement { }); } - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private _filter = ""; protected render() { @@ -195,37 +149,43 @@ export class HaSelectSelector extends LitElement { return html` ${value?.length ? html` - - ${repeat( - value, - (item) => item, - (item, idx) => { - const label = - options.find((option) => option.value === item)?.label || - item; - return html` - - ${this.selector.select?.reorder - ? html` - - ` - : nothing} - ${options.find((option) => option.value === item) - ?.label || item} - - `; - } - )} - + + + ${repeat( + value, + (item) => item, + (item, idx) => { + const label = + options.find((option) => option.value === item) + ?.label || item; + return html` + + ${this.selector.select?.reorder + ? html` + + ` + : nothing} + ${options.find((option) => option.value === item) + ?.label || item} + + `; + } + )} + + ` : nothing} @@ -419,25 +379,35 @@ export class HaSelectSelector extends LitElement { this.comboBox.filteredItems = filteredItems; } - static styles = [ - sortableStyles, - css` - :host { - position: relative; - } - ha-select, - mwc-formfield, - ha-formfield { - display: block; - } - mwc-list-item[disabled] { - --mdc-theme-text-primary-on-background: var(--disabled-text-color); - } - ha-chip-set { - padding: 8px 0; - } - `, - ]; + static styles = css` + :host { + position: relative; + } + ha-select, + mwc-formfield, + ha-formfield { + display: block; + } + mwc-list-item[disabled] { + --mdc-theme-text-primary-on-background: var(--disabled-text-color); + } + ha-chip-set { + padding: 8px 0; + } + + .sortable-fallback { + display: none; + opacity: 0; + } + + .sortable-ghost { + opacity: 0.4; + } + + .sortable-drag { + cursor: grabbing; + } + `; } declare global { diff --git a/src/components/ha-selector/ha-selector-selector.ts b/src/components/ha-selector/ha-selector-selector.ts index 93061f9ea4..7f944d46ad 100644 --- a/src/components/ha-selector/ha-selector-selector.ts +++ b/src/components/ha-selector/ha-selector-selector.ts @@ -284,6 +284,8 @@ export class HaSelectorSelector extends LitElement { margin-bottom: 16px; padding-left: 16px; padding-right: 4px; + padding-inline-start: 16px; + padding-inline-end: 4px; white-space: nowrap; } `; diff --git a/src/components/ha-selector/ha-selector-state.ts b/src/components/ha-selector/ha-selector-state.ts index 4b16c8e8b0..e79259dcd6 100644 --- a/src/components/ha-selector/ha-selector-state.ts +++ b/src/components/ha-selector/ha-selector-state.ts @@ -7,9 +7,9 @@ import "../entity/ha-entity-state-picker"; @customElement("ha-selector-state") export class HaSelectorState extends SubscribeMixin(LitElement) { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: StateSelector; + @property({ attribute: false }) public selector!: StateSelector; @property() public value?: any; @@ -21,7 +21,7 @@ export class HaSelectorState extends SubscribeMixin(LitElement) { @property({ type: Boolean }) public required = true; - @property() public context?: { + @property({ attribute: false }) public context?: { filter_attribute?: string; filter_entity?: string; }; diff --git a/src/components/ha-selector/ha-selector-statistic.ts b/src/components/ha-selector/ha-selector-statistic.ts index 360075069a..078e75849e 100644 --- a/src/components/ha-selector/ha-selector-statistic.ts +++ b/src/components/ha-selector/ha-selector-statistic.ts @@ -6,9 +6,9 @@ import "../entity/ha-statistics-picker"; @customElement("ha-selector-statistic") export class HaStatisticSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: StatisticSelector; + @property({ attribute: false }) public selector!: StatisticSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-stt.ts b/src/components/ha-selector/ha-selector-stt.ts index 75549077fd..03150da3da 100644 --- a/src/components/ha-selector/ha-selector-stt.ts +++ b/src/components/ha-selector/ha-selector-stt.ts @@ -6,9 +6,9 @@ import "../ha-stt-picker"; @customElement("ha-selector-stt") export class HaSTTSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: STTSelector; + @property({ attribute: false }) public selector!: STTSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-target.ts b/src/components/ha-selector/ha-selector-target.ts index 9b649f094f..fafc97b62b 100644 --- a/src/components/ha-selector/ha-selector-target.ts +++ b/src/components/ha-selector/ha-selector-target.ts @@ -28,11 +28,11 @@ import "../ha-target-picker"; @customElement("ha-selector-target") export class HaTargetSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: TargetSelector; + @property({ attribute: false }) public selector!: TargetSelector; - @property() public value?: HassServiceTarget; + @property({ type: Object }) public value?: HassServiceTarget; @property() public label?: string; diff --git a/src/components/ha-selector/ha-selector-template.ts b/src/components/ha-selector/ha-selector-template.ts index 4a2f3ff31e..ae83de5ee1 100644 --- a/src/components/ha-selector/ha-selector-template.ts +++ b/src/components/ha-selector/ha-selector-template.ts @@ -7,7 +7,7 @@ import "../ha-input-helper-text"; @customElement("ha-selector-template") export class HaTemplateSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @property() public value?: string; diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index 6d175017a4..89d336c102 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -12,7 +12,7 @@ import "../ha-textfield"; @customElement("ha-selector-text") export class HaTextSelector extends LitElement { - @property() public hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @property() public value?: any; @@ -24,7 +24,7 @@ export class HaTextSelector extends LitElement { @property() public helper?: string; - @property() public selector!: StringSelector; + @property({ attribute: false }) public selector!: StringSelector; @property({ type: Boolean }) public disabled = false; diff --git a/src/components/ha-selector/ha-selector-trigger.ts b/src/components/ha-selector/ha-selector-trigger.ts index 1f9213aa91..567a81c4d3 100644 --- a/src/components/ha-selector/ha-selector-trigger.ts +++ b/src/components/ha-selector/ha-selector-trigger.ts @@ -7,11 +7,11 @@ import { HomeAssistant } from "../../types"; @customElement("ha-selector-trigger") export class HaTriggerSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: TriggerSelector; + @property({ attribute: false }) public selector!: TriggerSelector; - @property() public value?: Trigger; + @property({ attribute: false }) public value?: Trigger; @property() public label?: string; @@ -24,8 +24,7 @@ export class HaTriggerSelector extends LitElement { .disabled=${this.disabled} .triggers=${this.value || []} .hass=${this.hass} - .nested=${this.selector.trigger?.nested} - .reOrderMode=${this.selector.trigger?.reorder_mode} + .path=${this.selector.trigger?.path} > `; } diff --git a/src/components/ha-selector/ha-selector-tts-voice.ts b/src/components/ha-selector/ha-selector-tts-voice.ts index dc6b0c0fdc..e5a67c6ef4 100644 --- a/src/components/ha-selector/ha-selector-tts-voice.ts +++ b/src/components/ha-selector/ha-selector-tts-voice.ts @@ -6,9 +6,9 @@ import "../ha-tts-voice-picker"; @customElement("ha-selector-tts_voice") export class HaTTSVoiceSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: TTSVoiceSelector; + @property({ attribute: false }) public selector!: TTSVoiceSelector; @property() public value?: any; @@ -47,6 +47,6 @@ export class HaTTSVoiceSelector extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-selector-tts-voice": HaTTSVoiceSelector; + "ha-selector-tts_voice": HaTTSVoiceSelector; } } diff --git a/src/components/ha-selector/ha-selector-tts.ts b/src/components/ha-selector/ha-selector-tts.ts index fbf83fb167..9111047497 100644 --- a/src/components/ha-selector/ha-selector-tts.ts +++ b/src/components/ha-selector/ha-selector-tts.ts @@ -6,9 +6,9 @@ import "../ha-tts-picker"; @customElement("ha-selector-tts") export class HaTTSSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: TTSSelector; + @property({ attribute: false }) public selector!: TTSSelector; @property() public value?: any; diff --git a/src/components/ha-selector/ha-selector-ui-action.ts b/src/components/ha-selector/ha-selector-ui-action.ts index 7888ec1128..b20cb6ed17 100644 --- a/src/components/ha-selector/ha-selector-ui-action.ts +++ b/src/components/ha-selector/ha-selector-ui-action.ts @@ -8,11 +8,11 @@ import { HomeAssistant } from "../../types"; @customElement("ha-selector-ui_action") export class HaSelectorUiAction extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: UiActionSelector; + @property({ attribute: false }) public selector!: UiActionSelector; - @property() public value?: ActionConfig; + @property({ attribute: false }) public value?: ActionConfig; @property() public label?: string; @@ -39,6 +39,6 @@ export class HaSelectorUiAction extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-selector-ui-action": HaSelectorUiAction; + "ha-selector-ui_action": HaSelectorUiAction; } } diff --git a/src/components/ha-selector/ha-selector-ui-color.ts b/src/components/ha-selector/ha-selector-ui-color.ts index 4d77d1e213..291977ace0 100644 --- a/src/components/ha-selector/ha-selector-ui-color.ts +++ b/src/components/ha-selector/ha-selector-ui-color.ts @@ -7,9 +7,9 @@ import { HomeAssistant } from "../../types"; @customElement("ha-selector-ui_color") export class HaSelectorUiColor extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selector!: UiColorSelector; + @property({ attribute: false }) public selector!: UiColorSelector; @property() public value?: string; @@ -36,6 +36,6 @@ export class HaSelectorUiColor extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-selector-ui-color": HaSelectorUiColor; + "ha-selector-ui_color": HaSelectorUiColor; } } diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 5f68124f89..e7f9613f5f 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -34,6 +34,7 @@ const LOAD_ELEMENTS = { navigation: () => import("./ha-selector-navigation"), number: () => import("./ha-selector-number"), object: () => import("./ha-selector-object"), + qr_code: () => import("./ha-selector-qr-code"), select: () => import("./ha-selector-select"), selector: () => import("./ha-selector-selector"), state: () => import("./ha-selector-state"), @@ -59,11 +60,11 @@ const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]); @customElement("ha-selector") export class HaSelector extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @property() public name?: string; - @property() public selector!: Selector; + @property({ attribute: false }) public selector!: Selector; @property() public value?: any; @@ -71,7 +72,8 @@ export class HaSelector extends LitElement { @property() public helper?: string; - @property() public localizeValue?: (key: string) => string; + @property({ attribute: false }) + public localizeValue?: (key: string) => string; @property() public placeholder?: any; @@ -79,7 +81,7 @@ export class HaSelector extends LitElement { @property({ type: Boolean }) public required = true; - @property() public context?: Record; + @property({ attribute: false }) public context?: Record; public async focus() { await this.updateComplete; diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 119418355c..57c3e2b3ba 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -9,8 +9,8 @@ import { CSSResultGroup, html, LitElement, - PropertyValues, nothing, + PropertyValues, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -19,6 +19,7 @@ import { fireEvent } from "../common/dom/fire_event"; import { computeDomain } from "../common/entity/compute_domain"; import { computeObjectId } from "../common/entity/compute_object_id"; import { supportsFeature } from "../common/entity/supports-feature"; +import { nestedArrayMove } from "../common/util/array-move"; import { fetchIntegrationManifest, IntegrationManifest, @@ -86,11 +87,11 @@ export class HaServiceControl extends LitElement { @property({ type: Boolean }) public disabled = false; - @property({ reflect: true, type: Boolean }) public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; - @property({ type: Boolean }) public showAdvanced?: boolean; + @property({ type: Boolean }) public showAdvanced = false; - @property({ type: Boolean, reflect: true }) public hidePicker?: boolean; + @property({ type: Boolean, reflect: true }) public hidePicker = false; @state() private _value!: this["value"]; @@ -449,7 +450,23 @@ export class HaServiceControl extends LitElement { @value-changed=${this._dataChanged} >` : filteredFields?.map((dataField) => { + const selector = dataField?.selector ?? { text: undefined }; + const type = Object.keys(selector)[0]; + const enhancedSelector = [ + "action", + "condition", + "trigger", + ].includes(type) + ? { + [type]: { + ...selector[type], + path: [dataField.key], + }, + } + : selector; + const showOptional = showOptionalToggle(dataField); + return dataField.selector && (!dataField.advanced || this.showAdvanced || @@ -488,7 +505,7 @@ export class HaServiceControl extends LitElement { (!this._value?.data || this._value.data[dataField.key] === undefined))} .hass=${this.hass} - .selector=${dataField.selector} + .selector=${enhancedSelector} .key=${dataField.key} @value-changed=${this._serviceDataChanged} .value=${this._value?.data @@ -496,6 +513,7 @@ export class HaServiceControl extends LitElement { : undefined} .placeholder=${dataField.default} .localizeValue=${this._localizeValueCallback} + @item-moved=${this._itemMoved} > ` : ""; @@ -697,6 +715,22 @@ export class HaServiceControl extends LitElement { }); } + private _itemMoved(ev) { + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + + const data = this.value?.data ?? {}; + + const newData = nestedArrayMove(data, oldIndex, newIndex, oldPath, newPath); + + fireEvent(this, "value-changed", { + value: { + ...this.value, + data: newData, + }, + }); + } + private _dataChanged(ev: CustomEvent) { ev.stopPropagation(); if (!ev.detail.isValid) { @@ -754,6 +788,8 @@ export class HaServiceControl extends LitElement { } ha-checkbox { margin-left: -16px; + margin-inline-start: -16px; + margin-inline-end: initial; } .help-icon { color: var(--secondary-text-color); @@ -763,6 +799,11 @@ export class HaServiceControl extends LitElement { display: flex; align-items: center; padding-right: 2px; + padding-inline-end: 2px; + padding-inline-start: initial; + } + .description p { + direction: ltr; } `; } diff --git a/src/components/ha-service-icon.ts b/src/components/ha-service-icon.ts new file mode 100644 index 0000000000..663043639e --- /dev/null +++ b/src/components/ha-service-icon.ts @@ -0,0 +1,57 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import { until } from "lit/directives/until"; +import { DEFAULT_SERVICE_ICON, FIXED_DOMAIN_ICONS } from "../common/const"; +import { computeDomain } from "../common/entity/compute_domain"; +import { serviceIcon } from "../data/icons"; +import { HomeAssistant } from "../types"; +import "./ha-icon"; +import "./ha-svg-icon"; + +@customElement("ha-service-icon") +export class HaServiceIcon extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public service?: string; + + @property() public icon?: string; + + protected render() { + if (this.icon) { + return html``; + } + + if (!this.service) { + return nothing; + } + + if (!this.hass) { + return this._renderFallback(); + } + + const icon = serviceIcon(this.hass, this.service).then((icn) => { + if (icn) { + return html``; + } + return this._renderFallback(); + }); + + return html`${until(icon)}`; + } + + private _renderFallback() { + const domain = computeDomain(this.service!); + + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-service-icon": HaServiceIcon; + } +} diff --git a/src/components/ha-service-picker.ts b/src/components/ha-service-picker.ts index db4dec62d7..c370928521 100644 --- a/src/components/ha-service-picker.ts +++ b/src/components/ha-service-picker.ts @@ -1,5 +1,5 @@ -import { html, LitElement } from "lit"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; +import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; @@ -7,16 +7,9 @@ import { LocalizeFunc } from "../common/translations/localize"; import { domainToName } from "../data/integration"; import { HomeAssistant } from "../types"; import "./ha-combo-box"; - -const rowRenderer: ComboBoxLitRenderer<{ service: string; name: string }> = ( - item -) => - html` - ${item.name} - ${item.name === item.service ? "" : item.service} - `; +import "./ha-list-item"; +import "./ha-service-icon"; +import { getServiceIcons } from "../data/icons"; @customElement("ha-service-picker") class HaServicePicker extends LitElement { @@ -31,9 +24,24 @@ class HaServicePicker extends LitElement { protected willUpdate() { if (!this.hasUpdated) { this.hass.loadBackendTranslation("services"); + getServiceIcons(this.hass); } } + private _rowRenderer: ComboBoxLitRenderer<{ service: string; name: string }> = + (item) => + html` + + ${item.name} + ${item.name === item.service ? "" : item.service} + `; + protected render() { return html` panel.url_path); + const panel = panelOrder.splice(oldIndex, 1)[0]; + panelOrder.splice(newIndex, 0, panel); + + this._panelOrder = panelOrder; + } + private _renderPanelsEdit(beforeSpacer: PanelInfo[]) { - // prettier-ignore - return html`
- ${guard([this._hiddenPanels, this._renderEmptySortable], () => - this._renderEmptySortable ? "" : this._renderPanels(beforeSpacer) - )} -
- ${this._renderSpacer()} - ${this._renderHiddenPanels()} `; + return html` + +
${this._renderPanels(beforeSpacer)}
+
+ ${this._renderSpacer()}${this._renderHiddenPanels()} + `; } private _renderHiddenPanels() { @@ -674,44 +685,22 @@ class HaSidebar extends SubscribeMixin(LitElement) { fireEvent(this, "hass-edit-sidebar", { editMode: true }); } - private async _activateEditMode() { - await Promise.all([this._loadSortableStyle(), this._createSortable()]); + private async _editModeActivated() { + await this._loadEditStyle(); } - private async _loadSortableStyle() { - if (this.sortableStyleLoaded) return; + private async _loadEditStyle() { + if (this._editStyleLoaded) return; - const sortStylesImport = await import("../resources/ha-sortable-style"); + const editStylesImport = await import("../resources/ha-sidebar-edit-style"); const style = document.createElement("style"); - style.innerHTML = (sortStylesImport.sortableStyles as CSSResult).cssText; + style.innerHTML = (editStylesImport.sidebarEditStyle as CSSResult).cssText; this.shadowRoot!.appendChild(style); - this.sortableStyleLoaded = true; await this.updateComplete; } - private async _createSortable() { - const Sortable = (await import("../resources/sortable")).default; - this._sortable = new Sortable( - this.shadowRoot!.getElementById("sortable")!, - { - animation: 150, - fallbackClass: "sortable-fallback", - dataIdAttr: "data-panel", - handle: "paper-icon-item", - onSort: async () => { - this._panelOrder = this._sortable!.toArray(); - }, - } - ); - } - - private _deactivateEditMode() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private _closeEditMode() { fireEvent(this, "hass-edit-sidebar", { editMode: false }); } @@ -724,13 +713,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { } // Make a copy for Memoize this._hiddenPanels = [...this._hiddenPanels, panel]; - this._renderEmptySortable = true; - await this.updateComplete; - const container = this.shadowRoot!.getElementById("sortable")!; - while (container.lastElementChild) { - container.removeChild(container.lastElementChild); - } - this._renderEmptySortable = false; + // Remove it from the panel order + this._panelOrder = this._panelOrder.filter((order) => order !== panel); } private async _unhidePanel(ev: Event) { @@ -739,13 +723,6 @@ class HaSidebar extends SubscribeMixin(LitElement) { this._hiddenPanels = this._hiddenPanels.filter( (hidden) => hidden !== panel ); - this._renderEmptySortable = true; - await this.updateComplete; - const container = this.shadowRoot!.getElementById("sortable")!; - while (container.lastElementChild) { - container.removeChild(container.lastElementChild); - } - this._renderEmptySortable = false; } private _itemMouseEnter(ev: MouseEvent) { @@ -910,7 +887,7 @@ class HaSidebar extends SubscribeMixin(LitElement) { .menu mwc-button { width: 100%; } - #sortable, + .reorder-list, .hidden-panel { display: none; } @@ -1071,6 +1048,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { .configuration-badge { position: absolute; left: calc(var(--app-drawer-width, 248px) - 42px); + inset-inline-start: calc(var(--app-drawer-width, 248px) - 42px); + inset-inline-end: initial; min-width: 20px; box-sizing: border-box; border-radius: 50%; diff --git a/src/components/ha-sortable.ts b/src/components/ha-sortable.ts new file mode 100644 index 0000000000..b188620c87 --- /dev/null +++ b/src/components/ha-sortable.ts @@ -0,0 +1,176 @@ +/* eslint-disable lit/prefer-static-styles */ +import { html, LitElement, nothing, PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators"; +import type { SortableEvent } from "sortablejs"; +import { fireEvent } from "../common/dom/fire_event"; +import type { SortableInstance } from "../resources/sortable"; +import { ItemPath } from "../types"; + +declare global { + interface HASSDomEvents { + "item-moved": { + oldIndex: number; + newIndex: number; + oldPath?: ItemPath; + newPath?: ItemPath; + }; + } +} + +@customElement("ha-sortable") +export class HaSortable extends LitElement { + private _sortable?: SortableInstance; + + @property({ type: Boolean }) + public disabled = false; + + @property({ type: Array }) + public path?: ItemPath; + + @property({ type: Boolean, attribute: "no-style" }) + public noStyle: boolean = false; + + @property({ type: String, attribute: "draggable-selector" }) + public draggableSelector?: string; + + @property({ type: String, attribute: "handle-selector" }) + public handleSelector?: string; + + @property({ type: String, attribute: "group" }) + public group?: string; + + protected updated(changedProperties: PropertyValues) { + if (changedProperties.has("disabled")) { + if (this.disabled) { + this._destroySortable(); + } else { + this._createSortable(); + } + } + } + + // Workaround for connectedCallback just after disconnectedCallback (when dragging sortable with sortable children) + private _shouldBeDestroy = false; + + public disconnectedCallback() { + super.disconnectedCallback(); + this._shouldBeDestroy = true; + setTimeout(() => { + if (this._shouldBeDestroy) { + this._destroySortable(); + this._shouldBeDestroy = false; + } + }, 1); + } + + public connectedCallback() { + super.connectedCallback(); + this._shouldBeDestroy = false; + } + + protected createRenderRoot() { + return this; + } + + protected render() { + if (this.noStyle) return nothing; + return html` + + `; + } + + private async _createSortable() { + if (this._sortable) return; + const container = this.children[0] as HTMLElement | undefined; + + if (!container) return; + + const Sortable = (await import("../resources/sortable")).default; + + const options: SortableInstance.Options = { + animation: 150, + swapThreshold: 0.75, + onChoose: this._handleChoose, + onEnd: this._handleEnd, + }; + + if (this.draggableSelector) { + options.draggable = this.draggableSelector; + } + if (this.handleSelector) { + options.handle = this.handleSelector; + } + if (this.draggableSelector) { + options.draggable = this.draggableSelector; + } + if (this.group) { + options.group = this.group; + } + + this._sortable = new Sortable(container, options); + } + + private _handleEnd = async (evt: SortableEvent) => { + // put back in original location + if ((evt.item as any).placeholder) { + (evt.item as any).placeholder.replaceWith(evt.item); + delete (evt.item as any).placeholder; + } + + const oldIndex = evt.oldIndex; + const oldPath = (evt.from.parentElement as HaSortable).path; + const newIndex = evt.newIndex; + const newPath = (evt.to.parentElement as HaSortable).path; + + if ( + oldIndex === undefined || + newIndex === undefined || + (oldIndex === newIndex && oldPath?.join(".") === newPath?.join(".")) + ) { + return; + } + + fireEvent(this, "item-moved", { + oldIndex, + newIndex, + oldPath, + newPath, + }); + }; + + private _handleChoose = (evt: SortableEvent) => { + (evt.item as any).placeholder = document.createComment("sort-placeholder"); + evt.item.after((evt.item as any).placeholder); + }; + + private _destroySortable() { + if (!this._sortable) return; + this._sortable.destroy(); + this._sortable = undefined; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-sortable": HaSortable; + } +} diff --git a/src/components/ha-state-icon.ts b/src/components/ha-state-icon.ts index eace7706bc..aa6371ec70 100644 --- a/src/components/ha-state-icon.ts +++ b/src/components/ha-state-icon.ts @@ -1,25 +1,60 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { html, LitElement, TemplateResult } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; -import { stateIconPath } from "../common/entity/state_icon_path"; +import { until } from "lit/directives/until"; +import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../common/const"; +import { computeStateDomain } from "../common/entity/compute_state_domain"; +import { entityIcon } from "../data/icons"; +import { HomeAssistant } from "../types"; import "./ha-icon"; import "./ha-svg-icon"; @customElement("ha-state-icon") export class HaStateIcon extends LitElement { - @property({ attribute: false }) public state?: HassEntity; + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HassEntity; + + @property({ attribute: false }) public stateValue?: string; @property() public icon?: string; - protected render(): TemplateResult { - if (this.icon || this.state?.attributes.icon) { - return html``; + protected render() { + const overrideIcon = + this.icon || + (this.stateObj && this.hass?.entities[this.stateObj.entity_id]?.icon) || + this.stateObj?.attributes.icon; + if (overrideIcon) { + return html``; } - return html``; + if (!this.stateObj) { + return nothing; + } + if (!this.hass) { + return this._renderFallback(); + } + const icon = entityIcon(this.hass, this.stateObj, this.stateValue).then( + (icn) => { + if (icn) { + return html``; + } + return this._renderFallback(); + } + ); + return html`${until(icon)}`; + } + + private _renderFallback() { + const domain = computeStateDomain(this.stateObj!); + + return html` + + `; } } + declare global { interface HTMLElementTagNameMap { "ha-state-icon": HaStateIcon; diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 549fa02320..4030e8ff34 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -62,9 +62,11 @@ export class HaTargetPicker extends LitElement { @property({ type: Array, attribute: "include-device-classes" }) public includeDeviceClasses?: string[]; - @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; + @property({ attribute: false }) + public deviceFilter?: HaDevicePickerDeviceFilterFunc; - @property() public entityFilter?: HaEntityPickerEntityFilterFunc; + @property({ attribute: false }) + public entityFilter?: HaEntityPickerEntityFilterFunc; @property({ type: Boolean, reflect: true }) public disabled = false; @@ -96,6 +98,7 @@ export class HaTargetPicker extends LitElement { area_id, area?.name || area_id, undefined, + area?.icon, mdiSofa ); }) @@ -108,6 +111,7 @@ export class HaTargetPicker extends LitElement { device_id, device ? computeDeviceName(device, this.hass) : device_id, undefined, + undefined, mdiDevices ); }) @@ -207,7 +211,8 @@ export class HaTargetPicker extends LitElement { id: string, name: string, entityState?: HassEntity, - iconPath?: string + icon?: string | null, + fallbackIconPath?: string ) { return html`
- ${iconPath - ? html`` - : ""} + .icon=${icon} + >` + : fallbackIconPath + ? html`` + : ""} ${entityState ? html`` : ""} @@ -637,6 +648,8 @@ export class HaTargetPicker extends LitElement { } .expand-btn { margin-right: 0; + margin-inline-end: 0; + margin-inline-start: initial; } .mdc-chip.area_id:not(.add) { border: 2px solid #fed6a4; diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts index 49068fbc54..170c0c179d 100644 --- a/src/components/ha-textfield.ts +++ b/src/components/ha-textfield.ts @@ -6,15 +6,15 @@ import { mainWindow } from "../common/dom/get_main_window"; @customElement("ha-textfield") export class HaTextField extends TextFieldBase { - @property({ type: Boolean }) public invalid?: boolean; + @property({ type: Boolean }) public invalid = false; @property({ attribute: "error-message" }) public errorMessage?: string; // @ts-ignore - @property({ type: Boolean }) public icon?: boolean; + @property({ type: Boolean }) public icon = false; // @ts-ignore - @property({ type: Boolean }) public iconTrailing?: boolean; + @property({ type: Boolean }) public iconTrailing = false; @property() public autocomplete?: string; @@ -187,6 +187,8 @@ export class HaTextField extends TextFieldBase { } .mdc-text-field__affix--prefix { padding-right: var(--text-field-prefix-padding-right, 2px); + padding-inline-end: var(--text-field-prefix-padding-right, 2px); + padding-inline-start: initial; } .mdc-text-field:not(.mdc-text-field--disabled) diff --git a/src/components/ha-theme-picker.ts b/src/components/ha-theme-picker.ts index 104c327cad..425b1a7227 100644 --- a/src/components/ha-theme-picker.ts +++ b/src/components/ha-theme-picker.ts @@ -21,7 +21,7 @@ export class HaThemePicker extends LitElement { @property() public label?: string; - @property() includeDefault?: boolean = false; + @property({ type: Boolean }) includeDefault = false; @property({ attribute: false }) public hass?: HomeAssistant; diff --git a/src/components/ha-top-app-bar-fixed.ts b/src/components/ha-top-app-bar-fixed.ts index 49501ac01b..e6fac8a019 100644 --- a/src/components/ha-top-app-bar-fixed.ts +++ b/src/components/ha-top-app-bar-fixed.ts @@ -23,6 +23,10 @@ export class HaTopAppBarFixed extends TopAppBarFixedBase { var(--mdc-theme-primary) ); } + .mdc-top-app-bar__title { + padding-inline-start: 20px; + padding-inline-end: initial; + } `, ]; } diff --git a/src/components/ha-two-pane-top-app-bar-fixed.ts b/src/components/ha-two-pane-top-app-bar-fixed.ts index df6477534c..5daae9d618 100644 --- a/src/components/ha-two-pane-top-app-bar-fixed.ts +++ b/src/components/ha-two-pane-top-app-bar-fixed.ts @@ -17,7 +17,7 @@ export const passiveEventOptionsIfSupported = supportsPassiveEventListener : undefined; @customElement("ha-two-pane-top-app-bar-fixed") -export abstract class TopAppBarBaseBase extends BaseElement { +export class TopAppBarBaseBase extends BaseElement { protected override mdcFoundation!: MDCFixedTopAppBarFoundation; protected override mdcFoundationClass = MDCFixedTopAppBarFoundation; @@ -320,6 +320,16 @@ export abstract class TopAppBarBaseBase extends BaseElement { height: 100%; overflow: auto; } + .mdc-top-app-bar__title { + padding-inline-start: 20px; + padding-inline-end: initial; + } `, ]; } + +declare global { + interface HTMLElementTagNameMap { + "ha-two-pane-top-app-bar-fixed": TopAppBarBaseBase; + } +} diff --git a/src/components/ha-vacuum-state.ts b/src/components/ha-vacuum-state.ts index 0e033e8cde..52c93b4a17 100644 --- a/src/components/ha-vacuum-state.ts +++ b/src/components/ha-vacuum-state.ts @@ -94,6 +94,8 @@ export class HaVacuumState extends LitElement { top: 3px; height: 37px; margin-right: -0.57em; + margin-inline-end: -0.57em; + margin-inline-start: initial; } mwc-button[disabled] { background-color: transparent; diff --git a/src/components/map/ha-locations-editor.ts b/src/components/map/ha-locations-editor.ts index 76edc2606f..e0f1c1d8f3 100644 --- a/src/components/map/ha-locations-editor.ts +++ b/src/components/map/ha-locations-editor.ts @@ -61,7 +61,7 @@ export class HaLocationsEditor extends LitElement { @property({ type: Number }) public zoom = 16; - @property({ type: Boolean }) public darkMode?: boolean; + @property({ type: Boolean }) public darkMode = false; @state() private _locationMarkers?: Record; @@ -133,7 +133,7 @@ export class HaLocationsEditor extends LitElement { .layers=${this._getLayers(this._circles, this._locationMarkers)} .zoom=${this.zoom} .autoFit=${this.autoFit} - .darkMode=${this.darkMode} + ?darkMode=${this.darkMode} > ${this.helper ? html`${this.helper}` @@ -168,6 +168,36 @@ export class HaLocationsEditor extends LitElement { } } + public updated(changedProps: PropertyValues): void { + // Still loading. + if (!this.Leaflet) { + return; + } + + if (changedProps.has("locations")) { + const oldLocations = changedProps.get("locations"); + const movedLocations = this.locations?.filter( + (loc, idx) => + !oldLocations[idx] || + ((loc.latitude !== oldLocations[idx].latitude || + loc.longitude !== oldLocations[idx].longitude) && + this.map.leafletMap?.getBounds().contains({ + lat: oldLocations[idx].latitude, + lng: oldLocations[idx].longitude, + }) && + !this.map.leafletMap + ?.getBounds() + .contains({ lat: loc.latitude, lng: loc.longitude })) + ); + if (movedLocations?.length === 1) { + this.map.leafletMap?.panTo({ + lat: movedLocations[0].latitude, + lng: movedLocations[0].longitude, + }); + } + } + } + private _updateLocation(ev: DragEndEvent) { const marker = ev.target; const latlng: LatLng = marker.getLatLng(); diff --git a/src/components/map/ha-map.ts b/src/components/map/ha-map.ts index c90053c9b8..42834dbe69 100644 --- a/src/components/map/ha-map.ts +++ b/src/components/map/ha-map.ts @@ -20,6 +20,7 @@ import { loadPolyfillIfNeeded } from "../../resources/resize-observer.polyfill"; import { HomeAssistant } from "../../types"; import "../ha-icon-button"; import "./ha-entity-marker"; +import { isTouch } from "../../util/is_touch"; const getEntityId = (entity: string | HaMapEntity): string => typeof entity === "string" ? entity : entity.entity_id; @@ -58,9 +59,9 @@ export class HaMap extends ReactiveElement { @property({ type: Boolean }) public interactiveZones = false; - @property({ type: Boolean }) public fitZones?: boolean; + @property({ type: Boolean }) public fitZones = false; - @property({ type: Boolean }) public darkMode?: boolean; + @property({ type: Boolean }) public darkMode = false; @property({ type: Number }) public zoom = 14; @@ -155,9 +156,9 @@ export class HaMap extends ReactiveElement { } private _updateMapStyle(): void { - const darkMode = this.darkMode ?? this.hass.themes.darkMode ?? false; - const forcedDark = this.darkMode ?? false; - const map = this.shadowRoot!.getElementById("map"); + const darkMode = this.darkMode || (this.hass.themes.darkMode ?? false); + const forcedDark = this.darkMode; + const map = this.renderRoot.querySelector("#map"); map!.classList.toggle("dark", darkMode); map!.classList.toggle("forced-dark", forcedDark); } @@ -282,7 +283,7 @@ export class HaMap extends ReactiveElement { this._mapPaths.push( Leaflet! .circleMarker(path.points[pointIndex].point, { - radius: 3, + radius: isTouch ? 8 : 3, color: path.color || darkPrimaryColor, opacity, fillOpacity: opacity, @@ -312,7 +313,7 @@ export class HaMap extends ReactiveElement { this._mapPaths.push( Leaflet! .circleMarker(path.points[pointIndex].point, { - radius: 3, + radius: isTouch ? 8 : 3, color: path.color || darkPrimaryColor, opacity, fillOpacity: opacity, @@ -360,7 +361,7 @@ export class HaMap extends ReactiveElement { ); const className = - this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light"; + this.darkMode || this.hass.themes.darkMode ? "dark" : "light"; for (const entity of this.entities) { const stateObj = hass.states[getEntityId(entity)]; diff --git a/src/components/media-player/dialog-media-manage.ts b/src/components/media-player/dialog-media-manage.ts index b7b1158e4e..3822d27591 100644 --- a/src/components/media-player/dialog-media-manage.ts +++ b/src/components/media-player/dialog-media-manage.ts @@ -318,10 +318,6 @@ class DialogMediaManage extends LitElement { display: block; } - mwc-list { - direction: ltr; - } - .danger { --mdc-theme-primary: var(--error-color); } diff --git a/src/components/media-player/ha-browse-media-tts.ts b/src/components/media-player/ha-browse-media-tts.ts index cb10b053c4..52a43d04d0 100644 --- a/src/components/media-player/ha-browse-media-tts.ts +++ b/src/components/media-player/ha-browse-media-tts.ts @@ -31,9 +31,9 @@ declare global { @customElement("ha-browse-media-tts") class BrowseMediaTTS extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public item!: MediaPlayerItem; + @property({ attribute: false }) public item!: MediaPlayerItem; @property() public action!: MediaPlayerBrowseAction; diff --git a/src/components/media-player/ha-media-manage-button.ts b/src/components/media-player/ha-media-manage-button.ts index e777aa95dc..90a85482df 100644 --- a/src/components/media-player/ha-media-manage-button.ts +++ b/src/components/media-player/ha-media-manage-button.ts @@ -19,7 +19,7 @@ declare global { class MediaManageButton extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() currentItem?: MediaPlayerItem; + @property({ attribute: false }) currentItem?: MediaPlayerItem; @state() _uploading = 0; diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index b3347dc8b1..6737ea613b 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -92,14 +92,13 @@ export class HaMediaPlayerBrowse extends LitElement { @property({ type: Boolean }) public dialog = false; - @property() public navigateIds!: MediaPlayerItemId[]; + @property({ attribute: false }) public navigateIds: MediaPlayerItemId[] = []; - @property({ type: Boolean, attribute: "narrow", reflect: true }) - // @ts-ignore - private _narrow = false; + // @todo Consider reworking to eliminate need for attribute since it is manipulated internally + @property({ type: Boolean, reflect: true }) public narrow = false; - @property({ type: Boolean, attribute: "scroll", reflect: true }) - private _scrolled = false; + // @todo Consider reworking to eliminate need for attribute since it is manipulated internally + @property({ type: Boolean, reflect: true }) public scrolled = false; @state() private _error?: { message: string; code: string }; @@ -178,7 +177,7 @@ export class HaMediaPlayerBrowse extends LitElement { // We're navigating. Reset the shizzle. this._content?.scrollTo(0, 0); - this._scrolled = false; + this.scrolled = false; const oldCurrentItem = this._currentItem; const oldParentItem = this._parentItem; this._currentItem = undefined; @@ -373,7 +372,7 @@ export class HaMediaPlayerBrowse extends LitElement { "" )}" > - ${this._narrow && currentItem?.can_play + ${this.narrow && currentItem?.can_play ? html` ${currentItem.can_play && - (!currentItem.thumbnail || !this._narrow) + (!currentItem.thumbnail || !this.narrow) ? html` { @@ -868,10 +867,10 @@ export class HaMediaPlayerBrowse extends LitElement { @eventOptions({ passive: true }) private _scroll(ev: Event): void { const content = ev.currentTarget as HTMLDivElement; - if (!this._scrolled && content.scrollTop > this._headerOffsetHeight) { - this._scrolled = true; - } else if (this._scrolled && content.scrollTop < this._headerOffsetHeight) { - this._scrolled = false; + if (!this.scrolled && content.scrollTop > this._headerOffsetHeight) { + this.scrolled = true; + } else if (this.scrolled && content.scrollTop < this._headerOffsetHeight) { + this.scrolled = false; } } @@ -1266,58 +1265,58 @@ export class HaMediaPlayerBrowse extends LitElement { } /* ============= Scroll ============= */ - :host([scroll]) .breadcrumb .subtitle { + :host([scrolled]) .breadcrumb .subtitle { height: 0; margin: 0; } - :host([scroll]) .breadcrumb .title { + :host([scrolled]) .breadcrumb .title { -webkit-line-clamp: 1; } - :host(:not([narrow])[scroll]) .header:not(.no-img) ha-icon-button { + :host(:not([narrow])[scrolled]) .header:not(.no-img) ha-icon-button { align-self: center; } - :host([scroll]) .header-info mwc-button, + :host([scrolled]) .header-info mwc-button, .no-img .header-info mwc-button { padding-right: 4px; } - :host([scroll][narrow]) .no-img .header-info mwc-button { + :host([scrolled][narrow]) .no-img .header-info mwc-button { padding-right: 16px; } - :host([scroll]) .header-info { + :host([scrolled]) .header-info { flex-direction: row; } - :host([scroll]) .header-info mwc-button { + :host([scrolled]) .header-info mwc-button { align-self: center; margin-top: 0; margin-bottom: 0; padding-bottom: 0; } - :host([scroll][narrow]) .no-img .header-info { + :host([scrolled][narrow]) .no-img .header-info { flex-direction: row-reverse; } - :host([scroll][narrow]) .header-info { + :host([scrolled][narrow]) .header-info { padding: 20px 24px 10px 24px; align-items: center; } - :host([scroll]) .header-content { + :host([scrolled]) .header-content { align-items: flex-end; flex-direction: row; } - :host([scroll]) .header-content .img { + :host([scrolled]) .header-content .img { height: 75px; width: 75px; } - :host([scroll]) .breadcrumb { + :host([scrolled]) .breadcrumb { padding-top: 0; align-self: center; } - :host([scroll][narrow]) .header-content .img { + :host([scrolled][narrow]) .header-content .img { height: 100px; width: 100px; padding-bottom: initial; margin-bottom: 0; } - :host([scroll]) ha-fab { + :host([scrolled]) ha-fab { bottom: 0px; right: -24px; --mdc-fab-box-shadow: none; diff --git a/src/components/media-player/ha-media-upload-button.ts b/src/components/media-player/ha-media-upload-button.ts index 92e7531dff..97a92ea171 100644 --- a/src/components/media-player/ha-media-upload-button.ts +++ b/src/components/media-player/ha-media-upload-button.ts @@ -24,7 +24,7 @@ declare global { class MediaUploadButton extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() currentItem?: MediaPlayerItem; + @property({ attribute: false }) currentItem?: MediaPlayerItem; @state() _uploading = 0; diff --git a/src/components/tile/ha-tile-badge.ts b/src/components/tile/ha-tile-badge.ts index dea713e069..33aa85fce4 100644 --- a/src/components/tile/ha-tile-badge.ts +++ b/src/components/tile/ha-tile-badge.ts @@ -1,19 +1,13 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement } from "lit/decorators"; import "../ha-icon"; @customElement("ha-tile-badge") export class HaTileBadge extends LitElement { - @property() public iconPath?: string; - - @property() public icon?: string; - protected render(): TemplateResult { return html`
- ${this.icon - ? html`` - : html``} +
`; } @@ -36,8 +30,7 @@ export class HaTileBadge extends LitElement { background-color: var(--tile-badge-background-color); transition: background-color 280ms ease-in-out; } - .badge ha-icon, - .badge ha-svg-icon { + .badge ::slotted(*) { color: var(--tile-badge-icon-color); } `; diff --git a/src/components/tile/ha-tile-icon.ts b/src/components/tile/ha-tile-icon.ts index 5bf889575f..f5ebe8863c 100644 --- a/src/components/tile/ha-tile-icon.ts +++ b/src/components/tile/ha-tile-icon.ts @@ -1,20 +1,14 @@ -import { CSSResultGroup, html, css, LitElement, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; +import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; +import { customElement } from "lit/decorators"; import "../ha-icon"; import "../ha-svg-icon"; @customElement("ha-tile-icon") export class HaTileIcon extends LitElement { - @property() public iconPath?: string; - - @property() public icon?: string; - protected render(): TemplateResult { return html`
- ${this.icon - ? html`` - : html``} +
`; } @@ -47,8 +41,7 @@ export class HaTileIcon extends LitElement { transition: color 180ms ease-in-out; overflow: hidden; } - .shape ha-icon, - .shape ha-svg-icon { + .shape ::slotted(*) { display: flex; color: var(--tile-icon-color); transition: color 180ms ease-in-out; diff --git a/src/components/tile/ha-tile-image.ts b/src/components/tile/ha-tile-image.ts index 7ecb5af008..fc29eb57d0 100644 --- a/src/components/tile/ha-tile-image.ts +++ b/src/components/tile/ha-tile-image.ts @@ -1,16 +1,19 @@ -import { CSSResultGroup, html, css, LitElement, nothing } from "lit"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; +export type TileImageStyle = "square" | "rounded-square" | "circle"; @customElement("ha-tile-image") export class HaTileImage extends LitElement { @property() public imageUrl?: string; @property() public imageAlt?: string; + @property() public imageStyle: TileImageStyle = "circle"; + protected render() { return html` -
+
${this.imageUrl ? html`${ifDefined(this.imageAlt)}` : nothing} @@ -31,6 +34,12 @@ export class HaTileImage extends LitElement { justify-content: center; overflow: hidden; } + .image.rounded-square { + border-radius: 8%; + } + .image.square { + border-radius: 0; + } .image img { width: 100%; height: 100%; diff --git a/src/components/trace/ha-timeline.ts b/src/components/trace/ha-timeline.ts index bf5685bb9e..8c4fae44a1 100644 --- a/src/components/trace/ha-timeline.ts +++ b/src/components/trace/ha-timeline.ts @@ -76,6 +76,8 @@ export class HaTimeline extends LitElement { flex-direction: column; align-items: center; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; width: 24px; } :host([notEnabled]) ha-svg-icon { diff --git a/src/components/trace/ha-trace-logbook.ts b/src/components/trace/ha-trace-logbook.ts index 7b0118b3cf..391c004a62 100644 --- a/src/components/trace/ha-trace-logbook.ts +++ b/src/components/trace/ha-trace-logbook.ts @@ -10,7 +10,7 @@ import { TraceExtended } from "../../data/trace"; export class HaTraceLogbook extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean, reflect: true }) public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; @property({ attribute: false }) public trace!: TraceExtended; diff --git a/src/components/trace/ha-trace-path-details.ts b/src/components/trace/ha-trace-path-details.ts index 514377e61f..cb71dd98a3 100644 --- a/src/components/trace/ha-trace-path-details.ts +++ b/src/components/trace/ha-trace-path-details.ts @@ -35,7 +35,7 @@ const TRACE_PATH_TABS = [ export class HaTracePathDetails extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean, reflect: true }) public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; @property({ attribute: false }) public trace!: TraceExtended; @@ -43,9 +43,10 @@ export class HaTracePathDetails extends LitElement { @property({ attribute: false }) public selected!: NodeInfo; - @property() public renderedNodes: Record = {}; + @property({ attribute: false }) + public renderedNodes: Record = {}; - @property() public trackedNodes!: Record; + @property({ attribute: false }) public trackedNodes!: Record; @state() private _view: (typeof TRACE_PATH_TABS)[number] = "step_config"; diff --git a/src/components/trace/hat-graph-branch.ts b/src/components/trace/hat-graph-branch.ts index 470ea6f6fe..373cfb58d5 100644 --- a/src/components/trace/hat-graph-branch.ts +++ b/src/components/trace/hat-graph-branch.ts @@ -17,9 +17,9 @@ interface BranchConfig { */ @customElement("hat-graph-branch") export class HatGraphBranch extends LitElement { - @property({ reflect: true, type: Boolean }) disabled?: boolean; + @property({ type: Boolean, reflect: true }) disabled = false; - @property({ type: Boolean }) selected?: boolean; + @property({ type: Boolean }) selected = false; @property({ type: Boolean }) start = false; diff --git a/src/components/trace/hat-graph-node.ts b/src/components/trace/hat-graph-node.ts index c55b03e20e..b9b6a499f0 100644 --- a/src/components/trace/hat-graph-node.ts +++ b/src/components/trace/hat-graph-node.ts @@ -8,6 +8,7 @@ import { } from "lit"; import { customElement, property } from "lit/decorators"; import { NODE_SIZE, SPACING } from "./hat-graph-const"; +import { isSafari } from "../../util/is_safari"; /** * @attribute active @@ -17,11 +18,11 @@ import { NODE_SIZE, SPACING } from "./hat-graph-const"; export class HatGraphNode extends LitElement { @property() iconPath?: string; - @property({ reflect: true, type: Boolean }) disabled?: boolean; + @property({ type: Boolean, reflect: true }) public disabled = false; @property({ reflect: true, type: Boolean }) notEnabled = false; - @property({ reflect: true, type: Boolean }) graphStart?: boolean; + @property({ reflect: true, type: Boolean }) graphStart = false; @property({ type: Boolean, attribute: "nofocus" }) noFocus = false; @@ -42,6 +43,7 @@ export class HatGraphNode extends LitElement { const width = SPACING + NODE_SIZE; return html` - `; - } - - private render_device_node( - node: DeviceAction, - path: string, - graphStart = false, - disabled = false - ) { - return html` - - `; - } - - private render_event_node( - node: EventAction, - path: string, - graphStart = false, - disabled = false - ) { - return html` - - `; - } - private render_repeat_node( node: RepeatAction, path: string, @@ -475,25 +406,6 @@ export class HatScriptGraph extends LitElement { `; } - private render_scene_node( - node: SceneAction, - path: string, - graphStart = false, - disabled = false - ) { - return html` - - `; - } - private render_service_node( node: ServiceAction, path: string, @@ -580,24 +492,6 @@ export class HatScriptGraph extends LitElement { `; } - private render_stop_node( - node: Action, - path: string, - graphStart = false, - disabled = false - ) { - return html` - - `; - } - private render_other_node( node: Action, path: string, @@ -607,7 +501,7 @@ export class HatScriptGraph extends LitElement { return html` - this.value?.map( - (user_id, idx) => html` -
- - - > -
- ` - ) + ${guard([notSelectedUsers], () => + this.value?.map( + (user_id, idx) => html` +
+ + + > +
+ ` + ) )} `; diff --git a/src/data/action.ts b/src/data/action.ts index 2c5f2aa125..c85d5226c8 100644 --- a/src/data/action.ts +++ b/src/data/action.ts @@ -2,6 +2,7 @@ import { mdiAbTesting, mdiApplicationVariableOutline, mdiArrowDecision, + mdiBullhorn, mdiCallSplit, mdiCodeBraces, mdiDevices, @@ -36,6 +37,7 @@ export const ACTION_ICONS = { stop: mdiHandBackRight, parallel: mdiShuffleDisabled, variables: mdiApplicationVariableOutline, + set_conversation_response: mdiBullhorn, } as const; export const YAML_ONLY_ACTION_TYPES = new Set([ @@ -68,6 +70,7 @@ export const ACTION_GROUPS: AutomationElementGroup = { members: { event: {}, service: {}, + set_conversation_response: {}, }, }, } as const; diff --git a/src/data/area_registry.ts b/src/data/area_registry.ts index d51f7c528f..fcdaaf1bc1 100644 --- a/src/data/area_registry.ts +++ b/src/data/area_registry.ts @@ -1,15 +1,15 @@ -import { Connection, createCollection } from "home-assistant-js-websocket"; -import { Store } from "home-assistant-js-websocket/dist/store"; import { stringCompare } from "../common/string/compare"; -import { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; import { DeviceRegistryEntry } from "./device_registry"; import { EntityRegistryEntry } from "./entity_registry"; +export { subscribeAreaRegistry } from "./ws-area_registry"; + export interface AreaRegistryEntry { area_id: string; name: string; picture: string | null; + icon: string | null; aliases: string[]; } @@ -24,6 +24,7 @@ export interface AreaDeviceLookup { export interface AreaRegistryEntryMutableParams { name: string; picture?: string | null; + icon?: string | null; aliases?: string[]; } @@ -53,45 +54,6 @@ export const deleteAreaRegistryEntry = (hass: HomeAssistant, areaId: string) => area_id: areaId, }); -const fetchAreaRegistry = (conn: Connection) => - conn - .sendMessagePromise({ - type: "config/area_registry/list", - }) - .then((areas) => - (areas as AreaRegistryEntry[]).sort((ent1, ent2) => - stringCompare(ent1.name, ent2.name) - ) - ); - -const subscribeAreaRegistryUpdates = ( - conn: Connection, - store: Store -) => - conn.subscribeEvents( - debounce( - () => - fetchAreaRegistry(conn).then((areas: AreaRegistryEntry[]) => - store.setState(areas, true) - ), - 500, - true - ), - "area_registry_updated" - ); - -export const subscribeAreaRegistry = ( - conn: Connection, - onChange: (areas: AreaRegistryEntry[]) => void -) => - createCollection( - "_areaRegistry", - fetchAreaRegistry, - subscribeAreaRegistryUpdates, - conn, - onChange - ); - export const getAreaEntityLookup = ( entities: EntityRegistryEntry[] ): AreaEntityLookup => { diff --git a/src/data/assist_pipeline.ts b/src/data/assist_pipeline.ts index 3ee5a8a27a..e5f60c2d9c 100644 --- a/src/data/assist_pipeline.ts +++ b/src/data/assist_pipeline.ts @@ -18,6 +18,11 @@ export interface AssistPipeline { wake_word_id: string | null; } +export interface AssistDevice { + device_id: string; + pipeline_entity: string; +} + export interface AssistPipelineMutableParams { name: string; language: string; @@ -366,3 +371,8 @@ export const fetchAssistPipelineLanguages = (hass: HomeAssistant) => hass.callWS<{ languages: string[] }>({ type: "assist_pipeline/language/list", }); + +export const listAssistDevices = (hass: HomeAssistant) => + hass.callWS({ + type: "assist_pipeline/device/list", + }); diff --git a/src/data/auth.ts b/src/data/auth.ts index 95389e6fd9..29095dcf13 100644 --- a/src/data/auth.ts +++ b/src/data/auth.ts @@ -1,5 +1,6 @@ import { HaFormSchema } from "../components/ha-form/types"; import { HomeAssistant } from "../types"; +import { RefreshTokenType } from "./refresh_token"; export interface AuthUrlSearchParams { client_id?: string; @@ -137,7 +138,13 @@ export const adminChangePassword = ( password, }); -export const deleteAllRefreshTokens = (hass: HomeAssistant) => +export const deleteAllRefreshTokens = ( + hass: HomeAssistant, + token_type?: RefreshTokenType, + delete_current_token?: boolean +) => hass.callWS({ type: "auth/delete_all_refresh_tokens", + token_type, + delete_current_token, }); diff --git a/src/data/automation.ts b/src/data/automation.ts index 1527822e71..31fd6d6e61 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -74,8 +74,8 @@ export interface StateTrigger extends BaseTrigger { platform: "state"; entity_id: string | string[]; attribute?: string; - from?: string | number; - to?: string | string[] | number; + from?: string | string[]; + to?: string | string[]; for?: string | number | ForDict; } diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index aec2965cdf..68fe6c24e2 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -64,23 +64,6 @@ const localizeTimeString = ( } }; -const ordinalSuffix = (n: number) => { - n %= 100; - if ([11, 12, 13].includes(n)) { - return "th"; - } - if (n % 10 === 1) { - return "st"; - } - if (n % 10 === 2) { - return "nd"; - } - if (n % 10 === 3) { - return "rd"; - } - return "th"; -}; - export const describeTrigger = ( trigger: Trigger, hass: HomeAssistant, @@ -216,57 +199,46 @@ const tryDescribeTrigger = ( // State Trigger if (trigger.platform === "state") { - let base = "When"; const entities: string[] = []; const states = hass.states; + let attribute = ""; if (trigger.attribute) { const stateObj = Array.isArray(trigger.entity_id) ? hass.states[trigger.entity_id[0]] : hass.states[trigger.entity_id]; - base += ` ${computeAttributeNameDisplay( + attribute = computeAttributeNameDisplay( hass.localize, stateObj, hass.entities, trigger.attribute - )} of`; + ); } - if (Array.isArray(trigger.entity_id)) { - for (const entity of trigger.entity_id.values()) { + const entityArray: string[] = ensureArray(trigger.entity_id); + if (entityArray) { + for (const entity of entityArray) { if (states[entity]) { entities.push(computeStateName(states[entity]) || entity); } } - } else if (trigger.entity_id) { - entities.push( - states[trigger.entity_id] - ? computeStateName(states[trigger.entity_id]) - : trigger.entity_id - ); } - if (entities.length === 0) { - // no entity_id or empty array - entities.push("something"); - } + const stateObj = hass.states[entityArray[0]]; - base += ` ${entities} changes`; - - const stateObj = - hass.states[ - Array.isArray(trigger.entity_id) - ? trigger.entity_id[0] - : trigger.entity_id - ]; + let fromChoice = "other"; + let fromString = ""; if (trigger.from !== undefined) { + let fromArray: string[] = []; if (trigger.from === null) { if (!trigger.attribute) { - base += " from any state"; + fromChoice = "null"; } - } else if (Array.isArray(trigger.from)) { + } else { + fromArray = ensureArray(trigger.from); + const from: string[] = []; - for (const state of trigger.from.values()) { + for (const state of fromArray) { from.push( trigger.attribute ? hass @@ -280,34 +252,25 @@ const tryDescribeTrigger = ( ); } if (from.length !== 0) { - const fromString = formatListWithOrs(hass.locale, from); - base += ` from ${fromString}`; + fromString = formatListWithOrs(hass.locale, from); + fromChoice = "fromUsed"; } - } else { - base += ` from ${ - trigger.attribute - ? hass - .formatEntityAttributeValue( - stateObj, - trigger.attribute, - trigger.from - ) - .toString() - : hass - .formatEntityState(stateObj, trigger.from.toString()) - .toString() - }`; } } + let toChoice = "other"; + let toString = ""; if (trigger.to !== undefined) { + let toArray: string[] = []; if (trigger.to === null) { if (!trigger.attribute) { - base += " to any state"; + toChoice = "null"; } - } else if (Array.isArray(trigger.to)) { + } else { + toArray = ensureArray(trigger.to); + const to: string[] = []; - for (const state of trigger.to.values()) { + for (const state of toArray) { to.push( trigger.attribute ? hass @@ -321,21 +284,9 @@ const tryDescribeTrigger = ( ); } if (to.length !== 0) { - const toString = formatListWithOrs(hass.locale, to); - base += ` to ${toString}`; + toString = formatListWithOrs(hass.locale, to); + toChoice = "toUsed"; } - } else { - base += ` to ${ - trigger.attribute - ? hass - .formatEntityAttributeValue( - stateObj, - trigger.attribute, - trigger.to - ) - .toString() - : hass.formatEntityState(stateObj, trigger.to.toString()) - }`; } } @@ -344,17 +295,29 @@ const tryDescribeTrigger = ( trigger.from === undefined && trigger.to === undefined ) { - base += " state or any attributes"; + toChoice = "special"; } + let duration = ""; if (trigger.for) { - const duration = describeDuration(hass.locale, trigger.for); - if (duration) { - base += ` for ${duration}`; - } + duration = describeDuration(hass.locale, trigger.for) ?? ""; } - return base; + return hass.localize( + `${triggerTranslationBaseKey}.state.description.full`, + { + hasAttribute: attribute !== "" ? "true" : "false", + attribute: attribute, + hasEntity: entities.length !== 0 ? "true" : "false", + entity: formatListWithOrs(hass.locale, entities), + fromChoice: fromChoice, + fromString: fromString, + toChoice: toChoice, + toString: toString, + hasDuration: duration !== "" ? "true" : "false", + duration: duration, + } + ); } // Sun Trigger @@ -401,14 +364,37 @@ const tryDescribeTrigger = ( // Time Pattern Trigger if (trigger.platform === "time_pattern") { if (!trigger.seconds && !trigger.minutes && !trigger.hours) { - return "When a time pattern matches"; + return hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.initial` + ); } - let result = "Trigger "; + + const invalidParts: Array<"seconds" | "minutes" | "hours"> = []; + + let secondsChoice: "every" | "every_interval" | "on_the_xth" | "other" = + "other"; + let minutesChoice: + | "every" + | "every_interval" + | "on_the_xth" + | "other" + | "has_seconds" = "other"; + let hoursChoice: + | "every" + | "every_interval" + | "on_the_xth" + | "other" + | "has_seconds_or_minutes" = "other"; + + let seconds = 0; + let minutes = 0; + let hours = 0; + if (trigger.seconds !== undefined) { const seconds_all = trigger.seconds === "*"; const seconds_interval = typeof trigger.seconds === "string" && trigger.seconds.startsWith("/"); - const seconds = seconds_all + seconds = seconds_all ? 0 : typeof trigger.seconds === "number" ? trigger.seconds @@ -422,22 +408,22 @@ const tryDescribeTrigger = ( seconds < 0 || (seconds_interval && seconds === 0) ) { - return "Invalid Time Pattern Seconds"; + invalidParts.push("seconds"); } if (seconds_all || (seconds_interval && seconds === 1)) { - result += "every second of "; + secondsChoice = "every"; } else if (seconds_interval) { - result += `every ${seconds} seconds of `; + secondsChoice = "every_interval"; } else { - result += `on the ${seconds}${ordinalSuffix(seconds)} second of `; + secondsChoice = "on_the_xth"; } } if (trigger.minutes !== undefined) { const minutes_all = trigger.minutes === "*"; const minutes_interval = typeof trigger.minutes === "string" && trigger.minutes.startsWith("/"); - const minutes = minutes_all + minutes = minutes_all ? 0 : typeof trigger.minutes === "number" ? trigger.minutes @@ -451,30 +437,30 @@ const tryDescribeTrigger = ( minutes < 0 || (minutes_interval && minutes === 0) ) { - return "Invalid Time Pattern Minutes"; + invalidParts.push("minutes"); } if (minutes_all || (minutes_interval && minutes === 1)) { - result += "every minute of "; + minutesChoice = "every"; } else if (minutes_interval) { - result += `every ${minutes} minutes of `; + minutesChoice = "every_interval"; } else { - result += `${ - trigger.seconds !== undefined ? "" : "on" - } the ${minutes}${ordinalSuffix(minutes)} minute of `; + minutesChoice = + trigger.seconds !== undefined ? "has_seconds" : "on_the_xth"; } } else if (trigger.seconds !== undefined) { if (trigger.hours !== undefined) { - result += `the 0${ordinalSuffix(0)} minute of `; + minutes = 0; + minutesChoice = "has_seconds"; } else { - result += "every minute of "; + minutesChoice = "every"; } } if (trigger.hours !== undefined) { const hours_all = trigger.hours === "*"; const hours_interval = typeof trigger.hours === "string" && trigger.hours.startsWith("/"); - const hours = hours_all + hours = hours_all ? 0 : typeof trigger.hours === "number" ? trigger.hours @@ -488,24 +474,68 @@ const tryDescribeTrigger = ( hours < 0 || (hours_interval && hours === 0) ) { - return "Invalid Time Pattern Hours"; + invalidParts.push("hours"); } if (hours_all || (hours_interval && hours === 1)) { - result += "every hour"; + hoursChoice = "every"; } else if (hours_interval) { - result += `every ${hours} hours`; + hoursChoice = "every_interval"; } else { - result += `${ + hoursChoice = trigger.seconds !== undefined || trigger.minutes !== undefined - ? "" - : "on" - } the ${hours}${ordinalSuffix(hours)} hour`; + ? "has_seconds_or_minutes" + : "on_the_xth"; } } else { - result += "every hour"; + hoursChoice = "every"; } - return result; + + if (invalidParts.length !== 0) { + return hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.invalid`, + { + parts: formatListWithAnds( + hass.locale, + invalidParts.map((invalidPart) => + hass.localize( + `${triggerTranslationBaseKey}.time_pattern.${invalidPart}` + ) + ) + ), + } + ); + } + + return hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.full`, + { + secondsChoice: secondsChoice, + minutesChoice: minutesChoice, + hoursChoice: hoursChoice, + seconds: seconds, + minutes: minutes, + hours: hours, + secondsWithOrdinal: hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.ordinal`, + { + part: seconds, + } + ), + minutesWithOrdinal: hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.ordinal`, + { + part: minutes, + } + ), + hoursWithOrdinal: hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.ordinal`, + { + part: hours, + } + ), + } + ); } // Zone Trigger @@ -966,60 +996,40 @@ const tryDescribeCondition = ( } // Sun condition - if ( - condition.condition === "sun" && - ("before" in condition || "after" in condition) - ) { - let base = "Confirm"; - - if (!condition.after && !condition.before) { - base += " sun"; - return base; - } - - base += " sun"; - - if (condition.after) { - let after_duration = ""; - - if (condition.after_offset) { - if (typeof condition.after_offset === "number") { - after_duration = ` offset by ${secondsToDuration( - condition.after_offset - )!}`; - } else if (typeof condition.after_offset === "string") { - after_duration = ` offset by ${condition.after_offset}`; - } else { - after_duration = ` offset by ${JSON.stringify( - condition.after_offset - )}`; - } + if (condition.condition === "sun" && (condition.before || condition.after)) { + let afterDuration = ""; + if (condition.after && condition.after_offset) { + if (typeof condition.after_offset === "number") { + afterDuration = secondsToDuration(condition.after_offset)!; + } else if (typeof condition.after_offset === "string") { + afterDuration = condition.after_offset; + } else { + afterDuration = JSON.stringify(condition.after_offset); } - - base += ` after ${condition.after}${after_duration}`; } - if (condition.before) { - let before_duration = ""; - - if (condition.before_offset) { - if (typeof condition.before_offset === "number") { - before_duration = ` offset by ${secondsToDuration( - condition.before_offset - )!}`; - } else if (typeof condition.before_offset === "string") { - before_duration = ` offset by ${condition.before_offset}`; - } else { - before_duration = ` offset by ${JSON.stringify( - condition.before_offset - )}`; - } + let beforeDuration = ""; + if (condition.before && condition.before_offset) { + if (typeof condition.before_offset === "number") { + beforeDuration = secondsToDuration(condition.before_offset)!; + } else if (typeof condition.before_offset === "string") { + beforeDuration = condition.before_offset; + } else { + beforeDuration = JSON.stringify(condition.before_offset); } - - base += ` before ${condition.before}${before_duration}`; } - return base; + return hass.localize( + `${conditionsTranslationBaseKey}.sun.description.full`, + { + afterChoice: condition.after ?? "other", + afterOffsetChoice: afterDuration !== "" ? "offset" : "other", + afterOffset: afterDuration, + beforeChoice: condition.before ?? "other", + beforeOffsetChoice: beforeDuration !== "" ? "offset" : "other", + beforeOffset: beforeDuration, + } + ); } // Zone condition diff --git a/src/data/climate.ts b/src/data/climate.ts index b3b7087a3b..97a2557c71 100644 --- a/src/data/climate.ts +++ b/src/data/climate.ts @@ -1,38 +1,17 @@ import { - mdiAccountArrowRight, - mdiArrowAll, - mdiArrowLeftRight, - mdiArrowUpDown, - mdiBed, - mdiCircleMedium, - mdiClockOutline, mdiFan, - mdiFanAuto, - mdiFanOff, mdiFire, - mdiHeatWave, - mdiHome, - mdiLeaf, - mdiMotionSensor, mdiPower, - mdiRocketLaunch, mdiSnowflake, - mdiSofa, - mdiSpeedometer, - mdiSpeedometerMedium, - mdiSpeedometerSlow, mdiSunSnowflakeVariant, - mdiTarget, + mdiThermostat, mdiThermostatAuto, mdiWaterPercent, - mdiWeatherWindy, } from "@mdi/js"; import { HassEntityAttributeBase, HassEntityBase, } from "home-assistant-js-websocket"; -import { haOscillatingOff } from "./icons/haOscillatingOff"; -import { haOscillating } from "./icons/haOscillating"; export const HVAC_MODES = [ "auto", @@ -93,6 +72,8 @@ export const enum ClimateEntityFeature { PRESET_MODE = 16, SWING_MODE = 32, AUX_HEAT = 64, + TURN_OFF = 128, + TURN_ON = 256, } const hvacModeOrdering = HVAC_MODES.reduce( @@ -116,16 +97,6 @@ export const CLIMATE_HVAC_ACTION_TO_MODE: Record = { off: "off", }; -export const CLIMATE_HVAC_ACTION_ICONS: Record = { - cooling: mdiSnowflake, - drying: mdiWaterPercent, - fan: mdiFan, - heating: mdiFire, - idle: mdiClockOutline, - off: mdiPower, - preheating: mdiHeatWave, -}; - export const CLIMATE_HVAC_MODE_ICONS: Record = { cool: mdiSnowflake, dry: mdiWaterPercent, @@ -136,81 +107,5 @@ export const CLIMATE_HVAC_MODE_ICONS: Record = { heat_cool: mdiSunSnowflakeVariant, }; -export const computeHvacModeIcon = (mode: HvacMode) => - CLIMATE_HVAC_MODE_ICONS[mode]; - -type ClimateBuiltInPresetMode = - | "eco" - | "away" - | "boost" - | "comfort" - | "home" - | "sleep" - | "activity"; - -export const CLIMATE_PRESET_MODE_ICONS: Record< - ClimateBuiltInPresetMode, - string -> = { - away: mdiAccountArrowRight, - boost: mdiRocketLaunch, - comfort: mdiSofa, - eco: mdiLeaf, - home: mdiHome, - sleep: mdiBed, - activity: mdiMotionSensor, -}; - -export const computePresetModeIcon = (mode: string) => - mode in CLIMATE_PRESET_MODE_ICONS - ? CLIMATE_PRESET_MODE_ICONS[mode] - : mdiCircleMedium; - -type ClimateBuiltInFanMode = - | "on" - | "off" - | "auto" - | "low" - | "medium" - | "high" - | "middle" - | "focus" - | "diffuse"; - -export const CLIMATE_FAN_MODE_ICONS: Record = { - on: mdiFan, - off: mdiFanOff, - auto: mdiFanAuto, - low: mdiSpeedometerSlow, - medium: mdiSpeedometerMedium, - high: mdiSpeedometer, - middle: mdiSpeedometerMedium, - focus: mdiTarget, - diffuse: mdiWeatherWindy, -}; - -export const computeFanModeIcon = (mode: string) => - mode in CLIMATE_FAN_MODE_ICONS - ? CLIMATE_FAN_MODE_ICONS[mode] - : mdiCircleMedium; - -type ClimateBuiltInSwingMode = - | "off" - | "on" - | "vertical" - | "horizontal" - | "both"; - -export const CLIMATE_SWING_MODE_ICONS: Record = - { - on: haOscillating, - off: haOscillatingOff, - vertical: mdiArrowUpDown, - horizontal: mdiArrowLeftRight, - both: mdiArrowAll, - }; - -export const computeSwingModeIcon = (mode: string) => - mode in CLIMATE_SWING_MODE_ICONS - ? CLIMATE_SWING_MODE_ICONS[mode] - : mdiCircleMedium; +export const climateHvacModeIcon = (mode: string) => + CLIMATE_HVAC_MODE_ICONS[mode] || mdiThermostat; diff --git a/src/data/context.ts b/src/data/context.ts index 7fbff9fd06..b5d914522c 100644 --- a/src/data/context.ts +++ b/src/data/context.ts @@ -3,6 +3,8 @@ import { HassConfig } from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; import { EntityRegistryEntry } from "./entity_registry"; +export const connectionContext = + createContext("connection"); export const statesContext = createContext("states"); export const entitiesContext = createContext("entities"); diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 4e2d308e0e..0127bdf7d3 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -1,8 +1,5 @@ -import { Connection, createCollection } from "home-assistant-js-websocket"; -import type { Store } from "home-assistant-js-websocket/dist/store"; import { computeStateName } from "../common/entity/compute_state_name"; import { caseInsensitiveStringCompare } from "../common/string/compare"; -import { debounce } from "../common/util/debounce"; import type { HomeAssistant } from "../types"; import type { EntityRegistryDisplayEntry, @@ -10,6 +7,11 @@ import type { } from "./entity_registry"; import type { EntitySources } from "./entity_sources"; +export { + fetchDeviceRegistry, + subscribeDeviceRegistry, +} from "./ws-device_registry"; + export interface DeviceRegistryEntry { id: string; config_entries: string[]; @@ -96,39 +98,6 @@ export const removeConfigEntryFromDevice = ( config_entry_id: configEntryId, }); -export const fetchDeviceRegistry = (conn: Connection) => - conn.sendMessagePromise({ - type: "config/device_registry/list", - }); - -const subscribeDeviceRegistryUpdates = ( - conn: Connection, - store: Store -) => - conn.subscribeEvents( - debounce( - () => - fetchDeviceRegistry(conn).then((devices) => - store.setState(devices, true) - ), - 500, - true - ), - "device_registry_updated" - ); - -export const subscribeDeviceRegistry = ( - conn: Connection, - onChange: (devices: DeviceRegistryEntry[]) => void -) => - createCollection( - "_dr", - fetchDeviceRegistry, - subscribeDeviceRegistryUpdates, - conn, - onChange - ); - export const sortDeviceRegistryByName = ( entries: DeviceRegistryEntry[], language: string diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index c55a09aaa3..5b015d9adb 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -8,11 +8,14 @@ import { HomeAssistant } from "../types"; import { LightColor } from "./light"; import { computeDomain } from "../common/entity/compute_domain"; +export { subscribeEntityRegistryDisplay } from "./ws-entity_registry_display"; + type entityCategory = "config" | "diagnostic"; export interface EntityRegistryDisplayEntry { entity_id: string; name?: string; + icon?: string; device_id?: string; area_id?: string; hidden?: boolean; @@ -22,13 +25,14 @@ export interface EntityRegistryDisplayEntry { display_precision?: number; } -interface EntityRegistryDisplayEntryResponse { +export interface EntityRegistryDisplayEntryResponse { entities: { ei: string; di?: string; ai?: string; ec?: number; en?: string; + ic?: string; pl?: string; tk?: string; hb?: boolean; @@ -98,6 +102,7 @@ export interface WeatherEntityOptions { export interface SwitchAsXEntityOptions { entity_id: string; + invert: boolean; } export interface EntityRegistryOptions { @@ -255,34 +260,6 @@ export const subscribeEntityRegistry = ( onChange ); -const subscribeEntityRegistryDisplayUpdates = ( - conn: Connection, - store: Store -) => - conn.subscribeEvents( - debounce( - () => - fetchEntityRegistryDisplay(conn).then((entities) => - store.setState(entities, true) - ), - 500, - true - ), - "entity_registry_updated" - ); - -export const subscribeEntityRegistryDisplay = ( - conn: Connection, - onChange: (entities: EntityRegistryDisplayEntryResponse) => void -) => - createCollection( - "_entityRegistryDisplay", - fetchEntityRegistryDisplay, - subscribeEntityRegistryDisplayUpdates, - conn, - onChange - ); - export const sortEntityRegistryByName = ( entries: EntityRegistryEntry[], language: string diff --git a/src/data/history.ts b/src/data/history.ts index a110c8c634..baece3da37 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -470,9 +470,15 @@ export const computeHistory = ( }[domain]; } - const deviceClass: string | undefined = ( - currentState?.attributes || numericStateFromHistory?.a - )?.device_class; + const specialDomainClasses = { + climate: "temperature", + humidifier: "humidity", + water_heater: "temperature", + }; + + const deviceClass: string | undefined = + specialDomainClasses[domain] || + (currentState?.attributes || numericStateFromHistory?.a)?.device_class; const key = computeGroupKey(unit, deviceClass, splitDeviceClasses); diff --git a/src/data/humidifier.ts b/src/data/humidifier.ts index bd94bf59d0..b0598ea798 100644 --- a/src/data/humidifier.ts +++ b/src/data/humidifier.ts @@ -1,19 +1,3 @@ -import { - mdiAccountArrowRight, - mdiArrowDownBold, - mdiArrowUpBold, - mdiBabyCarriage, - mdiCircleMedium, - mdiClockOutline, - mdiHome, - mdiLeaf, - mdiPower, - mdiPowerSleep, - mdiRefreshAuto, - mdiRocketLaunch, - mdiSofa, - mdiWaterPercent, -} from "@mdi/js"; import { HassEntityAttributeBase, HassEntityBase, @@ -44,39 +28,6 @@ export const enum HumidifierEntityDeviceClass { DEHUMIDIFIER = "dehumidifier", } -type HumidifierBuiltInMode = - | "normal" - | "eco" - | "away" - | "boost" - | "comfort" - | "home" - | "sleep" - | "auto" - | "baby"; - -export const HUMIDIFIER_MODE_ICONS: Record = { - auto: mdiRefreshAuto, - away: mdiAccountArrowRight, - baby: mdiBabyCarriage, - boost: mdiRocketLaunch, - comfort: mdiSofa, - eco: mdiLeaf, - home: mdiHome, - normal: mdiWaterPercent, - sleep: mdiPowerSleep, -}; - -export const computeHumidiferModeIcon = (mode?: string) => - HUMIDIFIER_MODE_ICONS[mode as HumidifierBuiltInMode] ?? mdiCircleMedium; - -export const HUMIDIFIER_ACTION_ICONS: Record = { - drying: mdiArrowDownBold, - humidifying: mdiArrowUpBold, - idle: mdiClockOutline, - off: mdiPower, -}; - export const HUMIDIFIER_ACTION_MODE: Record = { drying: "on", diff --git a/src/data/icons.ts b/src/data/icons.ts new file mode 100644 index 0000000000..fc7452964e --- /dev/null +++ b/src/data/icons.ts @@ -0,0 +1,262 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { computeDomain } from "../common/entity/compute_domain"; +import { computeObjectId } from "../common/entity/compute_object_id"; +import { computeStateDomain } from "../common/entity/compute_state_domain"; +import { stateIcon } from "../common/entity/state_icon"; +import { HomeAssistant } from "../types"; +import { + EntityRegistryDisplayEntry, + EntityRegistryEntry, +} from "./entity_registry"; + +const resources: Record = { + entity: {}, + entity_component: undefined, + services: {}, +}; + +interface IconResources { + resources: Record>; +} + +interface PlatformIcons { + [domain: string]: { + [translation_key: string]: { + state: Record; + state_attributes: Record< + string, + { + state: Record; + default: string; + } + >; + default: string; + }; + }; +} + +interface ComponentIcons { + [device_class: string]: { + state: Record; + state_attributes: Record< + string, + { + state: Record; + default: string; + } + >; + default: string; + }; +} + +interface ServiceIcons { + [service: string]: string; +} + +export type IconCategory = "entity" | "entity_component" | "services"; + +export const getHassIcons = async ( + hass: HomeAssistant, + category: IconCategory, + integration?: string +): Promise => + hass.callWS<{ resources: Record }>({ + type: "frontend/get_icons", + category, + integration, + }); + +export const getPlatformIcons = async ( + hass: HomeAssistant, + integration: string, + force = false +): Promise => { + if (!force && integration in resources.entity) { + return resources.entity[integration]; + } + const result = getHassIcons(hass, "entity", integration); + resources.entity[integration] = result.then( + (res) => res?.resources[integration] + ); + return resources.entity[integration]; +}; + +export const getComponentIcons = async ( + hass: HomeAssistant, + domain: string, + force = false +): Promise => { + if (!force && resources.entity_component) { + return resources.entity_component.then((res) => res[domain]); + } + resources.entity_component = getHassIcons(hass, "entity_component").then( + (result) => result.resources + ); + return resources.entity_component.then((res) => res[domain]); +}; + +export const getServiceIcons = async ( + hass: HomeAssistant, + domain?: string, + force = false +): Promise => { + if (!domain) { + if (!force && resources.services.all) { + return resources.services.all; + } + resources.services.all = getHassIcons(hass, "services", domain).then( + (res) => { + resources.services = res.resources; + return res?.resources; + } + ); + return resources.services.all; + } + if (!force && domain && domain in resources.services) { + return resources.services[domain]; + } + if (resources.services.all && !force) { + await resources.services.all; + if (domain in resources.services) { + return resources.services[domain]; + } + } + const result = getHassIcons(hass, "services", domain); + resources.services[domain] = result.then((res) => res?.resources[domain]); + return resources.services[domain]; +}; + +export const entityIcon = async ( + hass: HomeAssistant, + stateObj: HassEntity, + state?: string +) => { + const entry = hass.entities?.[stateObj.entity_id] as + | EntityRegistryDisplayEntry + | undefined; + if (entry?.icon) { + return entry.icon; + } + const domain = computeStateDomain(stateObj); + + return getEntityIcon(hass, domain, stateObj, state, entry); +}; + +export const entryIcon = async ( + hass: HomeAssistant, + entry: EntityRegistryEntry | EntityRegistryDisplayEntry +) => { + if (entry.icon) { + return entry.icon; + } + const domain = computeDomain(entry.entity_id); + return getEntityIcon(hass, domain, undefined, undefined, entry); +}; + +const getEntityIcon = async ( + hass: HomeAssistant, + domain: string, + stateObj?: HassEntity, + stateValue?: string, + entry?: EntityRegistryEntry | EntityRegistryDisplayEntry +) => { + const platform = entry?.platform; + const translation_key = entry?.translation_key; + const device_class = stateObj?.attributes.device_class; + const state = stateValue ?? stateObj?.state; + + let icon: string | undefined; + if (translation_key && platform) { + const platformIcons = await getPlatformIcons(hass, platform); + if (platformIcons) { + const translations = platformIcons[domain]?.[translation_key]; + icon = (state && translations?.state?.[state]) || translations?.default; + } + } + + if (!icon && stateObj) { + icon = stateIcon(stateObj, state); + } + + if (!icon) { + const entityComponentIcons = await getComponentIcons(hass, domain); + if (entityComponentIcons) { + const translations = + (device_class && entityComponentIcons[device_class]) || + entityComponentIcons._; + icon = (state && translations?.state?.[state]) || translations?.default; + } + } + return icon; +}; + +export const attributeIcon = async ( + hass: HomeAssistant, + state: HassEntity, + attribute: string, + attributeValue?: string +) => { + let icon: string | undefined; + const domain = computeStateDomain(state); + const deviceClass = state.attributes.device_class; + const entity = hass.entities?.[state.entity_id] as + | EntityRegistryDisplayEntry + | undefined; + const platform = entity?.platform; + const translation_key = entity?.translation_key; + const value = + attributeValue ?? + (state.attributes[attribute] as string | number | undefined); + + if (translation_key && platform) { + const platformIcons = await getPlatformIcons(hass, platform); + if (platformIcons) { + const translations = + platformIcons[domain]?.[translation_key]?.state_attributes?.[attribute]; + icon = (value && translations?.state?.[value]) || translations?.default; + } + } + if (!icon) { + const entityComponentIcons = await getComponentIcons(hass, domain); + if (entityComponentIcons) { + const translations = + (deviceClass && + entityComponentIcons[deviceClass]?.state_attributes?.[attribute]) || + entityComponentIcons._?.state_attributes?.[attribute]; + icon = (value && translations?.state?.[value]) || translations?.default; + } + } + return icon; +}; + +export const serviceIcon = async ( + hass: HomeAssistant, + service: string +): Promise => { + let icon: string | undefined; + const domain = computeDomain(service); + const serviceName = computeObjectId(service); + const serviceIcons = await getServiceIcons(hass, domain); + if (serviceIcons) { + icon = serviceIcons[serviceName]; + } + if (!icon) { + icon = await domainIcon(hass, domain); + } + return icon; +}; + +export const domainIcon = async ( + hass: HomeAssistant, + domain: string, + deviceClass?: string +): Promise => { + const entityComponentIcons = await getComponentIcons(hass, domain); + if (entityComponentIcons) { + const translations = + (deviceClass && entityComponentIcons[deviceClass]) || + entityComponentIcons._; + return translations?.default; + } + return undefined; +}; diff --git a/src/data/icons/haOscillating.ts b/src/data/icons/haOscillating.ts deleted file mode 100644 index a3afdb3b3c..0000000000 --- a/src/data/icons/haOscillating.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const haOscillating = - "M12,6C7.963,6 6,9.715 6,12L9,12L5,16L1,12L4,12C4,9.17 5.897,4 12.004,4C18.112,4 20.004,9.17 20.004,12L23.004,12L19.004,16L15.004,12L18.004,12C18.004,9.715 16.037,6 12,6Z"; diff --git a/src/data/icons/haOscillatingOff.ts b/src/data/icons/haOscillatingOff.ts deleted file mode 100644 index 285f6c5c39..0000000000 --- a/src/data/icons/haOscillatingOff.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const haOscillatingOff = - "M 2.9003906,0.8203125 1.6289062,2.0917969 6.0566406,6.5195312 C 4.5643882,8.2344127 4,10.461887 4,12 H 1 l 4,4 4,-4 H 6 C 6,10.82424 6.5229215,9.2725296 7.578125,8.0410156 L 19.730469,20.193359 21.003906,18.921875 8.9394531,6.8574219 7.3964844,5.3164062 Z M 12.003906,4 C 10.899236,4 9.9346562,4.1709695 9.0917969,4.4648438 L 10.755859,6.1289062 C 11.146838,6.0472549 11.559859,6 12,6 c 4.036992,0 6.003906,3.7150046 6.003906,6 h -1.376953 l 3.189453,3.1875 3.1875,-3.1875 h -3 c 0,-2.8299944 -1.892012,-8 -8,-8 z"; diff --git a/src/data/matter.ts b/src/data/matter.ts index 11686a7cb2..5b4009178d 100644 --- a/src/data/matter.ts +++ b/src/data/matter.ts @@ -3,6 +3,50 @@ import { navigate } from "../common/navigate"; import { HomeAssistant } from "../types"; import { subscribeDeviceRegistry } from "./device_registry"; +export enum NetworkType { + THREAD = "thread", + WIFI = "wifi", + ETHERNET = "ethernet", + UNKNOWN = "unknown", +} + +export enum NodeType { + END_DEVICE = "end_device", + SLEEPY_END_DEVICE = "sleepy_end_device", + ROUTING_END_DEVICE = "routing_end_device", + BRIDGE = "bridge", + UNKNOWN = "unknown", +} + +export interface MatterFabricData { + fabric_id: number; + vendor_id: number; + fabric_index: number; + fabric_label?: string; + vendor_name?: string; +} + +export interface MatterNodeDiagnostics { + node_id: number; + network_type: NetworkType; + node_type: NodeType; + network_name?: string; + ip_adresses: string[]; + mac_address?: string; + available: boolean; + active_fabrics: MatterFabricData[]; +} + +export interface MatterPingResult { + [ip_address: string]: boolean; +} + +export interface MatterCommissioningParameters { + setup_pin_code: number; + setup_manual_code: string; + setup_qr_code: string; +} + export const canCommissionMatterExternal = (hass: HomeAssistant) => hass.auth.external?.config.canCommissionMatter; @@ -86,3 +130,50 @@ export const matterSetThread = ( type: "matter/set_thread", thread_operation_dataset, }); + +export const getMatterNodeDiagnostics = ( + hass: HomeAssistant, + device_id: string +): Promise => + hass.callWS({ + type: "matter/node_diagnostics", + device_id, + }); + +export const pingMatterNode = ( + hass: HomeAssistant, + device_id: string +): Promise => + hass.callWS({ + type: "matter/ping_node", + device_id, + }); + +export const openMatterCommissioningWindow = ( + hass: HomeAssistant, + device_id: string +): Promise => + hass.callWS({ + type: "matter/open_commissioning_window", + device_id, + }); + +export const removeMatterFabric = ( + hass: HomeAssistant, + device_id: string, + fabric_index: number +): Promise => + hass.callWS({ + type: "matter/remove_matter_fabric", + device_id, + fabric_index, + }); + +export const interviewMatterNode = ( + hass: HomeAssistant, + device_id: string +): Promise => + hass.callWS({ + type: "matter/interview_node", + device_id, + }); diff --git a/src/data/onboarding.ts b/src/data/onboarding.ts index 9e5c751ada..170bbadee8 100644 --- a/src/data/onboarding.ts +++ b/src/data/onboarding.ts @@ -84,5 +84,9 @@ export const fetchInstallationType = async (): Promise => { throw Error("unauthorized"); } + if (response.status === 404) { + throw Error("not_found"); + } + return response.json(); }; diff --git a/src/data/person.ts b/src/data/person.ts index 122240aff9..d1a0cbdd6a 100644 --- a/src/data/person.ts +++ b/src/data/person.ts @@ -24,16 +24,6 @@ export const fetchPersons = (hass: HomeAssistant) => config: Person[]; }>({ type: "person/list" }); -export const listUserPersons = (): Promise> => - fetch("/api/person/list", { - credentials: "same-origin", - }).then((resp) => { - if (resp.ok) { - return resp.json(); - } - throw new Error(resp.statusText); - }); - export const createPerson = ( hass: HomeAssistant, values: PersonMutableParams diff --git a/src/data/refresh_token.ts b/src/data/refresh_token.ts index bc2752bdf0..e5cbfef53b 100644 --- a/src/data/refresh_token.ts +++ b/src/data/refresh_token.ts @@ -4,6 +4,8 @@ declare global { } } +export type RefreshTokenType = "normal" | "long_lived_access_token"; + export interface RefreshToken { client_icon?: string; client_id: string; @@ -13,5 +15,5 @@ export interface RefreshToken { is_current: boolean; last_used_at?: string; last_used_ip?: string; - type: "normal" | "long_lived_access_token"; + type: RefreshTokenType; } diff --git a/src/data/repairs.ts b/src/data/repairs.ts index 92a175f753..f1e0cbb7f8 100644 --- a/src/data/repairs.ts +++ b/src/data/repairs.ts @@ -32,6 +32,13 @@ export const fetchRepairsIssues = (conn: Connection) => type: "repairs/list_issues", }); +export const fetchRepairsIssueData = (conn: Connection, domain, issue_id) => + conn.sendMessagePromise<{ issue_data: { string: any } }>({ + type: "repairs/get_issue_data", + domain, + issue_id, + }); + export const ignoreRepairsIssue = async ( hass: HomeAssistant, issue: RepairsIssue, diff --git a/src/data/script.ts b/src/data/script.ts index 08910dc88f..0f585cf1e0 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -248,6 +248,10 @@ export interface ParallelAction extends BaseAction { parallel: ManualScriptConfig | Action | (ManualScriptConfig | Action)[]; } +export interface SetConversationResponseAction extends BaseAction { + set_conversation_response: string; +} + interface UnknownAction extends BaseAction { [key: string]: unknown; } @@ -292,6 +296,7 @@ export interface ActionTypes { play_media: PlayMediaAction; stop: StopAction; parallel: ParallelAction; + set_conversation_response: SetConversationResponseAction; unknown: UnknownAction; } @@ -383,6 +388,9 @@ export const getActionType = (action: Action): ActionType => { if ("parallel" in action) { return "parallel"; } + if ("set_conversation_response" in action) { + return "set_conversation_response"; + } if ("service" in action) { if ("metadata" in action) { if (is(action, activateSceneActionStruct)) { diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 40c823bc86..22d5e3c0a7 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -27,6 +27,7 @@ import { PlayMediaAction, RepeatAction, SceneAction, + SetConversationResponseAction, StopAction, VariablesAction, WaitForTriggerAction, @@ -443,5 +444,13 @@ const tryDescribeAction = ( ); } + if (actionType === "set_conversation_response") { + const config = action as SetConversationResponseAction; + return hass.localize( + `${actionTranslationBaseKey}.set_conversation_response.description.full`, + { response: config.set_conversation_response } + ); + } + return actionType; }; diff --git a/src/data/selector.ts b/src/data/selector.ts index cddf581020..d4de771854 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -3,7 +3,7 @@ import { ensureArray } from "../common/array/ensure-array"; import { computeStateDomain } from "../common/entity/compute_state_domain"; import { supportsFeature } from "../common/entity/supports-feature"; import { UiAction } from "../panels/lovelace/components/hui-action-editor"; -import { HomeAssistant } from "../types"; +import { HomeAssistant, ItemPath } from "../types"; import { DeviceRegistryEntry, getDeviceIntegrationLookup, @@ -41,6 +41,7 @@ export type Selector = | NumberSelector | ObjectSelector | AssistPipelineSelector + | QRCodeSelector | SelectSelector | SelectorSelector | StateSelector @@ -59,8 +60,7 @@ export type Selector = export interface ActionSelector { action: { - reorder_mode?: boolean; - nested?: boolean; + path?: ItemPath; } | null; } @@ -113,8 +113,7 @@ export interface ColorTempSelector { export interface ConditionSelector { condition: { - reorder_mode?: boolean; - nested?: boolean; + path?: ItemPath; } | null; } @@ -342,6 +341,15 @@ export interface BackupLocationSelector { backup_location: {} | null; } +export interface QRCodeSelector { + qr_code: { + data: string; + scale?: number; + error_correction_level?: "low" | "medium" | "quartile" | "high"; + center_image?: string; + } | null; +} + export interface StringSelector { text: { multiline?: boolean; @@ -392,8 +400,7 @@ export interface TimeSelector { export interface TriggerSelector { trigger: { - reorder_mode?: boolean; - nested?: boolean; + path?: ItemPath; } | null; } diff --git a/src/data/thread.ts b/src/data/thread.ts index cb1c1fa1da..82c19fbe03 100644 --- a/src/data/thread.ts +++ b/src/data/thread.ts @@ -22,6 +22,7 @@ export interface ThreadDataSet { network_name: string; pan_id: string | null; preferred_border_agent_id: string | null; + preferred_extended_address: string | null; preferred: boolean; source: string; } @@ -107,10 +108,12 @@ export const setPreferredThreadDataSet = ( export const setPreferredBorderAgent = ( hass: HomeAssistant, dataset_id: string, - border_agent_id: string + border_agent_id: string | null, + extended_address: string ): Promise => hass.callWS({ - type: "thread/set_preferred_border_agent_id", + type: "thread/set_preferred_border_agent", dataset_id, border_agent_id, + extended_address, }); diff --git a/src/data/time_date.ts b/src/data/time_date.ts new file mode 100644 index 0000000000..5f572cb658 --- /dev/null +++ b/src/data/time_date.ts @@ -0,0 +1,21 @@ +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HomeAssistant } from "../types"; + +export interface TimeDatePreview { + state: string; + attributes: Record; +} + +export const subscribePreviewTimeDate = ( + hass: HomeAssistant, + flow_id: string, + flow_type: "config_flow" | "options_flow", + user_input: Record, + callback: (preview: TimeDatePreview) => void +): Promise => + hass.connection.subscribeMessage(callback, { + type: "time_date/start_preview", + flow_id, + flow_type, + user_input, + }); diff --git a/src/data/weather.ts b/src/data/weather.ts index abded90d6d..8f02b7c5f3 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -8,7 +8,6 @@ import { mdiWeatherLightning, mdiWeatherLightningRainy, mdiWeatherNight, - mdiWeatherNightPartlyCloudy, mdiWeatherPartlyCloudy, mdiWeatherPouring, mdiWeatherRainy, @@ -520,13 +519,6 @@ export const getWeatherStateIcon = ( return undefined; }; -export const weatherIcon = (state?: string, nightTime?: boolean): string => - !state - ? undefined - : nightTime && state === "partlycloudy" - ? mdiWeatherNightPartlyCloudy - : weatherIcons[state]; - const EIGHT_HOURS = 28800000; const DAY_IN_MILLISECONDS = 86400000; diff --git a/src/data/ws-area_registry.ts b/src/data/ws-area_registry.ts new file mode 100644 index 0000000000..43e3d1f13f --- /dev/null +++ b/src/data/ws-area_registry.ts @@ -0,0 +1,37 @@ +import { Connection, createCollection } from "home-assistant-js-websocket"; +import { Store } from "home-assistant-js-websocket/dist/store"; +import { debounce } from "../common/util/debounce"; +import { AreaRegistryEntry } from "./area_registry"; + +const fetchAreaRegistry = (conn: Connection) => + conn.sendMessagePromise({ + type: "config/area_registry/list", + }); + +const subscribeAreaRegistryUpdates = ( + conn: Connection, + store: Store +) => + conn.subscribeEvents( + debounce( + () => + fetchAreaRegistry(conn).then((areas: AreaRegistryEntry[]) => + store.setState(areas, true) + ), + 500, + true + ), + "area_registry_updated" + ); + +export const subscribeAreaRegistry = ( + conn: Connection, + onChange: (areas: AreaRegistryEntry[]) => void +) => + createCollection( + "_areaRegistry", + fetchAreaRegistry, + subscribeAreaRegistryUpdates, + conn, + onChange + ); diff --git a/src/data/ws-device_registry.ts b/src/data/ws-device_registry.ts new file mode 100644 index 0000000000..85cc265681 --- /dev/null +++ b/src/data/ws-device_registry.ts @@ -0,0 +1,37 @@ +import { Connection, createCollection } from "home-assistant-js-websocket"; +import { Store } from "home-assistant-js-websocket/dist/store"; +import { DeviceRegistryEntry } from "./device_registry"; +import { debounce } from "../common/util/debounce"; + +export const fetchDeviceRegistry = (conn: Connection) => + conn.sendMessagePromise({ + type: "config/device_registry/list", + }); + +const subscribeDeviceRegistryUpdates = ( + conn: Connection, + store: Store +) => + conn.subscribeEvents( + debounce( + () => + fetchDeviceRegistry(conn).then((devices) => + store.setState(devices, true) + ), + 500, + true + ), + "device_registry_updated" + ); + +export const subscribeDeviceRegistry = ( + conn: Connection, + onChange: (devices: DeviceRegistryEntry[]) => void +) => + createCollection( + "_dr", + fetchDeviceRegistry, + subscribeDeviceRegistryUpdates, + conn, + onChange + ); diff --git a/src/data/ws-entity_registry_display.ts b/src/data/ws-entity_registry_display.ts new file mode 100644 index 0000000000..c57229b618 --- /dev/null +++ b/src/data/ws-entity_registry_display.ts @@ -0,0 +1,35 @@ +import { Connection, createCollection } from "home-assistant-js-websocket"; +import { Store } from "home-assistant-js-websocket/dist/store"; +import { + EntityRegistryDisplayEntryResponse, + fetchEntityRegistryDisplay, +} from "./entity_registry"; +import { debounce } from "../common/util/debounce"; + +const subscribeEntityRegistryDisplayUpdates = ( + conn: Connection, + store: Store +) => + conn.subscribeEvents( + debounce( + () => + fetchEntityRegistryDisplay(conn).then((entities) => + store.setState(entities, true) + ), + 500, + true + ), + "entity_registry_updated" + ); + +export const subscribeEntityRegistryDisplay = ( + conn: Connection, + onChange: (entities: EntityRegistryDisplayEntryResponse) => void +) => + createCollection( + "_entityRegistryDisplay", + fetchEntityRegistryDisplay, + subscribeEntityRegistryDisplayUpdates, + conn, + onChange + ); diff --git a/src/data/zha.ts b/src/data/zha.ts index 5605384973..be61adbf9e 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -73,7 +73,7 @@ export interface ClusterAttributeData { export interface AttributeConfigurationStatus { id: number; name: string; - success: boolean | undefined; + status: string; min: number; max: number; change: number; diff --git a/src/dialogs/area-filter/area-filter-dialog.ts b/src/dialogs/area-filter/area-filter-dialog.ts index 2dc7cacadc..1e26064aa0 100644 --- a/src/dialogs/area-filter/area-filter-dialog.ts +++ b/src/dialogs/area-filter/area-filter-dialog.ts @@ -4,15 +4,14 @@ import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { repeat } from "lit/directives/repeat"; -import type { SortableEvent } from "sortablejs"; import { fireEvent } from "../../common/dom/fire_event"; import type { AreaFilterValue } from "../../components/ha-area-filter"; import "../../components/ha-button"; +import "../../components/ha-dialog"; import "../../components/ha-icon-button"; import "../../components/ha-list-item"; +import "../../components/ha-sortable"; import { areaCompare } from "../../data/area_registry"; -import { sortableStyles } from "../../resources/ha-sortable-style"; -import type { SortableInstance } from "../../resources/sortable"; import { haStyleDialog } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import { HassDialog } from "../make-dialog-manager"; @@ -31,23 +30,18 @@ export class DialogAreaFilter @state() private _areas: string[] = []; - private _sortable?: SortableInstance; - - public async showDialog(dialogParams: AreaFilterDialogParams): Promise { + public showDialog(dialogParams: AreaFilterDialogParams): void { this._dialogParams = dialogParams; this._hidden = dialogParams.initialValue?.hidden ?? []; const order = dialogParams.initialValue?.order ?? []; const allAreas = Object.keys(this.hass!.areas); this._areas = allAreas.concat().sort(areaCompare(this.hass!.areas, order)); - await this.updateComplete; - this._createSortable(); } public closeDialog(): void { this._dialogParams = undefined; this._hidden = []; this._areas = []; - this._destroySortable(); fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -66,42 +60,14 @@ export class DialogAreaFilter this.closeDialog(); } - private async _createSortable() { - const Sortable = (await import("../../resources/sortable")).default; - if (this._sortable) return; - this._sortable = new Sortable(this.shadowRoot!.querySelector(".areas")!, { - animation: 150, - fallbackClass: "sortable-fallback", - handle: ".handle", - draggable: ".draggable", - onChoose: (evt: SortableEvent) => { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._dragged(evt); - }, - }); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - - private _dragged(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) return; + private _areaMoved(ev: CustomEvent): void { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; const areas = this._areas.concat(); - const option = areas.splice(ev.oldIndex!, 1)[0]; - areas.splice(ev.newIndex!, 0, option); + const option = areas.splice(oldIndex, 1)[0]; + areas.splice(newIndex, 0, option); this._areas = areas; } @@ -120,50 +86,56 @@ export class DialogAreaFilter .heading=${this._dialogParams.title ?? this.hass.localize("ui.components.area-filter.title")} > - - ${repeat( - allAreas, - (area) => area, - (area, _idx) => { - const isVisible = !this._hidden.includes(area); - const name = this.hass!.areas[area]?.name || area; - return html` - - ${isVisible - ? html`` - : nothing} - ${name} - - - `; - } - )} - + + + ${repeat( + allAreas, + (area) => area, + (area, _idx) => { + const isVisible = !this._hidden.includes(area); + const name = this.hass!.areas[area]?.name || area; + return html` + + ${isVisible + ? html`` + : nothing} + ${name} + + + `; + } + )} + + ${this.hass.localize("ui.common.cancel")} @@ -192,7 +164,6 @@ export class DialogAreaFilter static get styles(): CSSResultGroup { return [ - sortableStyles, haStyleDialog, css` ha-dialog { diff --git a/src/dialogs/config-flow/previews/entity-preview-row.ts b/src/dialogs/config-flow/previews/entity-preview-row.ts index 760366c61a..724e60ebf2 100644 --- a/src/dialogs/config-flow/previews/entity-preview-row.ts +++ b/src/dialogs/config-flow/previews/entity-preview-row.ts @@ -2,8 +2,10 @@ import { HassEntity } from "home-assistant-js-websocket"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeStateName } from "../../../common/entity/compute_state_name"; +import "../../../components/entity/state-badge"; import { isUnavailableState } from "../../../data/entity"; import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor"; +import "../../../panels/lovelace/components/hui-timestamp-display"; import { HomeAssistant } from "../../../types"; @customElement("entity-preview-row") @@ -49,6 +51,8 @@ class EntityPreviewRow extends LitElement { .name { margin-left: 16px; margin-right: 8px; + margin-inline-start: 16px; + margin-inline-end: 8px; flex: 1 1 30%; } .value { diff --git a/src/dialogs/config-flow/previews/flow-preview-group.ts b/src/dialogs/config-flow/previews/flow-preview-group.ts index 11d48cd09f..5afc93a038 100644 --- a/src/dialogs/config-flow/previews/flow-preview-group.ts +++ b/src/dialogs/config-flow/previews/flow-preview-group.ts @@ -19,7 +19,7 @@ class FlowPreviewGroup extends LitElement { @property() public flowId!: string; - @property() public stepData!: Record; + @property({ attribute: false }) public stepData!: Record; @state() private _preview?: HassEntity; diff --git a/src/dialogs/config-flow/previews/flow-preview-template.ts b/src/dialogs/config-flow/previews/flow-preview-template.ts index 19e7c6c998..e2f49feb43 100644 --- a/src/dialogs/config-flow/previews/flow-preview-template.ts +++ b/src/dialogs/config-flow/previews/flow-preview-template.ts @@ -24,7 +24,7 @@ class FlowPreviewTemplate extends LitElement { @property() public flowId!: string; - @property() public stepData!: Record; + @property({ attribute: false }) public stepData!: Record; @state() private _preview?: HassEntity; diff --git a/src/dialogs/config-flow/previews/flow-preview-time_date.ts b/src/dialogs/config-flow/previews/flow-preview-time_date.ts new file mode 100644 index 0000000000..fd4676ec43 --- /dev/null +++ b/src/dialogs/config-flow/previews/flow-preview-time_date.ts @@ -0,0 +1,94 @@ +import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { LitElement, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { FlowType } from "../../../data/data_entry_flow"; +import { + TimeDatePreview, + subscribePreviewTimeDate, +} from "../../../data/time_date"; +import { HomeAssistant } from "../../../types"; +import "./entity-preview-row"; +import { debounce } from "../../../common/util/debounce"; + +@customElement("flow-preview-time_date") +class FlowPreviewTimeDate extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public flowType!: FlowType; + + public handler!: string; + + @property() public stepId!: string; + + @property() public flowId!: string; + + @property() public stepData!: Record; + + @state() private _preview?: HassEntity; + + private _unsub?: Promise; + + disconnectedCallback(): void { + super.disconnectedCallback(); + if (this._unsub) { + this._unsub.then((unsub) => unsub()); + this._unsub = undefined; + } + } + + willUpdate(changedProps) { + if (changedProps.has("stepData")) { + this._debouncedSubscribePreview(); + } + } + + protected render() { + return html``; + } + + private _setPreview = (preview: TimeDatePreview) => { + const now = new Date().toISOString(); + this._preview = { + entity_id: `${this.stepId}.___flow_preview___`, + last_changed: now, + last_updated: now, + context: { id: "", parent_id: null, user_id: null }, + ...preview, + }; + }; + + private _debouncedSubscribePreview = debounce(() => { + this._subscribePreview(); + }, 250); + + private async _subscribePreview() { + if (this._unsub) { + (await this._unsub)(); + this._unsub = undefined; + } + if (this.flowType === "repair_flow") { + return; + } + try { + this._unsub = subscribePreviewTimeDate( + this.hass, + this.flowId, + this.flowType, + this.stepData, + this._setPreview + ); + await this._unsub; + } catch (err) { + this._preview = undefined; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "flow-preview-time_date": FlowPreviewTimeDate; + } +} diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index eb9595ab50..05f3acc1b4 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -110,9 +110,11 @@ export const showOptionsFlowDialog = ( }, renderShowFormStepFieldError(hass, step, error) { - return hass.localize( - `component.${configEntry.domain}.options.error.${error}`, - step.description_placeholders + return ( + hass.localize( + `component.${configEntry.domain}.options.error.${error}`, + step.description_placeholders + ) || error ); }, diff --git a/src/dialogs/config-flow/step-flow-create-entry.ts b/src/dialogs/config-flow/step-flow-create-entry.ts index f7e15836d7..43c5e93e3c 100644 --- a/src/dialogs/config-flow/step-flow-create-entry.ts +++ b/src/dialogs/config-flow/step-flow-create-entry.ts @@ -126,6 +126,8 @@ class StepFlowCreateEntry extends LitElement { } .buttons > *:last-child { margin-left: auto; + margin-inline-start: auto; + margin-inline-end: initial; } @media all and (max-width: 450px), all and (max-height: 500px) { .device { diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index fd7b28ea5e..1390c3f02e 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -201,8 +201,19 @@ class StepFlowForm extends LitElement { step, }); } catch (err: any) { - this._errorMsg = - (err && err.body && err.body.message) || "Unknown error occurred"; + if (err && err.body) { + if (err.body.message) { + this._errorMsg = err.body.message; + } + if (err.body.errors) { + this.step = { ...this.step, errors: err.body.errors }; + } + if (!err.body.message && !err.body.errors) { + this._errorMsg = "Unknown error occurred"; + } + } else { + this._errorMsg = "Unknown error occurred"; + } } finally { this._loading = false; } @@ -239,6 +250,8 @@ class StepFlowForm extends LitElement { .submit-spinner { margin-right: 16px; + margin-inline-end: 16px; + margin-inline-start: initial; } ha-alert, diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index 3b2ded7aad..2660b56799 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -74,7 +74,7 @@ class DialogBox extends LitElement { { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._dragged(evt); - }, - } - ); - } - - private _dragged(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) return; - this._move(ev.oldIndex!, ev.newIndex!); + private _colorMoved(ev: CustomEvent): void { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; + this._move(oldIndex, newIndex); } private _move(index: number, newIndex: number) { @@ -107,11 +73,6 @@ export class HaMoreInfoLightFavoriteColors extends LitElement { this._save(favoriteColors); } - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private _apply = (index: number) => { const favorite = this._favoriteColors[index]; this.hass.callService("light", "turn_on", { @@ -223,72 +184,79 @@ export class HaMoreInfoLightFavoriteColors extends LitElement { protected render(): TemplateResult { return html` -
- ${this._favoriteColors.map( - (color, index) => html` -
-
- +
+ ${this._favoriteColors.map( + (color, index) => html` +
+
- - ${this.editMode - ? html` - - ` - : nothing} + + + ${this.editMode + ? html` + + ` + : nothing} +
-
- ` - )} - ${this.editMode - ? html` - - - - - - ` - : nothing} -
+ )} + ${this.editMode + ? html` + + + + + + + ` + : nothing} +
+ `; } diff --git a/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts b/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts index 53cd1ac08e..bfc39a2ddc 100644 --- a/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts +++ b/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts @@ -2,9 +2,9 @@ import { mdiShieldOff } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; -import { domainIcon } from "../../../common/entity/domain_icon"; import { stateColorCss } from "../../../common/entity/state_color"; import "../../../components/ha-outlined-button"; +import "../../../components/ha-state-icon"; import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel"; import "../../../state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes"; import type { HomeAssistant } from "../../../types"; @@ -59,9 +59,8 @@ class MoreInfoAlarmControlPanel extends LitElement {
- + +
${this.hass.localize("ui.card.alarm_control_panel.disarm")} diff --git a/src/dialogs/more-info/controls/more-info-climate.ts b/src/dialogs/more-info/controls/more-info-climate.ts index b409270629..086bb7fa78 100644 --- a/src/dialogs/more-info/controls/more-info-climate.ts +++ b/src/dialogs/more-info/controls/more-info-climate.ts @@ -1,8 +1,8 @@ import "@material/mwc-list/mwc-list-item"; import { + mdiArrowOscillating, mdiFan, mdiThermometer, - mdiThermostat, mdiTuneVariant, mdiWaterPercent, } from "@mdi/js"; @@ -10,6 +10,7 @@ import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; import "../../../components/ha-control-select-menu"; import "../../../components/ha-icon-button-group"; import "../../../components/ha-icon-button-toggle"; @@ -19,14 +20,10 @@ import "../../../components/ha-switch"; import { ClimateEntity, ClimateEntityFeature, + climateHvacModeIcon, compareClimateHvacModes, - computeFanModeIcon, - computeHvacModeIcon, - computePresetModeIcon, - computeSwingModeIcon, } from "../../../data/climate"; import { UNAVAILABLE } from "../../../data/entity"; -import { haOscillating } from "../../../data/icons/haOscillating"; import "../../../state-control/climate/ha-state-control-climate-humidity"; import "../../../state-control/climate/ha-state-control-climate-temperature"; import { HomeAssistant } from "../../../types"; @@ -38,7 +35,7 @@ type MainControl = "temperature" | "humidity"; class MoreInfoClimate extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: ClimateEntity; + @property({ attribute: false }) public stateObj?: ClimateEntity; @state() private _mainControl: MainControl = "temperature"; @@ -164,7 +161,12 @@ class MoreInfoClimate extends LitElement { @selected=${this._handleOperationModeChanged} @closed=${stopPropagation} > - + ${html` + + `} ${stateObj.attributes.hvac_modes .concat() .sort(compareClimateHvacModes) @@ -173,7 +175,7 @@ class MoreInfoClimate extends LitElement { ${this.hass.formatEntityState(stateObj, mode)} @@ -194,14 +196,32 @@ class MoreInfoClimate extends LitElement { @selected=${this._handlePresetmodeChanged} @closed=${stopPropagation} > - + ${stateObj.attributes.preset_mode + ? html` + + ` + : html` + + `} ${stateObj.attributes.preset_modes!.map( (mode) => html` - + .hass=${this.hass} + .stateObj=${stateObj} + attribute="preset_mode" + .attributeValue=${mode} + > ${this.hass.formatEntityAttributeValue( stateObj, "preset_mode", @@ -227,14 +247,29 @@ class MoreInfoClimate extends LitElement { @selected=${this._handleFanModeChanged} @closed=${stopPropagation} > - + ${stateObj.attributes.fan_mode + ? html` + + ` + : html` + + `} ${stateObj.attributes.fan_modes!.map( (mode) => html` - + .hass=${this.hass} + .stateObj=${stateObj} + attribute="fan_mode" + .attributeValue=${mode} + > ${this.hass.formatEntityAttributeValue( stateObj, "fan_mode", @@ -260,14 +295,32 @@ class MoreInfoClimate extends LitElement { @selected=${this._handleSwingmodeChanged} @closed=${stopPropagation} > - + ${stateObj.attributes.swing_mode + ? html` + + ` + : html` + + `} ${stateObj.attributes.swing_modes!.map( (mode) => html` - + .hass=${this.hass} + .stateObj=${stateObj} + attribute="swing_mode" + .attributeValue=${mode} + > ${this.hass.formatEntityAttributeValue( stateObj, "swing_mode", diff --git a/src/dialogs/more-info/controls/more-info-configurator.ts b/src/dialogs/more-info/controls/more-info-configurator.ts index dca2575fe0..28c392d4c6 100644 --- a/src/dialogs/more-info/controls/more-info-configurator.ts +++ b/src/dialogs/more-info/controls/more-info-configurator.ts @@ -118,6 +118,8 @@ export class MoreInfoConfigurator extends LitElement { width: 14px; height: 14px; margin-right: 20px; + margin-inline-end: 20px; + margin-inline-start: initial; } `; } diff --git a/src/dialogs/more-info/controls/more-info-counter.ts b/src/dialogs/more-info/controls/more-info-counter.ts index c1132f8172..60e8c733e6 100644 --- a/src/dialogs/more-info/controls/more-info-counter.ts +++ b/src/dialogs/more-info/controls/more-info-counter.ts @@ -9,7 +9,7 @@ import { HomeAssistant } from "../../../types"; class MoreInfoCounter extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; protected render() { if (!this.hass || !this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-datetime.ts b/src/dialogs/more-info/controls/more-info-datetime.ts index 85d7f44bd3..88020df8a8 100644 --- a/src/dialogs/more-info/controls/more-info-datetime.ts +++ b/src/dialogs/more-info/controls/more-info-datetime.ts @@ -74,6 +74,8 @@ class MoreInfoDatetime extends LitElement { } ha-date-input + ha-time-input { margin-left: 4px; + margin-inline-start: 4px; + margin-inline-end: initial; } `; } diff --git a/src/dialogs/more-info/controls/more-info-default.ts b/src/dialogs/more-info/controls/more-info-default.ts index a8e14bdecb..c7993fa47f 100644 --- a/src/dialogs/more-info/controls/more-info-default.ts +++ b/src/dialogs/more-info/controls/more-info-default.ts @@ -8,7 +8,7 @@ import { HomeAssistant } from "../../../types"; class MoreInfoDefault extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; protected render() { if (!this.hass || !this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-fan.ts b/src/dialogs/more-info/controls/more-info-fan.ts index 7d6f9f129e..d26a4defda 100644 --- a/src/dialogs/more-info/controls/more-info-fan.ts +++ b/src/dialogs/more-info/controls/more-info-fan.ts @@ -1,9 +1,9 @@ import { + mdiArrowOscillating, + mdiArrowOscillatingOff, mdiFan, mdiFanOff, mdiPower, - mdiRotateLeft, - mdiRotateRight, mdiTuneVariant, } from "@mdi/js"; import { CSSResultGroup, LitElement, PropertyValues, html, nothing } from "lit"; @@ -11,6 +11,7 @@ import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { stateActive } from "../../../common/entity/state_active"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; import "../../../components/ha-control-select-menu"; import "../../../components/ha-list-item"; import "../../../components/ha-outlined-icon-button"; @@ -23,8 +24,6 @@ import { computeFanSpeedStateDisplay, } from "../../../data/fan"; import { forwardHaptic } from "../../../data/haptics"; -import { haOscillating } from "../../../data/icons/haOscillating"; -import { haOscillatingOff } from "../../../data/icons/haOscillatingOff"; import "../../../state-control/fan/ha-state-control-fan-speed"; import "../../../state-control/ha-state-control-toggle"; import type { HomeAssistant } from "../../../types"; @@ -187,10 +186,30 @@ class MoreInfoFan extends LitElement { @selected=${this._handlePresetMode} @closed=${stopPropagation} > - + ${this.stateObj.attributes.preset_mode + ? html`` + : html` + + `} ${this.stateObj.attributes.preset_modes?.map( (mode) => html` - + + ${this.hass.formatEntityAttributeValue( this.stateObj!, "preset_mode", @@ -216,12 +235,21 @@ class MoreInfoFan extends LitElement { @selected=${this._handleDirection} @closed=${stopPropagation} > - + - + .hass=${this.hass} + .stateObj=${this.stateObj} + attribute="direction" + attributeValue="forward" + > ${this.hass.formatEntityAttributeValue( this.stateObj, "direction", @@ -229,10 +257,13 @@ class MoreInfoFan extends LitElement { )} - + .hass=${this.hass} + .stateObj=${this.stateObj} + attribute="direction" + attributeValue="reverse" + > ${this.hass.formatEntityAttributeValue( this.stateObj, "direction", @@ -260,12 +291,12 @@ class MoreInfoFan extends LitElement { > ${this.hass.formatEntityAttributeValue( this.stateObj, @@ -276,7 +307,7 @@ class MoreInfoFan extends LitElement { ${this.hass.formatEntityAttributeValue( this.stateObj, diff --git a/src/dialogs/more-info/controls/more-info-group.ts b/src/dialogs/more-info/controls/more-info-group.ts index dd634a9927..b6cfc47d23 100644 --- a/src/dialogs/more-info/controls/more-info-group.ts +++ b/src/dialogs/more-info/controls/more-info-group.ts @@ -21,7 +21,7 @@ import { class MoreInfoGroup extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: GroupEntity; + @property({ attribute: false }) public stateObj?: GroupEntity; @state() private _groupDomainStateObj?: HassEntity; diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts index 3799d557f9..28d2ab3de0 100644 --- a/src/dialogs/more-info/controls/more-info-humidifier.ts +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -12,11 +12,11 @@ import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-control-select-menu"; import "../../../components/ha-list-item"; +import "../../../components/ha-attribute-icon"; import { UNAVAILABLE } from "../../../data/entity"; import { HumidifierEntity, HumidifierEntityFeature, - computeHumidiferModeIcon, } from "../../../data/humidifier"; import "../../../state-control/humidifier/ha-state-control-humidifier-humidity"; import { HomeAssistant } from "../../../types"; @@ -26,7 +26,7 @@ import { moreInfoControlStyle } from "../components/more-info-control-style"; class MoreInfoHumidifier extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HumidifierEntity; + @property({ attribute: false }) public stateObj?: HumidifierEntity; @state() public _mode?: string; @@ -109,14 +109,32 @@ class MoreInfoHumidifier extends LitElement { @selected=${this._handleModeChanged} @closed=${stopPropagation} > - + ${stateObj.attributes.mode + ? html` + + ` + : html` + + `} ${stateObj.attributes.available_modes!.map( (mode) => html` - + .hass=${this.hass} + .stateObj=${stateObj} + attribute="mode" + .attributeValue=${mode} + > ${this.hass.formatEntityAttributeValue( stateObj!, "mode", diff --git a/src/dialogs/more-info/controls/more-info-input_datetime.ts b/src/dialogs/more-info/controls/more-info-input_datetime.ts index a44917e79a..8f1ae7273d 100644 --- a/src/dialogs/more-info/controls/more-info-input_datetime.ts +++ b/src/dialogs/more-info/controls/more-info-input_datetime.ts @@ -86,6 +86,8 @@ class MoreInfoInputDatetime extends LitElement { } ha-date-input + ha-time-input { margin-left: 4px; + margin-inline-start: 4px; + margin-inline-end: initial; } `; } diff --git a/src/dialogs/more-info/controls/more-info-lawn_mower.ts b/src/dialogs/more-info/controls/more-info-lawn_mower.ts index 05a92ce1ce..349f5e3b3a 100644 --- a/src/dialogs/more-info/controls/more-info-lawn_mower.ts +++ b/src/dialogs/more-info/controls/more-info-lawn_mower.ts @@ -54,7 +54,7 @@ const LAWN_MOWER_COMMANDS: LawnMowerCommand[] = [ class MoreInfoLawnMower extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: LawnMowerEntity; + @property({ attribute: false }) public stateObj?: LawnMowerEntity; protected render() { if (!this.hass || !this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 663c88dfbb..5754e27c71 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -19,6 +19,7 @@ import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; +import "../../../components/ha-attribute-icon"; import "../../../components/ha-control-select-menu"; import "../../../components/ha-icon-button-group"; import "../../../components/ha-icon-button-toggle"; @@ -271,14 +272,32 @@ class MoreInfoLight extends LitElement { @selected=${this._handleEffect} @closed=${stopPropagation} > - + ${this.stateObj.attributes.effect + ? html`` + : html``} ${this.stateObj.attributes.effect_list?.map( - (mode) => html` - + (effect) => html` + + ${this.hass.formatEntityAttributeValue( this.stateObj!, "effect", - mode + effect )} ` diff --git a/src/dialogs/more-info/controls/more-info-lock.ts b/src/dialogs/more-info/controls/more-info-lock.ts index fb5815cc52..9af77fd128 100644 --- a/src/dialogs/more-info/controls/more-info-lock.ts +++ b/src/dialogs/more-info/controls/more-info-lock.ts @@ -2,11 +2,11 @@ import { mdiDoorOpen, mdiLock, mdiLockOff } from "@mdi/js"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; -import { domainIcon } from "../../../common/entity/domain_icon"; import { stateColorCss } from "../../../common/entity/state_color"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; import "../../../components/ha-outlined-icon-button"; +import "../../../components/ha-state-icon"; import { UNAVAILABLE } from "../../../data/entity"; import { LockEntity, @@ -62,9 +62,10 @@ class MoreInfoLock extends LitElement {
- +
` diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index c33dab198b..b7510a675c 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -261,6 +261,8 @@ class MoreInfoMediaPlayer extends LitElement { .browse-media-icon { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } `; } diff --git a/src/dialogs/more-info/controls/more-info-person.ts b/src/dialogs/more-info/controls/more-info-person.ts index c5d21ad097..bba6427c8f 100644 --- a/src/dialogs/more-info/controls/more-info-person.ts +++ b/src/dialogs/more-info/controls/more-info-person.ts @@ -13,7 +13,7 @@ import { HomeAssistant } from "../../../types"; class MoreInfoPerson extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; private _entityArray = memoizeOne((entityId: string) => [entityId]); diff --git a/src/dialogs/more-info/controls/more-info-remote.ts b/src/dialogs/more-info/controls/more-info-remote.ts index 918ebd3a71..382fcff7d8 100644 --- a/src/dialogs/more-info/controls/more-info-remote.ts +++ b/src/dialogs/more-info/controls/more-info-remote.ts @@ -1,6 +1,6 @@ -import "@material/mwc-list/mwc-list"; +import "@material/mwc-select/mwc-select"; import "@material/mwc-list/mwc-list-item"; -import { html, LitElement, nothing } from "lit"; +import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; @@ -14,7 +14,7 @@ const filterExtraAttributes = "activity_list,current_activity"; class MoreInfoRemote extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: RemoteEntity; + @property({ attribute: false }) public stateObj?: RemoteEntity; protected render() { if (!this.hass || !this.stateObj) { @@ -26,17 +26,17 @@ class MoreInfoRemote extends LitElement { return html` ${supportsFeature(stateObj, REMOTE_SUPPORT_ACTIVITY) ? html` - - ${stateObj.attributes.activity_list!.map( + ${stateObj.attributes.activity_list?.map( (activity) => html` ${this.hass.formatEntityAttributeValue( @@ -47,7 +47,7 @@ class MoreInfoRemote extends LitElement { ` )} - + ` : nothing} @@ -59,7 +59,7 @@ class MoreInfoRemote extends LitElement { `; } - private handleActivityChanged(ev) { + private _handleActivityChanged(ev) { const oldVal = this.stateObj!.attributes.current_activity; const newVal = ev.target.value; @@ -72,6 +72,12 @@ class MoreInfoRemote extends LitElement { activity: newVal, }); } + + static styles = css` + mwc-select { + width: 100%; + } + `; } declare global { diff --git a/src/dialogs/more-info/controls/more-info-script.ts b/src/dialogs/more-info/controls/more-info-script.ts index 1d9ed6fb0c..c53da01be1 100644 --- a/src/dialogs/more-info/controls/more-info-script.ts +++ b/src/dialogs/more-info/controls/more-info-script.ts @@ -8,7 +8,7 @@ import { HomeAssistant } from "../../../types"; class MoreInfoScript extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; protected render() { if (!this.hass || !this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-sun.ts b/src/dialogs/more-info/controls/more-info-sun.ts index 341da55268..fb3da7108c 100644 --- a/src/dialogs/more-info/controls/more-info-sun.ts +++ b/src/dialogs/more-info/controls/more-info-sun.ts @@ -9,7 +9,7 @@ import { HomeAssistant } from "../../../types"; class MoreInfoSun extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; protected render() { if (!this.hass || !this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-timer.ts b/src/dialogs/more-info/controls/more-info-timer.ts index cba66d5ed7..5af7aca7b6 100644 --- a/src/dialogs/more-info/controls/more-info-timer.ts +++ b/src/dialogs/more-info/controls/more-info-timer.ts @@ -9,7 +9,7 @@ import { HomeAssistant } from "../../../types"; class MoreInfoTimer extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: TimerEntity; + @property({ attribute: false }) public stateObj?: TimerEntity; protected render() { if (!this.hass || !this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index 00367bdd37..56dc50dee9 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -99,7 +99,7 @@ class MoreInfoUpdate extends LitElement { : ""} ${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) && !this._error - ? !this._releaseNotes + ? this._releaseNotes === undefined ? html`
` diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts index 4076935d9e..c5b7e9768c 100644 --- a/src/dialogs/more-info/controls/more-info-vacuum.ts +++ b/src/dialogs/more-info/controls/more-info-vacuum.ts @@ -100,7 +100,7 @@ const VACUUM_COMMANDS: VacuumCommand[] = [ class MoreInfoVacuum extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: VacuumEntity; + @property({ attribute: false }) public stateObj?: VacuumEntity; protected render() { if (!this.hass || !this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-water_heater.ts b/src/dialogs/more-info/controls/more-info-water_heater.ts index 71e1ffc5f5..4a5f1d2f04 100644 --- a/src/dialogs/more-info/controls/more-info-water_heater.ts +++ b/src/dialogs/more-info/controls/more-info-water_heater.ts @@ -21,7 +21,7 @@ import { moreInfoControlStyle } from "../components/more-info-control-style"; class MoreInfoWaterHeater extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: WaterHeaterEntity; + @property({ attribute: false }) public stateObj?: WaterHeaterEntity; protected render() { if (!this.stateObj) { diff --git a/src/dialogs/more-info/controls/more-info-weather.ts b/src/dialogs/more-info/controls/more-info-weather.ts index 2854d90110..4b3cd4eba9 100644 --- a/src/dialogs/more-info/controls/more-info-weather.ts +++ b/src/dialogs/more-info/controls/more-info-weather.ts @@ -37,7 +37,7 @@ import { HomeAssistant } from "../../../types"; class MoreInfoWeather extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj?: WeatherEntity; + @property({ attribute: false }) public stateObj?: WeatherEntity; @state() private _forecastEvent?: ForecastEvent; @@ -330,6 +330,8 @@ class MoreInfoWeather extends LitElement { ha-svg-icon { color: var(--paper-item-icon-color); margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } mwc-tab-bar { @@ -346,10 +348,15 @@ class MoreInfoWeather extends LitElement { height: 32px; align-items: center; } + .flex > div:last-child { + direction: ltr; + } .main { flex: 1; margin-left: 24px; + margin-inline-start: 24px; + margin-inline-end: initial; } .temp, diff --git a/src/dialogs/more-info/ha-more-info-info.ts b/src/dialogs/more-info/ha-more-info-info.ts index ca05ec75d2..99e8a95448 100644 --- a/src/dialogs/more-info/ha-more-info-info.ts +++ b/src/dialogs/more-info/ha-more-info-info.ts @@ -45,9 +45,13 @@ export class MoreInfoInfo extends LitElement {
${!stateObj ? html` - ${this.hass.localize( - "ui.dialogs.entity_registry.editor.unavailable" - )} + ${this.entry?.disabled_by + ? this.hass.localize( + "ui.dialogs.entity_registry.editor.entity_disabled" + ) + : this.hass.localize( + "ui.dialogs.entity_registry.editor.unavailable" + )} ` : nothing} ${stateObj?.attributes.restored && entityRegObj diff --git a/src/dialogs/notifications/configurator-notification-item.ts b/src/dialogs/notifications/configurator-notification-item.ts index ae69269952..0d87c6fd5f 100644 --- a/src/dialogs/notifications/configurator-notification-item.ts +++ b/src/dialogs/notifications/configurator-notification-item.ts @@ -11,7 +11,8 @@ import "./notification-item-template"; export class HuiConfiguratorNotificationItem extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public notification?: PersitentNotificationEntity; + @property({ attribute: false }) + public notification?: PersitentNotificationEntity; protected render() { if (!this.hass || !this.notification) { diff --git a/src/dialogs/notifications/notification-drawer.ts b/src/dialogs/notifications/notification-drawer.ts index 9ea46efedd..a1a15f8130 100644 --- a/src/dialogs/notifications/notification-drawer.ts +++ b/src/dialogs/notifications/notification-drawer.ts @@ -157,6 +157,8 @@ export class HuiNotificationDrawer extends LitElement { padding-top: 16px; padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); + padding-inline-start: env(safe-area-inset-left); + padding-inline-end: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); height: calc(100% - 1px - var(--header-height)); box-sizing: border-box; diff --git a/src/dialogs/notifications/notification-item.ts b/src/dialogs/notifications/notification-item.ts index 908e192514..ca92227e1f 100644 --- a/src/dialogs/notifications/notification-item.ts +++ b/src/dialogs/notifications/notification-item.ts @@ -10,7 +10,8 @@ import "./persistent-notification-item"; export class HuiNotificationItem extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public notification?: HassEntity | PersistentNotification; + @property({ attribute: false }) + public notification?: HassEntity | PersistentNotification; protected shouldUpdate(changedProps: PropertyValues): boolean { if (!this.hass || !this.notification || changedProps.has("notification")) { diff --git a/src/dialogs/notifications/persistent-notification-item.ts b/src/dialogs/notifications/persistent-notification-item.ts index 40ce7e6b2f..16a6152965 100644 --- a/src/dialogs/notifications/persistent-notification-item.ts +++ b/src/dialogs/notifications/persistent-notification-item.ts @@ -13,7 +13,7 @@ import "./notification-item-template"; export class HuiPersistentNotificationItem extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public notification?: PersistentNotification; + @property({ attribute: false }) public notification?: PersistentNotification; protected render() { if (!this.hass || !this.notification) { diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 71da2ec0dc..da30fb7e9b 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -8,7 +8,7 @@ import { mdiReload, mdiServerNetwork, } from "@mdi/js"; -import { LitElement, css, html, nothing } from "lit"; +import { LitElement, TemplateResult, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; @@ -17,9 +17,7 @@ import { canShowPage } from "../../common/config/can_show_page"; import { componentsWithService } from "../../common/config/components_with_service"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { fireEvent } from "../../common/dom/fire_event"; -import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; -import { domainIcon } from "../../common/entity/domain_icon"; import { navigate } from "../../common/navigate"; import { caseInsensitiveStringCompare } from "../../common/string/compare"; import { @@ -27,9 +25,9 @@ import { fuzzyFilterSort, } from "../../common/string/filter/sequence-matching"; import { debounce } from "../../common/util/debounce"; -import "../../components/ha-label"; import "../../components/ha-circular-progress"; import "../../components/ha-icon-button"; +import "../../components/ha-label"; import "../../components/ha-list-item"; import "../../components/ha-textfield"; import { fetchHassioAddonsInfo } from "../../data/hassio/addon"; @@ -56,7 +54,7 @@ interface CommandItem extends QuickBarItem { interface EntityItem extends QuickBarItem { altText: string; - icon?: string; + icon?: TemplateResult; } const isCommandItem = (item: QuickBarItem): item is CommandItem => @@ -296,16 +294,14 @@ export class QuickBar extends LitElement { graphic="icon" > ${item.iconPath - ? html`` - : html``} + ? html` + + ` + : html`${item.icon}`} ${item.primaryText} ${item.altText ? html` @@ -476,10 +472,12 @@ export class QuickBar extends LitElement { const entityItem = { primaryText: computeStateName(entityState), altText: entityId, - icon: entityState.attributes.icon, - iconPath: entityState.attributes.icon - ? undefined - : domainIcon(computeDomain(entityId), entityState), + icon: html` + + `, action: () => fireEvent(this, "hass-more-info", { entityId }), }; diff --git a/src/dialogs/restart/dialog-restart.ts b/src/dialogs/restart/dialog-restart.ts index a3fe4e992d..97f16e8087 100644 --- a/src/dialogs/restart/dialog-restart.ts +++ b/src/dialogs/restart/dialog-restart.ts @@ -456,6 +456,8 @@ class DialogRestart extends LitElement { margin: 8px 0 4px 0; padding-left: var(--mdc-list-side-padding, 20px); padding-right: var(--mdc-list-side-padding, 20px); + padding-inline-start: var(--mdc-list-side-padding, 20px); + padding-inline-end: var(--mdc-list-side-padding, 20px); } .loader { display: flex; diff --git a/src/entrypoints/core.ts b/src/entrypoints/core.ts index 6ca3c28053..e9bf6b4c00 100644 --- a/src/entrypoints/core.ts +++ b/src/entrypoints/core.ts @@ -11,19 +11,22 @@ import { import { loadTokens, saveTokens } from "../common/auth/token_storage"; import { hassUrl } from "../data/auth"; import { isExternal } from "../data/external"; -import { getRecorderInfo } from "../data/recorder"; import { subscribeFrontendUserData } from "../data/frontend"; import { fetchConfig } from "../data/lovelace/config/types"; import { fetchResources } from "../data/lovelace/resource"; +import { MAIN_WINDOW_NAME } from "../data/main_window"; +import { WindowWithPreloads } from "../data/preloads"; +import { getRecorderInfo } from "../data/recorder"; +import { subscribeRepairsIssueRegistry } from "../data/repairs"; +import { subscribeAreaRegistry } from "../data/ws-area_registry"; +import { subscribeDeviceRegistry } from "../data/ws-device_registry"; +import { subscribeEntityRegistryDisplay } from "../data/ws-entity_registry_display"; import { subscribePanels } from "../data/ws-panels"; import { subscribeThemes } from "../data/ws-themes"; -import { subscribeRepairsIssueRegistry } from "../data/repairs"; import { subscribeUser } from "../data/ws-user"; import type { ExternalAuth } from "../external_app/external_auth"; import "../resources/array.flat.polyfill"; import "../resources/safari-14-attachshadow-patch"; -import { MAIN_WINDOW_NAME } from "../data/main_window"; -import { WindowWithPreloads } from "../data/preloads"; window.name = MAIN_WINDOW_NAME; (window as any).frontendVersion = __VERSION__; @@ -113,6 +116,9 @@ window.hassConnection.then(({ conn }) => { // do nothing }; subscribeEntities(conn, noop); + subscribeEntityRegistryDisplay(conn, noop); + subscribeDeviceRegistry(conn, noop); + subscribeAreaRegistry(conn, noop); subscribeConfig(conn, noop); subscribeServices(conn, noop); subscribePanels(conn, noop); diff --git a/src/external_app/external_app_entrypoint.ts b/src/external_app/external_app_entrypoint.ts index 39176b652b..2f070044e2 100644 --- a/src/external_app/external_app_entrypoint.ts +++ b/src/external_app/external_app_entrypoint.ts @@ -7,6 +7,7 @@ This is the entry point for providing external app stuff from app entrypoint. import { fireEvent } from "../common/dom/fire_event"; import { mainWindow } from "../common/dom/get_main_window"; +import { showAutomationEditor } from "../data/automation"; import { HomeAssistantMain } from "../layouts/home-assistant-main"; import type { EMIncomingMessageCommands } from "./external_messaging"; @@ -79,6 +80,14 @@ const handleExternalMessage = ( success: true, result: null, }); + } else if (msg.command === "automation/editor/show") { + showAutomationEditor(msg.payload?.config); + bus.fireMessage({ + id: msg.id, + type: "result", + success: true, + result: null, + }); } else { return false; } diff --git a/src/external_app/external_messaging.ts b/src/external_app/external_messaging.ts index 94114f2fbf..8c346738b7 100644 --- a/src/external_app/external_messaging.ts +++ b/src/external_app/external_messaging.ts @@ -1,3 +1,5 @@ +import { AutomationConfig } from "../data/automation"; + const CALLBACK_EXTERNAL_BUS = "externalBus"; interface CommandInFlight { @@ -33,6 +35,13 @@ interface EMOutgoingMessageConfigGet extends EMMessage { type: "config/get"; } +interface EMOutgoingMessageScanQRCode extends EMMessage { + type: "qr_code/scan"; + title: string; + description: string; + alternative_option_label?: string; +} + interface EMOutgoingMessageMatterCommission extends EMMessage { type: "matter/commission"; } @@ -46,6 +55,13 @@ type EMOutgoingMessageWithAnswer = { request: EMOutgoingMessageConfigGet; response: ExternalConfig; }; + "qr_code/scan": { + request: EMOutgoingMessageScanQRCode; + response: + | EMIncomingMessageQRCodeResponseCanceled + | EMIncomingMessageQRCodeResponseAlternativeOptions + | EMIncomingMessageQRCodeResponseScanResult; + }; }; interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage { @@ -147,11 +163,34 @@ interface EMIncomingMessageShowSidebar { command: "sidebar/show"; } +interface EMIncomingMessageShowAutomationEditor { + id: number; + type: "command"; + command: "automation/editor/show"; + payload?: { + config?: Partial; + }; +} + +export interface EMIncomingMessageQRCodeResponseCanceled { + action: "canceled"; +} + +export interface EMIncomingMessageQRCodeResponseAlternativeOptions { + action: "alternative_options"; +} + +export interface EMIncomingMessageQRCodeResponseScanResult { + action: "scan_result"; + result: string; +} + export type EMIncomingMessageCommands = | EMIncomingMessageRestart | EMIncomingMessageShowNotifications | EMIncomingMessageToggleSidebar - | EMIncomingMessageShowSidebar; + | EMIncomingMessageShowSidebar + | EMIncomingMessageShowAutomationEditor; type EMIncomingMessage = | EMMessageResultSuccess @@ -168,6 +207,7 @@ export interface ExternalConfig { canCommissionMatter: boolean; canImportThreadCredentials: boolean; hasAssist: boolean; + hasQRScanner: number; } export class ExternalMessaging { diff --git a/src/layouts/hass-router-page.ts b/src/layouts/hass-router-page.ts index cf3aca4f78..3751b94341 100644 --- a/src/layouts/hass-router-page.ts +++ b/src/layouts/hass-router-page.ts @@ -46,7 +46,7 @@ export interface RouterOptions { const LOADING_SCREEN_THRESHOLD = 400; // ms export class HassRouterPage extends ReactiveElement { - @property() public route?: Route; + @property({ attribute: false }) public route?: Route; protected routerOptions!: RouterOptions; diff --git a/src/layouts/hass-subpage.ts b/src/layouts/hass-subpage.ts index c0763b5790..3a1ed24385 100644 --- a/src/layouts/hass-subpage.ts +++ b/src/layouts/hass-subpage.ts @@ -25,7 +25,7 @@ class HassSubpage extends LitElement { @property({ type: String, attribute: "back-path" }) public backPath?: string; - @property() public backCallback?: () => void; + @property({ attribute: false }) public backCallback?: () => void; @property({ type: Boolean, reflect: true }) public narrow = false; @@ -119,7 +119,6 @@ class HassSubpage extends LitElement { font-size: 20px; height: var(--header-height); padding: 8px 12px; - pointer-events: none; background-color: var(--app-header-background-color); font-weight: 400; color: var(--app-header-text-color, white); @@ -144,7 +143,7 @@ class HassSubpage extends LitElement { } .main-title { - margin: 0 0 0 24px; + margin: var(--margin-title); line-height: 20px; flex-grow: 1; } diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 7d3e90d623..275cfe82f1 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -116,7 +116,7 @@ export class HaTabsSubpageDataTable extends LitElement { * Function to call when the back button is pressed. * @type {() => void} */ - @property() public backCallback?: () => void; + @property({ attribute: false }) public backCallback?: () => void; /** * String to show when there are no records in the data table. @@ -130,13 +130,13 @@ export class HaTabsSubpageDataTable extends LitElement { */ @property({ type: Boolean }) public empty = false; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; /** * Array of tabs to show on the page. * @type {Array} */ - @property() public tabs: PageNavigation[] = []; + @property({ attribute: false }) public tabs: PageNavigation[] = []; /** * Force hides the filter menu. diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index ac650afdd4..15d8b26629 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -45,7 +45,7 @@ class HassTabsSubpage extends LitElement { @property({ type: String, attribute: "back-path" }) public backPath?: string; - @property() public backCallback?: () => void; + @property({ attribute: false }) public backCallback?: () => void; @property({ type: Boolean, attribute: "main-page" }) public mainPage = false; @@ -227,6 +227,8 @@ class HassTabsSubpage extends LitElement { ha-menu-button { margin-right: 24px; + margin-inline-end: 24px; + margin-inline-start: initial; } .toolbar { @@ -302,7 +304,7 @@ class HassTabsSubpage extends LitElement { max-height: var(--header-height); line-height: 20px; color: var(--sidebar-text-color); - margin: var(--main-title-margin, 0 0 0 24px); + margin: var(--main-title-margin, var(--margin-title)); } .content { @@ -312,6 +314,8 @@ class HassTabsSubpage extends LitElement { ); margin-left: env(safe-area-inset-left); margin-right: env(safe-area-inset-right); + margin-inline-start: env(safe-area-inset-left); + margin-inline-end: env(safe-area-inset-right); height: calc(100% - 1px - var(--header-height)); height: calc( 100% - 1px - var(--header-height) - env(safe-area-inset-bottom) diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts index 956c4c8eed..d6e7b3c826 100644 --- a/src/layouts/home-assistant-main.ts +++ b/src/layouts/home-assistant-main.ts @@ -38,9 +38,9 @@ interface EditSideBarEvent { export class HomeAssistantMain extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public route?: Route; + @property({ attribute: false }) public route?: Route; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _sidebarEditMode = false; diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index f9781eef5d..6bac52581f 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -3,16 +3,16 @@ import { customElement, state } from "lit/decorators"; import { isNavigationClick } from "../common/dom/is-navigation-click"; import { navigate } from "../common/navigate"; import { getStorageDefaultPanelUrlPath } from "../data/panel"; +import { WindowWithPreloads } from "../data/preloads"; import { getRecorderInfo, RecorderInfo } from "../data/recorder"; import "../resources/custom-card-support"; import { HassElement } from "../state/hass-element"; import QuickBarMixin from "../state/quick-bar-mixin"; import { HomeAssistant, Route } from "../types"; -import { WindowWithPreloads } from "../data/preloads"; import { storeState } from "../util/ha-pref-storage"; import { - renderLaunchScreenInfoBox, removeLaunchScreen, + renderLaunchScreenInfoBox, } from "../util/launch-screen"; import { registerServiceWorker, diff --git a/src/mixins/lit-localize-lite-mixin.ts b/src/mixins/lit-localize-lite-mixin.ts index 2259a23dff..7d4823d733 100644 --- a/src/mixins/lit-localize-lite-mixin.ts +++ b/src/mixins/lit-localize-lite-mixin.ts @@ -13,7 +13,7 @@ export const litLocalizeLiteMixin = >( ) => { class LitLocalizeLiteClass extends superClass { // Initialized to empty will prevent undefined errors if called before connected to DOM. - @property() public localize: LocalizeFunc = empty; + @property({ attribute: false }) public localize: LocalizeFunc = empty; // Use browser language setup before login. @property() public language?: string = getLocalLanguage(); diff --git a/src/onboarding/dialogs/app-dialog.ts b/src/onboarding/dialogs/app-dialog.ts index 087adfc3a7..3f66189792 100644 --- a/src/onboarding/dialogs/app-dialog.ts +++ b/src/onboarding/dialogs/app-dialog.ts @@ -6731,6 +6731,8 @@ class DialogApp extends LitElement { } a:first-child { margin-right: 16px; + margin-inline-end: 16px; + margin-inline-start: initial; } svg { width: 100%; diff --git a/src/onboarding/ha-onboarding.ts b/src/onboarding/ha-onboarding.ts index 02981a76cb..7237e4083d 100644 --- a/src/onboarding/ha-onboarding.ts +++ b/src/onboarding/ha-onboarding.ts @@ -286,7 +286,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) { try { const response = await (window.stepsPromise || fetchOnboardingOverview()); - if (response.status === 404) { + if (response.status === 401 || response.status === 404) { // We don't load the component when onboarding is done document.location.assign("/"); return; @@ -518,6 +518,8 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) { text-decoration: none; color: var(--primary-text-color); margin-right: 16px; + margin-inline-end: 16px; + margin-inline-start: initial; } `; } diff --git a/src/onboarding/integration-badge.ts b/src/onboarding/integration-badge.ts index be4b7ec8ef..e42b1b22e6 100644 --- a/src/onboarding/integration-badge.ts +++ b/src/onboarding/integration-badge.ts @@ -9,7 +9,7 @@ class IntegrationBadge extends LitElement { @property() public title!: string; - @property({ type: Boolean }) public darkOptimizedIcon?: boolean; + @property({ type: Boolean }) public darkOptimizedIcon = false; @property({ type: Boolean, reflect: true }) public clickable = false; diff --git a/src/onboarding/onboarding-analytics.ts b/src/onboarding/onboarding-analytics.ts index 0bf36b8159..4cce93b6d2 100644 --- a/src/onboarding/onboarding-analytics.ts +++ b/src/onboarding/onboarding-analytics.ts @@ -5,6 +5,7 @@ import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { LocalizeFunc } from "../common/translations/localize"; import "../components/ha-analytics"; +import "../components/ha-svg-icon"; import { Analytics, setAnalyticsPreferences } from "../data/analytics"; import { onboardAnalyticsStep } from "../data/onboarding"; import type { HomeAssistant } from "../types"; @@ -15,7 +16,7 @@ import { onBoardingStyles } from "./styles"; class OnboardingAnalytics extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public localize!: LocalizeFunc; + @property({ attribute: false }) public localize!: LocalizeFunc; @state() private _error?: string; diff --git a/src/onboarding/onboarding-core-config.ts b/src/onboarding/onboarding-core-config.ts index 7849ecfee6..a45b989c20 100644 --- a/src/onboarding/onboarding-core-config.ts +++ b/src/onboarding/onboarding-core-config.ts @@ -9,9 +9,11 @@ import { TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { LOCAL_TIME_ZONE } from "../common/datetime/resolve-time-zone"; import { fireEvent } from "../common/dom/fire_event"; import type { LocalizeFunc } from "../common/translations/localize"; import "../components/ha-alert"; +import "../components/ha-circular-progress"; import "../components/ha-country-picker"; import { ConfigUpdateValues, saveCoreConfig } from "../data/core"; import { countryCurrency } from "../data/currency"; @@ -24,7 +26,7 @@ import "./onboarding-location"; class OnboardingCoreConfig extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public onboardingLocalize!: LocalizeFunc; + @property({ attribute: false }) public onboardingLocalize!: LocalizeFunc; @state() private _working = false; @@ -32,8 +34,7 @@ class OnboardingCoreConfig extends LitElement { private _elevation = "0"; - private _timeZone: ConfigUpdateValues["time_zone"] = - Intl.DateTimeFormat?.().resolvedOptions?.().timeZone; + private _timeZone: ConfigUpdateValues["time_zone"] = LOCAL_TIME_ZONE; private _language: ConfigUpdateValues["language"] = getLocalLanguage(); diff --git a/src/onboarding/onboarding-create-user.ts b/src/onboarding/onboarding-create-user.ts index e8d50bfcc7..615d28930c 100644 --- a/src/onboarding/onboarding-create-user.ts +++ b/src/onboarding/onboarding-create-user.ts @@ -43,7 +43,7 @@ const CREATE_USER_SCHEMA: HaFormSchema[] = [ @customElement("onboarding-create-user") class OnboardingCreateUser extends LitElement { - @property() public localize!: LocalizeFunc; + @property({ attribute: false }) public localize!: LocalizeFunc; @property() public language!: string; diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index 337896b43a..12156a6ea8 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -36,7 +36,7 @@ const HIDDEN_DOMAINS = new Set([ class OnboardingIntegrations extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public onboardingLocalize!: LocalizeFunc; + @property({ attribute: false }) public onboardingLocalize!: LocalizeFunc; @state() private _entries: ConfigEntry[] = []; diff --git a/src/onboarding/onboarding-location.ts b/src/onboarding/onboarding-location.ts index 583ac73b60..1d031b81f3 100644 --- a/src/onboarding/onboarding-location.ts +++ b/src/onboarding/onboarding-location.ts @@ -1,4 +1,5 @@ import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list"; import { mdiCrosshairsGps, mdiMagnify, @@ -6,18 +7,21 @@ import { mdiMapSearchOutline, } from "@mdi/js"; import { - css, CSSResultGroup, - html, LitElement, - nothing, TemplateResult, + css, + html, + nothing, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { fireEvent } from "../common/dom/fire_event"; import type { LocalizeFunc } from "../common/translations/localize"; import "../components/ha-alert"; +import "../components/ha-circular-progress"; import "../components/ha-formfield"; +import "../components/ha-list-item"; import "../components/ha-radio"; import "../components/ha-textfield"; import type { HaTextField } from "../components/ha-textfield"; @@ -27,14 +31,13 @@ import type { MarkerLocation, } from "../components/map/ha-locations-editor"; import { ConfigUpdateValues, detectCoreConfig } from "../data/core"; -import { showConfirmationDialog } from "../dialogs/generic/show-dialog-box"; -import type { HomeAssistant } from "../types"; -import { fireEvent } from "../common/dom/fire_event"; import { OpenStreetMapPlace, reverseGeocode, searchPlaces, } from "../data/openstreetmap"; +import { showConfirmationDialog } from "../dialogs/generic/show-dialog-box"; +import type { HomeAssistant } from "../types"; import { onBoardingStyles } from "./styles"; const AMSTERDAM: [number, number] = [52.3731339, 4.8903147]; @@ -45,7 +48,7 @@ const LOCATION_MARKER_ID = "location"; class OnboardingLocation extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public onboardingLocalize!: LocalizeFunc; + @property({ attribute: false }) public onboardingLocalize!: LocalizeFunc; @state() private _working = false; @@ -292,8 +295,10 @@ class OnboardingLocation extends LitElement { if (ev.detail.id === LOCATION_MARKER_ID) { return; } - this._highlightedMarker = ev.detail.id; - const place = this._places!.find((plc) => plc.place_id === ev.detail.id)!; + this._highlightedMarker = Number(ev.detail.id); + const place = this._places!.find( + (plc) => plc.place_id === Number(ev.detail.id) + )!; this._location = [Number(place.lat), Number(place.lon)]; this._country = place.address.country_code.toUpperCase(); } diff --git a/src/onboarding/onboarding-restore-backup.ts b/src/onboarding/onboarding-restore-backup.ts index 58fd1dcb97..a9a004ce6c 100644 --- a/src/onboarding/onboarding-restore-backup.ts +++ b/src/onboarding/onboarding-restore-backup.ts @@ -62,7 +62,10 @@ class OnboardingRestoreBackup extends LitElement { try { await fetchInstallationType(); } catch (err: any) { - if ((err as Error).message === "unauthorized") { + if ( + (err as Error).message === "unauthorized" || + (err as Error).message === "not_found" + ) { window.location.replace("/"); } } diff --git a/src/onboarding/onboarding-welcome-link.ts b/src/onboarding/onboarding-welcome-link.ts index 7d4dbdd79d..d1d7972f15 100644 --- a/src/onboarding/onboarding-welcome-link.ts +++ b/src/onboarding/onboarding-welcome-link.ts @@ -10,6 +10,7 @@ import { state, } from "lit/decorators"; import "../components/ha-card"; +import "../components/ha-svg-icon"; @customElement("onboarding-welcome-link") class OnboardingWelcomeLink extends LitElement { @@ -17,7 +18,7 @@ class OnboardingWelcomeLink extends LitElement { @property() public iconPath!: string; - @property({ attribute: true, type: Boolean }) public noninteractive?: boolean; + @property({ type: Boolean }) public noninteractive = false; @queryAsync("mwc-ripple") private _ripple!: Promise; diff --git a/src/onboarding/onboarding-welcome-links.ts b/src/onboarding/onboarding-welcome-links.ts index 8be8372879..9c342e5083 100644 --- a/src/onboarding/onboarding-welcome-links.ts +++ b/src/onboarding/onboarding-welcome-links.ts @@ -19,9 +19,9 @@ import "./onboarding-welcome-link"; class OnboardingWelcomeLinks extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public localize!: LocalizeFunc; + @property({ attribute: false }) public localize!: LocalizeFunc; - @property({ type: Boolean }) public mobileApp!: boolean; + @property({ type: Boolean }) public mobileApp = false; protected render(): TemplateResult { return html` - + ${selCal.name} ` @@ -147,11 +150,18 @@ class PanelCalendar extends LitElement { > ${calendarItems} -
  • - - - ${this.hass.localize("ui.components.calendar.create_calendar")} - + ${this.hass.user?.is_admin + ? html`
  • + + + ${this.hass.localize( + "ui.components.calendar.create_calendar" + )} + ` + : nothing} ` : html`
    ${this.hass.localize("ui.components.calendar.my_calendars")} @@ -162,7 +172,7 @@ class PanelCalendar extends LitElement { .label=${this.hass.localize("ui.common.refresh")} @click=${this._handleRefresh} > - ${showPane + ${showPane && this.hass.user?.is_admin ? html`${calendarItems} + + ) { this._error = undefined; this._picture = (ev.target as HaPictureUpload).value; } private async _updateEntry() { + const create = !this._params!.entry; this._submitting = true; try { const values: AreaRegistryEntryMutableParams = { name: this._name.trim(), - picture: this._picture, + picture: this._picture || (create ? undefined : null), + icon: this._icon || (create ? undefined : null), aliases: this._aliases, }; - if (this._params!.entry) { - await this._params!.updateEntry!(values); - } else { + if (create) { await this._params!.createEntry!(values); + } else { + await this._params!.updateEntry!(values); } this.closeDialog(); } catch (err: any) { @@ -188,6 +207,7 @@ class DialogAreaDetail extends LitElement { haStyleDialog, css` ha-textfield, + ha-icon-picker, ha-picture-upload { display: block; margin-bottom: 16px; diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index a8a65fa10c..62abd48d4e 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -1,11 +1,9 @@ +import { consume } from "@lit-labs/context"; import "@material/mwc-button"; import "@material/mwc-list"; import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js"; -import { - HassEntity, - UnsubscribeFunc, -} from "home-assistant-js-websocket/dist/types"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { HassEntity } from "home-assistant-js-websocket/dist/types"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; @@ -18,33 +16,31 @@ import { afterNextRender } from "../../../common/util/render-status"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-next"; +import "../../../components/ha-list-item"; import { AreaRegistryEntry, deleteAreaRegistryEntry, - subscribeAreaRegistry, updateAreaRegistryEntry, } from "../../../data/area_registry"; import { AutomationEntity } from "../../../data/automation"; +import { fullEntitiesContext } from "../../../data/context"; import { - computeDeviceName, DeviceRegistryEntry, + computeDeviceName, sortDeviceRegistryByName, - subscribeDeviceRegistry, } from "../../../data/device_registry"; import { - computeEntityRegistryName, EntityRegistryEntry, + computeEntityRegistryName, sortEntityRegistryByName, - subscribeEntityRegistry, } from "../../../data/entity_registry"; import { SceneEntity } from "../../../data/scene"; import { ScriptEntity } from "../../../data/script"; -import { findRelated, RelatedResult } from "../../../data/search"; +import { RelatedResult, findRelated } from "../../../data/search"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog"; import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-subpage"; -import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import "../../logbook/ha-logbook"; @@ -52,7 +48,6 @@ import { loadAreaRegistryDetailDialog, showAreaRegistryDetailDialog, } from "./show-dialog-area-registry-detail"; -import "../../../components/ha-list-item"; declare type NameAndEntity = { name: string; @@ -60,35 +55,25 @@ declare type NameAndEntity = { }; @customElement("ha-config-area-page") -class HaConfigAreaPage extends SubscribeMixin(LitElement) { +class HaConfigAreaPage extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public areaId!: string; - @property({ type: Boolean, reflect: true }) public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; - @property({ type: Boolean }) public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property({ type: Boolean }) public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced = false; - @state() public _areas!: AreaRegistryEntry[]; - - @state() public _devices!: DeviceRegistryEntry[]; - - @state() public _entities!: EntityRegistryEntry[]; + @state() + @consume({ context: fullEntitiesContext, subscribe: true }) + _entityReg!: EntityRegistryEntry[]; @state() private _related?: RelatedResult; private _logbookTime = { recent: 86400 }; - private _area = memoizeOne( - ( - areaId: string, - areas: AreaRegistryEntry[] - ): AreaRegistryEntry | undefined => - areas.find((area) => area.area_id === areaId) - ); - private _memberships = memoizeOne( ( areaId: string, @@ -150,26 +135,12 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } } - protected hassSubscribe(): (UnsubscribeFunc | Promise)[] { - return [ - subscribeAreaRegistry(this.hass.connection, (areas) => { - this._areas = areas; - }), - subscribeDeviceRegistry(this.hass.connection, (entries) => { - this._devices = entries; - }), - subscribeEntityRegistry(this.hass.connection, (entries) => { - this._entities = entries; - }), - ]; - } - protected render() { - if (!this._areas || !this._devices || !this._entities) { + if (!this.hass.areas || !this.hass.devices || !this.hass.entities) { return nothing; } - const area = this._area(this.areaId, this._areas); + const area = this.hass.areas[this.areaId]; if (!area) { return html` @@ -182,8 +153,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { const memberships = this._memberships( this.areaId, - this._devices, - this._entities + Object.values(this.hass.devices), + this._entityReg ); const { devices, entities } = memberships; @@ -617,7 +588,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } private _renderScript(name: string, entityState: ScriptEntity) { - const entry = this._entities.find( + const entry = this._entityReg.find( (e) => e.entity_id === entityState.entity_id ); let url = `/config/script/show/${entityState.entity_id}`; @@ -657,7 +628,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } private async _deleteConfirm() { - const area = this._area(this.areaId, this._areas); + const area = this.hass.areas[this.areaId]; showConfirmationDialog(this, { title: this.hass.localize( "ui.panel.config.areas.delete.confirmation_title", @@ -686,7 +657,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { font-weight: 500; color: var(--secondary-text-color); } - img { border-radius: var(--ha-card-border-radius, 12px); width: 100%; diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts index 3623d58557..0dad5dc63c 100644 --- a/src/panels/config/areas/ha-config-areas-dashboard.ts +++ b/src/panels/config/areas/ha-config-areas-dashboard.ts @@ -1,7 +1,13 @@ import { mdiHelpCircle, mdiPlus } from "@mdi/js"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { + CSSResultGroup, + LitElement, + TemplateResult, + css, + html, + nothing, +} from "lit"; +import { customElement, property } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { formatListWithAnds } from "../../../common/string/format-list"; @@ -11,19 +17,9 @@ import "../../../components/ha-svg-icon"; import { AreaRegistryEntry, createAreaRegistryEntry, - subscribeAreaRegistry, } from "../../../data/area_registry"; -import { - DeviceRegistryEntry, - subscribeDeviceRegistry, -} from "../../../data/device_registry"; -import { - EntityRegistryEntry, - subscribeEntityRegistry, -} from "../../../data/entity_registry"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-tabs-subpage"; -import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant, Route } from "../../../types"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; @@ -33,33 +29,27 @@ import { } from "./show-dialog-area-registry-detail"; @customElement("ha-config-areas-dashboard") -export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { +export class HaConfigAreasDashboard extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public isWide?: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public route!: Route; - - @state() private _areas!: AreaRegistryEntry[]; - - @state() private _devices!: DeviceRegistryEntry[]; - - @state() private _entities!: EntityRegistryEntry[]; + @property({ attribute: false }) public route!: Route; private _processAreas = memoizeOne( ( - areas: AreaRegistryEntry[], - devices: DeviceRegistryEntry[], - entities: EntityRegistryEntry[] - ) => - areas.map((area) => { + areas: HomeAssistant["areas"], + devices: HomeAssistant["devices"], + entities: HomeAssistant["entities"] + ) => { + const processArea = (area: AreaRegistryEntry) => { let noDevicesInArea = 0; let noServicesInArea = 0; let noEntitiesInArea = 0; - for (const device of devices) { + for (const device of Object.values(devices)) { if (device.area_id === area.area_id) { if (device.entry_type === "service") { noServicesInArea++; @@ -69,7 +59,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { } } - for (const entity of entities) { + for (const entity of Object.values(entities)) { if (entity.area_id === area.area_id) { noEntitiesInArea++; } @@ -81,24 +71,22 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { services: noServicesInArea, entities: noEntitiesInArea, }; - }) + }; + + return Object.values(areas).map(processArea); + } ); - protected hassSubscribe(): (UnsubscribeFunc | Promise)[] { - return [ - subscribeAreaRegistry(this.hass.connection, (areas) => { - this._areas = areas; - }), - subscribeDeviceRegistry(this.hass.connection, (entries) => { - this._devices = entries; - }), - subscribeEntityRegistry(this.hass.connection, (entries) => { - this._entities = entries; - }), - ]; - } - protected render(): TemplateResult { + const areas = + !this.hass.areas || !this.hass.devices || !this.hass.entities + ? undefined + : this._processAreas( + this.hass.areas, + this.hass.devices, + this.hass.entities + ); + return html` + +
    + ${!area.picture && area.icon + ? html`` + : ""} +
    +

    ${area.name}

    +
    +
    + ${formatListWithAnds( + this.hass.locale, + [ + area.devices && + this.hass.localize( + "ui.panel.config.integrations.config_entry.devices", + { count: area.devices } + ), + area.services && + this.hass.localize( + "ui.panel.config.integrations.config_entry.services", + { count: area.services } + ), + area.entities && + this.hass.localize( + "ui.panel.config.integrations.config_entry.entities", + { count: area.entities } + ), + ].filter((v): v is string => Boolean(v)) + )} +
    +
    +
    + `; + } + protected firstUpdated(changedProps) { super.firstUpdated(changedProps); loadAreaRegistryDetailDialog(); } private _createArea() { - this._openDialog(); + this._openAreaDialog(); } private _showHelp() { @@ -202,7 +191,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { }); } - private _openDialog(entry?: AreaRegistryEntry) { + private _openAreaDialog(entry?: AreaRegistryEntry) { showAreaRegistryDetailDialog(this, { entry, createEntry: async (values) => @@ -213,14 +202,17 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { static get styles(): CSSResultGroup { return css` .container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - grid-gap: 16px 16px; padding: 8px 16px 16px; margin: 0 auto 64px auto; - max-width: 2000px; } - .container > * { + .areas { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + grid-gap: 16px 16px; + max-width: 2000px; + margin-bottom: 16px; + } + .areas > * { max-width: 500px; } ha-card { @@ -239,6 +231,12 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) { background-position: center; position: relative; } + .placeholder { + display: flex; + align-items: center; + justify-content: center; + --mdc-icon-size: 48px; + } .picture.placeholder::before { position: absolute; content: ""; diff --git a/src/panels/config/areas/ha-config-areas.ts b/src/panels/config/areas/ha-config-areas.ts index c5508eccfb..fd8959f050 100644 --- a/src/panels/config/areas/ha-config-areas.ts +++ b/src/panels/config/areas/ha-config-areas.ts @@ -11,11 +11,11 @@ import "./ha-config-areas-dashboard"; class HaConfigAreas extends HassRouterPage { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced = false; protected routerOptions: RouterOptions = { defaultPage: "dashboard", diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 0e58c0e8b2..6986aba974 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -3,6 +3,8 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { mdiAlertCircleCheck, + mdiArrowDown, + mdiArrowUp, mdiCheck, mdiContentCopy, mdiContentCut, @@ -12,7 +14,6 @@ import { mdiPlay, mdiPlayCircleOutline, mdiRenameBox, - mdiSort, mdiStopCircleOutline, } from "@mdi/js"; import deepClone from "deep-clone-simple"; @@ -29,8 +30,6 @@ import { classMap } from "lit/directives/class-map"; import { storage } from "../../../../common/decorators/storage"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeDomain } from "../../../../common/entity/compute_domain"; -import { domainIconWithoutDefault } from "../../../../common/entity/domain_icon"; import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; import "../../../../components/ha-alert"; @@ -38,6 +37,7 @@ import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; +import "../../../../components/ha-service-icon"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { ACTION_ICONS, YAML_ONLY_ACTION_TYPES } from "../../../../data/action"; import { AutomationClipboard } from "../../../../data/automation"; @@ -57,7 +57,7 @@ import { showPromptDialog, } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; -import type { HomeAssistant } from "../../../../types"; +import type { HomeAssistant, ItemPath } from "../../../../types"; import { showToast } from "../../../../util/toast"; import "./types/ha-automation-action-activate_scene"; import "./types/ha-automation-action-choose"; @@ -70,6 +70,7 @@ import "./types/ha-automation-action-parallel"; import "./types/ha-automation-action-play_media"; import "./types/ha-automation-action-repeat"; import "./types/ha-automation-action-service"; +import "./types/ha-automation-action-set_conversation_response"; import "./types/ha-automation-action-stop"; import "./types/ha-automation-action-wait_for_trigger"; import "./types/ha-automation-action-wait_template"; @@ -121,15 +122,17 @@ const preventDefault = (ev) => ev.preventDefault(); export default class HaAutomationActionRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public action!: Action; + @property({ attribute: false }) public action!: Action; @property({ type: Boolean }) public narrow = false; @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public hideMenu = false; + @property({ type: Array }) public path?: ItemPath; - @property({ type: Boolean }) public reOrderMode = false; + @property({ type: Boolean }) public first?: boolean; + + @property({ type: Boolean }) public last?: boolean; @storage({ key: "automationClipboard", @@ -176,36 +179,43 @@ export default class HaAutomationActionRow extends LitElement { } protected render() { + if (!this.action) return nothing; + const type = getType(this.action); const yamlMode = this._yamlMode; return html` ${this.action.enabled === false - ? html`
    - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.disabled" - )} -
    ` - : ""} + ? html` +
    + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.disabled" + )} +
    + ` + : nothing}

    - + ${type === "service" && + "service" in this.action && + this.action.service + ? html`` + : html``} ${capitalizeFirstLetter( describeAction(this.hass, this._entityReg, this.action) )}

    + ${type !== "condition" && (this.action as NonConditionAction).continue_on_error === true ? html`
    @@ -217,142 +227,134 @@ export default class HaAutomationActionRow extends LitElement {
    ` : nothing} - ${this.hideMenu - ? "" - : html` - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.run" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.rename" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.re_order" - )} - - + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.run" + )} + + -
  • + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.rename" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - +
  • - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.copy" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.cut" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + -
  • + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - )} - ${!yamlMode - ? html`` - : ``} - + + ${this.hass.localize("ui.panel.config.automation.editor.move_up")} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.move_down" + )} + -
  • +
  • - - ${this.action.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    - `} + + ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} + ${!yamlMode + ? html`` + : ``} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${yamlMode + ? html`` + : ``} + + +
  • + + + ${this.action.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    `} @@ -435,30 +437,33 @@ export default class HaAutomationActionRow extends LitElement { await this._renameAction(); break; case 2: - fireEvent(this, "re-order"); + fireEvent(this, "duplicate"); break; case 3: - fireEvent(this, "duplicate"); + this._setClipboard(); break; case 4: - this._setClipboard(); - break; - case 5: this._setClipboard(); fireEvent(this, "value-changed", { value: null }); break; + case 5: + fireEvent(this, "move-up"); + break; case 6: + fireEvent(this, "move-down"); + break; + case 7: this._switchUiMode(); this.expand(); break; - case 7: + case 8: this._switchYamlMode(); this.expand(); break; - case 8: + case 9: this._onDisable(); break; - case 9: + case 10: this._onDelete(); break; } @@ -625,6 +630,8 @@ export default class HaAutomationActionRow extends LitElement { color: var(--secondary-text-color); opacity: 0.9; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } } .card-content { @@ -640,6 +647,9 @@ export default class HaAutomationActionRow extends LitElement { mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } + mwc-list-item.hidden { + display: none; + } .warning ul { margin: 4px 0; } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index ce312a7bd3..3cf22ec221 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -1,20 +1,26 @@ -import "@material/mwc-button"; -import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; +import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; -import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit"; -import { customElement, property } from "lit/decorators"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import type { SortableEvent } from "sortablejs"; import { storage } from "../../../../common/decorators/storage"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { listenMediaQuery } from "../../../../common/dom/media_query"; +import { nestedArrayMove } from "../../../../common/util/array-move"; import "../../../../components/ha-button"; +import "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import { getService, isService } from "../../../../data/action"; import type { AutomationClipboard } from "../../../../data/automation"; import { Action } from "../../../../data/script"; -import { sortableStyles } from "../../../../resources/ha-sortable-style"; -import type { SortableInstance } from "../../../../resources/sortable"; -import { HomeAssistant } from "../../../../types"; +import { HomeAssistant, ItemPath } from "../../../../types"; import { PASTE_VALUE, showAddAutomationElementDialog, @@ -30,11 +36,11 @@ export default class HaAutomationAction extends LitElement { @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public nested = false; + @property({ type: Array }) public path?: ItemPath; - @property() public actions!: Action[]; + @property({ attribute: false }) public actions!: Action[]; - @property({ type: Boolean }) public reOrderMode = false; + @state() private _showReorder: boolean = false; @storage({ key: "automationClipboard", @@ -48,77 +54,66 @@ export default class HaAutomationAction extends LitElement { private _actionKeys = new WeakMap(); - private _sortable?: SortableInstance; + private _unsubMql?: () => void; + + public connectedCallback() { + super.connectedCallback(); + this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => { + this._showReorder = matches; + }); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._unsubMql?.(); + this._unsubMql = undefined; + } + + private get nested() { + return this.path !== undefined; + } protected render() { return html` - ${this.reOrderMode && !this.nested - ? html` - - ${this.hass.localize( - "ui.panel.config.automation.editor.re_order_mode.description_actions" - )} - - ${this.hass.localize( - "ui.panel.config.automation.editor.re_order_mode.exit" - )} - - - ` - : null} -
    - ${repeat( - this.actions, - (action) => this._getKey(action), - (action, idx) => html` - - ${this.reOrderMode - ? html` - - -
    - -
    - ` - : ""} -
    - ` - )} -
    + +
    + ${repeat( + this.actions, + (action) => this._getKey(action), + (action, idx) => html` + + ${this._showReorder && !this.disabled + ? html` +
    + +
    + ` + : nothing} +
    + ` + )} +
    +
    +
    { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._dragged(evt); - }, - }); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private _getKey(action: Action) { if (!this._actionKeys.has(action)) { this._actionKeys.set(action, Math.random().toString()); @@ -262,16 +213,28 @@ export default class HaAutomationAction extends LitElement { this._move(index, newIndex); } - private _dragged(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) return; - this._move(ev.oldIndex!, ev.newIndex!); + private _move( + oldIndex: number, + newIndex: number, + oldPath?: ItemPath, + newPath?: ItemPath + ) { + const actions = nestedArrayMove( + this.actions, + oldIndex, + newIndex, + oldPath, + newPath + ); + + fireEvent(this, "value-changed", { value: actions }); } - private _move(index: number, newIndex: number) { - const actions = this.actions.concat(); - const action = actions.splice(index, 1)[0]; - actions.splice(newIndex, 0, action); - fireEvent(this, "value-changed", { value: actions }); + private _actionMoved(ev: CustomEvent): void { + if (this.nested) return; + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + this._move(oldIndex, newIndex, oldPath, newPath); } private _actionChanged(ev: CustomEvent) { @@ -302,39 +265,36 @@ export default class HaAutomationAction extends LitElement { } static get styles(): CSSResultGroup { - return [ - sortableStyles, - css` - ha-automation-action-row { - display: block; - margin-bottom: 16px; - scroll-margin-top: 48px; - } - ha-svg-icon { - height: 20px; - } - ha-alert { - display: block; - margin-bottom: 16px; - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } - .handle { - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - padding: 12px; - } - .handle ha-svg-icon { - pointer-events: none; - height: 24px; - } - .buttons { - display: flex; - flex-wrap: wrap; - gap: 8px; - } - `, - ]; + return css` + ha-automation-action-row { + display: block; + margin-bottom: 16px; + scroll-margin-top: 48px; + } + ha-svg-icon { + height: 20px; + } + ha-alert { + display: block; + margin-bottom: 16px; + border-radius: var(--ha-card-border-radius, 12px); + overflow: hidden; + } + .handle { + padding: 12px 4px; + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; + } + .handle ha-svg-icon { + pointer-events: none; + height: 24px; + } + .buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + `; } } diff --git a/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts b/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts index 5e61ca2034..d8664f2c35 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts @@ -14,7 +14,7 @@ export class HaSceneAction extends LitElement implements ActionElement { @property({ type: Boolean }) public disabled = false; - @property() public action!: SceneAction; + @property({ attribute: false }) public action!: SceneAction; public static get defaultConfig(): SceneAction { return { diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index 5c0ee07637..56dfe10da7 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -1,29 +1,38 @@ import { consume } from "@lit-labs/context"; -import type { SortableEvent } from "sortablejs"; +import type { ActionDetail } from "@material/mwc-list"; import { - mdiDotsVertical, - mdiRenameBox, - mdiSort, + mdiArrowDown, + mdiArrowUp, mdiContentDuplicate, mdiDelete, - mdiPlus, - mdiArrowUp, - mdiArrowDown, + mdiDotsVertical, mdiDrag, + mdiPlus, + mdiRenameBox, } from "@mdi/js"; import deepClone from "deep-clone-simple"; -import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; import { customElement, property, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import type { ActionDetail } from "@material/mwc-list"; -import type { SortableInstance } from "../../../../../resources/sortable"; import { ensureArray } from "../../../../../common/array/ensure-array"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import { listenMediaQuery } from "../../../../../common/dom/media_query"; import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter"; import "../../../../../components/ha-button"; -import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-button-menu"; +import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-sortable"; import { Condition } from "../../../../../data/automation"; +import { describeCondition } from "../../../../../data/automation_i18n"; +import { fullEntitiesContext } from "../../../../../data/context"; +import { EntityRegistryEntry } from "../../../../../data/entity_registry"; import { Action, ChooseAction, @@ -34,12 +43,8 @@ import { showPromptDialog, } from "../../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../../resources/styles"; -import { HomeAssistant } from "../../../../../types"; +import { HomeAssistant, ItemPath } from "../../../../../types"; import { ActionElement } from "../ha-automation-action-row"; -import { describeCondition } from "../../../../../data/automation_i18n"; -import { fullEntitiesContext } from "../../../../../data/context"; -import { EntityRegistryEntry } from "../../../../../data/entity_registry"; -import { sortableStyles } from "../../../../../resources/ha-sortable-style"; const preventDefault = (ev) => ev.preventDefault(); @@ -49,9 +54,9 @@ export class HaChooseAction extends LitElement implements ActionElement { @property({ type: Boolean }) public disabled = false; - @property() public action!: ChooseAction; + @property({ attribute: false }) public path?: ItemPath; - @property({ type: Boolean }) public reOrderMode = false; + @property({ attribute: false }) public action!: ChooseAction; @state() private _showDefault = false; @@ -61,9 +66,24 @@ export class HaChooseAction extends LitElement implements ActionElement { @consume({ context: fullEntitiesContext, subscribe: true }) _entityReg!: EntityRegistryEntry[]; + @state() private _showReorder: boolean = false; + private _expandLast = false; - private _sortable?: SortableInstance; + private _unsubMql?: () => void; + + public connectedCallback() { + super.connectedCallback(); + this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => { + this._showReorder = matches; + }); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._unsubMql?.(); + this._unsubMql = undefined; + } public static get defaultConfig() { return { choose: [{ conditions: [], sequence: [] }] }; @@ -100,157 +120,164 @@ export class HaChooseAction extends LitElement implements ActionElement { const action = this.action; return html` -
    - ${repeat( - action.choose ? ensureArray(action.choose) : [], - (option) => option, - (option, idx) => - html` - -

    - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.type.choose.option", - { number: idx + 1 } - )}: - ${option.alias || - (this._expandedStates[idx] - ? "" - : this._getDescription(option))} -

    - ${this.reOrderMode - ? html` + +
    + ${repeat( + action.choose ? ensureArray(action.choose) : [], + (option) => option, + (option, idx) => html` +
    + + +

    + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.type.choose.option", + { number: idx + 1 } + )}: + ${option.alias || + (this._expandedStates[idx] + ? "" + : this._getDescription(option))} +

    + ${this._showReorder && !this.disabled + ? html` +
    + +
    + ` + : nothing} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.rename" + )} + + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + + + + ${this.hass.localize( "ui.panel.config.automation.editor.move_up" )} - .path=${mdiArrowUp} - @click=${this._moveUp} - .disabled=${idx === 0} - > - + + + + ${this.hass.localize( "ui.panel.config.automation.editor.move_down" )} - .path=${mdiArrowDown} - @click=${this._moveDown} - .disabled=${idx === - ensureArray(this.action.choose).length - 1} - > -
    - -
    - ` - : html` - +
    + + - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.rename" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.re_order" - )} - - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.type.choose.remove_option" - )} - - -
    - `} -
    -

    - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.type.choose.conditions" - )}: -

    - ( - option.conditions - )} - .reOrderMode=${this.reOrderMode} - .disabled=${this.disabled} - .hass=${this.hass} - .idx=${idx} - @value-changed=${this._conditionChanged} - > -

    - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.type.choose.sequence" - )}: -

    - -
    -
    -
    ` - )} -
    + slot="graphic" + .path=${mdiDelete} + > + + + +
    +

    + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.type.choose.conditions" + )}: +

    + ( + option.conditions + )} + .disabled=${this.disabled} + .hass=${this.hass} + .idx=${idx} + @value-changed=${this._conditionChanged} + > +

    + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.type.choose.sequence" + )}: +

    + +
    + + +
    + ` + )} +
    + { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._dragged(evt); - }, - }); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; + this._showDefault = true; + const defaultAction = ev.detail.value as Action[]; + const newValue: ChooseAction = { + ...this.action, + default: defaultAction, + }; + if (defaultAction.length === 0) { + delete newValue.default; + } + fireEvent(this, "value-changed", { value: newValue }); } static get styles(): CSSResultGroup { return [ haStyle, - sortableStyles, css` - ha-card { + .option { margin: 0 0 16px 0; } .add-card mwc-button { @@ -523,6 +513,12 @@ export class HaChooseAction extends LitElement implements ActionElement { --expansion-panel-summary-padding: 0 0 0 8px; --expansion-panel-content-padding: 0; } + mwc-list-item[disabled] { + --mdc-theme-text-primary-on-background: var(--disabled-text-color); + } + mwc-list-item.hidden { + display: none; + } h3 { margin: 0; font-size: inherit; @@ -543,9 +539,9 @@ export class HaChooseAction extends LitElement implements ActionElement { padding: 0 16px 16px 16px; } .handle { + padding: 12px 4px; cursor: move; /* fallback if grab cursor is unsupported */ cursor: grab; - padding: 12px; } .handle ha-svg-icon { pointer-events: none; diff --git a/src/panels/config/automation/action/types/ha-automation-action-condition.ts b/src/panels/config/automation/action/types/ha-automation-action-condition.ts index d62176812d..3b098e9a86 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-condition.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-condition.ts @@ -18,7 +18,7 @@ export class HaConditionAction extends LitElement implements ActionElement { @property({ type: Boolean }) public disabled = false; - @property() public action!: Condition; + @property({ attribute: false }) public action!: Condition; public static get defaultConfig() { return { condition: "state" }; diff --git a/src/panels/config/automation/action/types/ha-automation-action-event.ts b/src/panels/config/automation/action/types/ha-automation-action-event.ts index c2aebdd022..15ed85aea4 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-event.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-event.ts @@ -16,7 +16,7 @@ export class HaEventAction extends LitElement implements ActionElement { @property({ type: Boolean }) public disabled = false; - @property() public action!: EventAction; + @property({ attribute: false }) public action!: EventAction; @query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor; diff --git a/src/panels/config/automation/action/types/ha-automation-action-if.ts b/src/panels/config/automation/action/types/ha-automation-action-if.ts index 195590a689..b68cf1f157 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-if.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-if.ts @@ -4,7 +4,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-textfield"; import { Action, IfAction } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; -import type { HomeAssistant } from "../../../../../types"; +import type { HomeAssistant, ItemPath } from "../../../../../types"; import type { Condition } from "../../../../lovelace/common/validate-condition"; import "../ha-automation-action"; import type { ActionElement } from "../ha-automation-action-row"; @@ -15,9 +15,9 @@ export class HaIfAction extends LitElement implements ActionElement { @property({ type: Boolean }) public disabled = false; - @property({ attribute: false }) public action!: IfAction; + @property({ attribute: false }) public path?: ItemPath; - @property({ type: Boolean }) public reOrderMode = false; + @property({ attribute: false }) public action!: IfAction; @state() private _showElse = false; @@ -38,9 +38,8 @@ export class HaIfAction extends LitElement implements ActionElement { )}*: [ { @@ -60,20 +63,22 @@ export class HaRepeatAction extends LitElement implements ActionElement { name: "count", required: true, selector: template - ? ({ template: {} } as const) - : ({ number: { mode: "box", min: 1 } } as const), + ? { template: {} } + : { number: { mode: "box", min: 1 } }, }, - ] as const) + ] as const satisfies readonly HaFormSchema[]) : []), ...(type === "until" || type === "while" ? ([ { name: type, selector: { - condition: { nested: true, reorder_mode: reOrderMode }, + condition: { + path: [...(path ?? []), "repeat", type], + }, }, }, - ] as const) + ] as const satisfies readonly HaFormSchema[]) : []), ...(type === "for_each" ? ([ @@ -82,13 +87,17 @@ export class HaRepeatAction extends LitElement implements ActionElement { required: true, selector: { object: {} }, }, - ] as const) + ] as const satisfies readonly HaFormSchema[]) : []), { name: "sequence", - selector: { action: { nested: true, reorder_mode: reOrderMode } }, + selector: { + action: { + path: [...(path ?? []), "repeat", "sequence"], + }, + }, }, - ] as const + ] as const satisfies readonly HaFormSchema[] ); protected render() { @@ -97,11 +106,12 @@ export class HaRepeatAction extends LitElement implements ActionElement { const schema = this._schema( this.hass.localize, type ?? "count", - this.reOrderMode, "count" in action && typeof action.count === "string" ? isTemplate(action.count) - : false + : false, + this.path ); + const data = { ...action, type }; return html` + `; + } + + private _computeLabelCallback = (): string => + this.hass.localize( + "ui.panel.config.automation.editor.actions.type.set_conversation_response.label" + ); +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-action-set_conversation_response": HaSetConversationResponseAction; + } +} diff --git a/src/panels/config/automation/action/types/ha-automation-action-stop.ts b/src/panels/config/automation/action/types/ha-automation-action-stop.ts index 6ca7177e4f..c98a5e2cff 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-stop.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-stop.ts @@ -10,7 +10,7 @@ import { ActionElement } from "../ha-automation-action-row"; export class HaStopAction extends LitElement implements ActionElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public action!: StopAction; + @property({ attribute: false }) public action!: StopAction; @property({ type: Boolean }) public disabled = false; diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index b6bd373831..52b8476a31 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -8,7 +8,7 @@ import "../../../../../components/ha-duration-input"; import "../../../../../components/ha-formfield"; import "../../../../../components/ha-textfield"; import { WaitForTriggerAction } from "../../../../../data/script"; -import { HomeAssistant } from "../../../../../types"; +import { HomeAssistant, ItemPath } from "../../../../../types"; import "../../trigger/ha-automation-trigger"; import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; @@ -23,7 +23,7 @@ export class HaWaitForTriggerAction @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public reOrderMode = false; + @property({ attribute: false }) public path?: ItemPath; public static get defaultConfig() { return { wait_for_trigger: [] }; @@ -55,12 +55,11 @@ export class HaWaitForTriggerAction > `; diff --git a/src/panels/config/automation/add-automation-element-dialog.ts b/src/panels/config/automation/add-automation-element-dialog.ts index cf5874f80c..4145a3397b 100644 --- a/src/panels/config/automation/add-automation-element-dialog.ts +++ b/src/panels/config/automation/add-automation-element-dialog.ts @@ -1,10 +1,12 @@ import "@material/mwc-list/mwc-list"; +import "@material/web/divider/divider"; import { mdiClose, mdiContentPaste, mdiPlus } from "@mdi/js"; import Fuse, { IFuseOptions } from "fuse.js"; import { CSSResultGroup, LitElement, PropertyValues, + TemplateResult, css, html, nothing, @@ -15,17 +17,20 @@ import { repeat } from "lit/directives/repeat"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; -import { domainIconWithoutDefault } from "../../../common/entity/domain_icon"; -import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; +import { computeDomain } from "../../../common/entity/compute_domain"; import { stringCompare } from "../../../common/string/compare"; import { LocalizeFunc } from "../../../common/translations/localize"; +import { deepEqual } from "../../../common/util/deep-equal"; import "../../../components/ha-dialog"; import type { HaDialog } from "../../../components/ha-dialog"; import "../../../components/ha-dialog-header"; +import "../../../components/ha-domain-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button-prev"; import "../../../components/ha-icon-next"; -import "../../../components/ha-list-item"; +import "../../../components/ha-list-item-new"; +import "../../../components/ha-list-new"; +import "../../../components/ha-service-icon"; import "../../../components/search-input"; import { ACTION_GROUPS, @@ -36,6 +41,7 @@ import { } from "../../../data/action"; import { AutomationElementGroup } from "../../../data/automation"; import { CONDITION_GROUPS, CONDITION_ICONS } from "../../../data/condition"; +import { getServiceIcons } from "../../../data/icons"; import { IntegrationManifest, domainToName, @@ -45,13 +51,10 @@ import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger"; import { HassDialog } from "../../../dialogs/make-dialog-manager"; import { haStyle, haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import { brandsUrl } from "../../../util/brands-url"; import { AddAutomationElementDialogParams, PASTE_VALUE, } from "./show-add-automation-element-dialog"; -import { computeDomain } from "../../../common/entity/compute_domain"; -import { deepEqual } from "../../../common/util/deep-equal"; const TYPES = { trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS }, @@ -69,8 +72,8 @@ interface ListItem { key: string; name: string; description: string; - icon?: string; - image?: string; + iconPath?: string; + icon?: TemplateResult; group: boolean; } @@ -123,6 +126,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { this.hass.loadBackendTranslation("services"); this._fetchManifests(); this._calculateUsedDomains(); + getServiceIcons(this.hass); } this._fullScreen = matchMedia( "all and (max-width: 450px), all and (max-height: 500px)" @@ -173,7 +177,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { options.members ? "groups" : "type" }.${key}.description${options.members ? "" : ".picker"}` ), - icon: options.icon || TYPES[type].icons[key], + iconPath: options.icon || TYPES[type].icons[key], }); private _getFilteredItems = memoizeOne( @@ -313,17 +317,15 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { (!domainUsed && manifest?.integration_type === "entity") || !["helper", "entity"].includes(manifest?.integration_type || ""))) ) { - const icon = domainIconWithoutDefault(domain); result.push({ group: true, - icon, - image: !icon - ? brandsUrl({ - domain, - type: "icon", - darkOptimized: this.hass.themes?.darkMode, - }) - : undefined, + icon: html` + + `, key: `${SERVICE_PREFIX}${domain}`, name: domainToName(localize, domain, manifest), description: "", @@ -357,17 +359,14 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { const services_keys = Object.keys(services[dmn]); for (const service of services_keys) { - const icon = domainIconWithoutDefault(dmn); result.push({ group: false, - icon, - image: !icon - ? brandsUrl({ - domain: dmn, - type: "icon", - darkOptimized: this.hass.themes?.darkMode, - }) - : undefined, + icon: html` + + `, key: `${SERVICE_PREFIX}${dmn}.${service}`, name: `${domain ? "" : `${domainToName(localize, dmn)}: `}${ this.hass.localize(`component.${dmn}.services.${service}.name`) || @@ -433,7 +432,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { protected _opened(): void { // Store the width and height so that when we search, box doesn't jump const boundingRect = - this.shadowRoot!.querySelector("mwc-list")?.getBoundingClientRect(); + this.shadowRoot!.querySelector("ha-list-new")?.getBoundingClientRect(); this._width = boundingRect?.width; this._height = boundingRect?.height; } @@ -525,11 +524,8 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { )} >
    - item.key === this._params!.clipboardItem)) - ? html` ${this.hass.localize( `ui.panel.config.automation.editor.${this._params.type}s.paste` @@ -557,49 +550,44 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { )} - -
  • ` + > + + ` : ""} ${repeat( items, (item) => item.key, (item) => html` - - ${item.name} - ${item.description} +
    ${item.name}
    +
    ${item.description}
    ${item.icon - ? html`` - : html``} + ? html`${item.icon}` + : item.iconPath + ? html`` + : nothing} ${item.group - ? html`` + ? html`` : html``} -
    + ` )} -
    + `; } @@ -619,9 +607,6 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { } private _selected(ev) { - if (!shouldHandleRequestSelectedEvent(ev)) { - return; - } this._dialog!.scrollToPos(0, 0); const item = ev.currentTarget; if (item.group) { @@ -654,9 +639,14 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { ha-icon-next { width: 24px; } - mwc-list { + ha-list-new { max-height: 468px; max-width: 100vw; + --md-list-item-leading-space: 24px; + --md-list-item-trailing-space: 24px; + } + ha-list-item-new img { + width: 24px; } search-input { display: block; diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index 3f5a8ea650..f57806f896 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -3,13 +3,14 @@ import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; +import { nestedArrayMove } from "../../../common/util/array-move"; +import "../../../components/ha-alert"; import "../../../components/ha-blueprint-picker"; import "../../../components/ha-card"; import "../../../components/ha-circular-progress"; import "../../../components/ha-markdown"; import "../../../components/ha-selector/ha-selector"; import "../../../components/ha-settings-row"; -import "../../../components/ha-alert"; import { BlueprintAutomationConfig } from "../../../data/automation"; import { BlueprintOrError, @@ -24,15 +25,15 @@ import "../ha-config-section"; export class HaBlueprintAutomationEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; @property({ type: Boolean }) public disabled = false; - @property({ reflect: true, type: Boolean }) public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; - @property() public config!: BlueprintAutomationConfig; + @property({ attribute: false }) public config!: BlueprintAutomationConfig; - @property() public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: HassEntity; @state() private _blueprints?: Blueprints; @@ -119,8 +120,23 @@ export class HaBlueprintAutomationEditor extends LitElement { ${blueprint?.metadata?.input && Object.keys(blueprint.metadata.input).length ? Object.entries(blueprint.metadata.input).map( - ([key, value]) => - html` + ([key, value]) => { + const selector = value?.selector ?? { text: undefined }; + const type = Object.keys(selector)[0]; + const enhancedSelector = [ + "action", + "condition", + "trigger", + ].includes(type) + ? { + [type]: { + ...selector[type], + path: [key], + }, + } + : selector; + + return html` ${value?.name || key} ${html``} - ` + `; + } ) : html`

    ${this.hass.localize( @@ -197,6 +215,29 @@ export class HaBlueprintAutomationEditor extends LitElement { }); } + private _itemMoved(ev) { + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + + const input = nestedArrayMove( + this.config.use_blueprint.input, + oldIndex, + newIndex, + oldPath, + newPath + ); + + fireEvent(this, "value-changed", { + value: { + ...this.config, + use_blueprint: { + ...this.config.use_blueprint, + input, + }, + }, + }); + } + private async _enable(): Promise { if (!this.hass || !this.stateObj) { return; @@ -259,6 +300,10 @@ export class HaBlueprintAutomationEditor extends LitElement { margin-bottom: 16px; display: block; } + ha-alert.re-order { + border-radius: var(--ha-card-border-radius, 12px); + overflow: hidden; + } `, ]; } diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index 81b4eec95b..f39092d940 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -7,7 +7,7 @@ import "../../../../components/ha-yaml-editor"; import type { Condition } from "../../../../data/automation"; import { expandConditionWithShorthand } from "../../../../data/automation"; import { haStyle } from "../../../../resources/styles"; -import type { HomeAssistant } from "../../../../types"; +import type { HomeAssistant, ItemPath } from "../../../../types"; import "./types/ha-automation-condition-and"; import "./types/ha-automation-condition-device"; import "./types/ha-automation-condition-not"; @@ -30,7 +30,7 @@ export default class HaAutomationConditionEditor extends LitElement { @property({ type: Boolean }) public yamlMode = false; - @property({ type: Boolean }) public reOrderMode = false; + @property({ type: Array }) public path?: ItemPath; private _processedCondition = memoizeOne((condition) => expandConditionWithShorthand(condition) @@ -67,8 +67,8 @@ export default class HaAutomationConditionEditor extends LitElement { { hass: this.hass, condition: condition, - reOrderMode: this.reOrderMode, disabled: this.disabled, + path: this.path, } )}

    diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 1b51a9d278..df6a0e59fe 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -2,6 +2,8 @@ import { consume } from "@lit-labs/context"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { + mdiArrowDown, + mdiArrowUp, mdiCheck, mdiContentCopy, mdiContentCut, @@ -11,7 +13,6 @@ import { mdiFlask, mdiPlayCircleOutline, mdiRenameBox, - mdiSort, mdiStopCircleOutline, } from "@mdi/js"; import deepClone from "deep-clone-simple"; @@ -39,7 +40,7 @@ import { showPromptDialog, } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; -import { HomeAssistant } from "../../../../types"; +import { HomeAssistant, ItemPath } from "../../../../types"; import "./ha-automation-condition-editor"; export interface ConditionElement extends LitElement { @@ -77,14 +78,16 @@ export const handleChangeEvent = ( export default class HaAutomationConditionRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public condition!: Condition; - - @property({ type: Boolean }) public hideMenu = false; - - @property({ type: Boolean }) public reOrderMode = false; + @property({ attribute: false }) public condition!: Condition; @property({ type: Boolean }) public disabled = false; + @property({ type: Array }) public path?: ItemPath; + + @property({ type: Boolean }) public first?: boolean; + + @property({ type: Boolean }) public last?: boolean; + @storage({ key: "automationClipboard", state: false, @@ -109,14 +112,17 @@ export default class HaAutomationConditionRow extends LitElement { if (!this.condition) { return nothing; } + return html` ${this.condition.enabled === false - ? html`
    - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.disabled" - )} -
    ` + ? html` +
    + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.disabled" + )} +
    + ` : ""} @@ -131,138 +137,135 @@ export default class HaAutomationConditionRow extends LitElement { - ${this.hideMenu - ? "" - : html` - - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.test" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.rename" - )} - - + + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.re_order" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.test" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.rename" + )} + + -
  • +
  • - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.copy" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.cut" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + -
  • + + ${this.hass.localize("ui.panel.config.automation.editor.move_up")} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - )} - ${!this._yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.move_down" + )} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${this._yamlMode - ? html`` - : ``} - +
  • -
  • + + ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} + ${!this._yamlMode + ? html`` + : ``} + - - ${this.condition.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    - `} + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${this._yamlMode + ? html`` + : ``} + + +
  • + + + ${this.condition.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    @@ -344,30 +347,33 @@ export default class HaAutomationConditionRow extends LitElement { await this._renameCondition(); break; case 2: - fireEvent(this, "re-order"); + fireEvent(this, "duplicate"); break; case 3: - fireEvent(this, "duplicate"); + this._setClipboard(); break; case 4: - this._setClipboard(); - break; - case 5: this._setClipboard(); fireEvent(this, "value-changed", { value: null }); break; + case 5: + fireEvent(this, "move-up"); + break; case 6: + fireEvent(this, "move-down"); + break; + case 7: this._switchUiMode(); this.expand(); break; - case 7: + case 8: this._switchYamlMode(); this.expand(); break; - case 8: + case 9: this._onDisable(); break; - case 9: + case 10: this._onDelete(); break; } @@ -533,6 +539,8 @@ export default class HaAutomationConditionRow extends LitElement { color: var(--secondary-text-color); opacity: 0.9; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } } .card-content { @@ -547,6 +555,9 @@ export default class HaAutomationConditionRow extends LitElement { mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } + mwc-list-item.hidden { + display: none; + } .testing { position: absolute; top: 0px; diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 27364b92a3..916d32f16b 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -1,5 +1,4 @@ -import "@material/mwc-button"; -import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; +import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import { CSSResultGroup, @@ -9,21 +8,21 @@ import { html, nothing, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import type { SortableEvent } from "sortablejs"; import { storage } from "../../../../common/decorators/storage"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { listenMediaQuery } from "../../../../common/dom/media_query"; +import { nestedArrayMove } from "../../../../common/util/array-move"; import "../../../../components/ha-button"; import "../../../../components/ha-button-menu"; +import "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import type { AutomationClipboard, Condition, } from "../../../../data/automation"; -import { sortableStyles } from "../../../../resources/ha-sortable-style"; -import type { SortableInstance } from "../../../../resources/sortable"; -import type { HomeAssistant } from "../../../../types"; +import type { HomeAssistant, ItemPath } from "../../../../types"; import { PASTE_VALUE, showAddAutomationElementDialog, @@ -35,13 +34,13 @@ import type HaAutomationConditionRow from "./ha-automation-condition-row"; export default class HaAutomationCondition extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public conditions!: Condition[]; + @property({ attribute: false }) public conditions!: Condition[]; @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public nested = false; + @property({ type: Array }) public path?: ItemPath; - @property({ type: Boolean }) public reOrderMode = false; + @state() private _showReorder: boolean = false; @storage({ key: "automationClipboard", @@ -55,17 +54,22 @@ export default class HaAutomationCondition extends LitElement { private _conditionKeys = new WeakMap(); - private _sortable?: SortableInstance; + private _unsubMql?: () => void; + + public connectedCallback() { + super.connectedCallback(); + this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => { + this._showReorder = matches; + }); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._unsubMql?.(); + this._unsubMql = undefined; + } protected updated(changedProperties: PropertyValues) { - if (changedProperties.has("reOrderMode")) { - if (this.reOrderMode) { - this._createSortable(); - } else { - this._destroySortable(); - } - } - if (!changedProperties.has("conditions")) { return; } @@ -102,79 +106,53 @@ export default class HaAutomationCondition extends LitElement { } } + private get nested() { + return this.path !== undefined; + } + protected render() { if (!Array.isArray(this.conditions)) { return nothing; } return html` - ${this.reOrderMode && !this.nested - ? html` - - ${this.hass.localize( - "ui.panel.config.automation.editor.re_order_mode.description_conditions" - )} - - ${this.hass.localize( - "ui.panel.config.automation.editor.re_order_mode.exit" - )} - - - ` - : null} -
    - ${repeat( - this.conditions.filter((c) => typeof c === "object"), - (condition) => this._getKey(condition), - (cond, idx) => html` - - ${this.reOrderMode - ? html` - - -
    - -
    - ` - : ""} -
    - ` - )} -
    + +
    + ${repeat( + this.conditions.filter((c) => typeof c === "object"), + (condition) => this._getKey(condition), + (cond, idx) => html` + + ${this._showReorder && !this.disabled + ? html` +
    + +
    + ` + : nothing} +
    + ` + )} +
    +
    { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._dragged(evt); - }, - } - ); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private _getKey(condition: Condition) { if (!this._conditionKeys.has(condition)) { this._conditionKeys.set(condition, Math.random().toString()); @@ -298,16 +236,28 @@ export default class HaAutomationCondition extends LitElement { this._move(index, newIndex); } - private _dragged(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) return; - this._move(ev.oldIndex!, ev.newIndex!); + private _move( + oldIndex: number, + newIndex: number, + oldPath?: ItemPath, + newPath?: ItemPath + ) { + const conditions = nestedArrayMove( + this.conditions, + oldIndex, + newIndex, + oldPath, + newPath + ); + + fireEvent(this, "value-changed", { value: conditions }); } - private _move(index: number, newIndex: number) { - const conditions = this.conditions.concat(); - const condition = conditions.splice(index, 1)[0]; - conditions.splice(newIndex, 0, condition); - fireEvent(this, "value-changed", { value: conditions }); + private _conditionMoved(ev: CustomEvent): void { + if (this.nested) return; + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + this._move(oldIndex, newIndex, oldPath, newPath); } private _conditionChanged(ev: CustomEvent) { @@ -340,39 +290,36 @@ export default class HaAutomationCondition extends LitElement { } static get styles(): CSSResultGroup { - return [ - sortableStyles, - css` - ha-automation-condition-row { - display: block; - margin-bottom: 16px; - scroll-margin-top: 48px; - } - ha-svg-icon { - height: 20px; - } - ha-alert { - display: block; - margin-bottom: 16px; - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } - .handle { - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - padding: 12px; - } - .handle ha-svg-icon { - pointer-events: none; - height: 24px; - } - .buttons { - display: flex; - flex-wrap: wrap; - gap: 8px; - } - `, - ]; + return css` + ha-automation-condition-row { + display: block; + margin-bottom: 16px; + scroll-margin-top: 48px; + } + ha-svg-icon { + height: 20px; + } + ha-alert { + display: block; + margin-bottom: 16px; + border-radius: var(--ha-card-border-radius, 12px); + overflow: hidden; + } + .handle { + padding: 12px 4px; + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; + } + .handle ha-svg-icon { + pointer-events: none; + height: 24px; + } + .buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + `; } } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts index a7aa3fb9a6..4716131c35 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts @@ -2,7 +2,7 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import type { LogicalCondition } from "../../../../../data/automation"; -import type { HomeAssistant } from "../../../../../types"; +import type { HomeAssistant, ItemPath } from "../../../../../types"; import "../ha-automation-condition"; import type { ConditionElement } from "../ha-automation-condition-row"; @@ -14,7 +14,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement { @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public reOrderMode = false; + @property({ attribute: false }) public path?: ItemPath; public static get defaultConfig() { return { @@ -25,12 +25,11 @@ export class HaLogicalCondition extends LitElement implements ConditionElement { protected render() { return html` `; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts index 8935668618..db63c78e01 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts @@ -1,10 +1,11 @@ import "@material/mwc-list/mwc-list-item"; -import memoizeOne from "memoize-one"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { fireEvent } from "../../../../../common/dom/fire_event"; +import memoizeOne from "memoize-one"; import { ensureArray } from "../../../../../common/array/ensure-array"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../../components/ha-form/types"; import "../../../../../components/ha-select"; import type { diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index c8ca54c3d7..ed180172b3 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -76,7 +76,8 @@ declare global { }; "ui-mode-not-available": Error; duplicate: undefined; - "re-order": undefined; + "move-down": undefined; + "move-up": undefined; } } @@ -87,13 +88,13 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { @property() public entityId: string | null = null; - @property() public automations!: AutomationEntity[]; + @property({ attribute: false }) public automations!: AutomationEntity[]; - @property() public isWide?: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; @state() private _config?: AutomationConfig; @@ -780,6 +781,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { } ha-entity-toggle { margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } ha-fab { position: relative; diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 7fe54ec1f8..3bb5802c7d 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -73,15 +73,15 @@ type AutomationItem = AutomationEntity & { class HaAutomationPicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; - @property() public automations!: AutomationEntity[]; + @property({ attribute: false }) public automations!: AutomationEntity[]; - @property() private _activeFilters?: string[]; + @state() private _activeFilters?: string[]; @state() private _searchParms = new URLSearchParams(window.location.search); @@ -123,7 +123,8 @@ class HaAutomationPicker extends LitElement { type: "icon", template: (automation) => html` { class HaConfigAutomation extends HassRouterPage { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced = false; - @property() public automations: AutomationEntity[] = []; + @property({ attribute: false }) public automations: AutomationEntity[] = []; private _debouncedUpdateAutomations = debounce((pageEl) => { const newAutomations = this._getAutomations(this.hass.states); diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 9be779d275..8fed0a26c3 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -5,6 +5,7 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { ensureArray } from "../../../common/array/ensure-array"; import { fireEvent } from "../../../common/dom/fire_event"; +import { nestedArrayMove } from "../../../common/util/array-move"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; import "../../../components/ha-markdown"; @@ -25,9 +26,9 @@ import "./trigger/ha-automation-trigger"; export class HaManualAutomationEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ type: Boolean }) public disabled = false; @@ -44,7 +45,7 @@ export class HaManualAutomationEditor extends LitElement { ${this.hass.localize("ui.panel.config.automation.editor.migrate")} ` - : ""} + : nothing} ${this.stateObj?.state === "off" ? html` @@ -97,7 +98,9 @@ export class HaManualAutomationEditor extends LitElement { role="region" aria-labelledby="triggers-heading" .triggers=${this.config.trigger} + .path=${["trigger"]} @value-changed=${this._triggerChanged} + @item-moved=${this._itemMoved} .hass=${this.hass} .disabled=${this.disabled} > @@ -137,7 +140,9 @@ export class HaManualAutomationEditor extends LitElement { role="region" aria-labelledby="conditions-heading" .conditions=${this.config.condition || []} + .path=${["condition"]} @value-changed=${this._conditionChanged} + @item-moved=${this._itemMoved} .hass=${this.hass} .disabled=${this.disabled} > @@ -175,7 +180,9 @@ export class HaManualAutomationEditor extends LitElement { role="region" aria-labelledby="actions-heading" .actions=${this.config.action} + .path=${["action"]} @value-changed=${this._actionChanged} + @item-moved=${this._itemMoved} .hass=${this.hass} .narrow=${this.narrow} .disabled=${this.disabled} @@ -207,6 +214,21 @@ export class HaManualAutomationEditor extends LitElement { }); } + private _itemMoved(ev: CustomEvent): void { + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + const updatedConfig = nestedArrayMove( + this.config, + oldIndex, + newIndex, + oldPath, + newPath + ); + fireEvent(this, "value-changed", { + value: updatedConfig, + }); + } + private async _enable(): Promise { if (!this.hass || !this.stateObj) { return; @@ -258,6 +280,12 @@ export class HaManualAutomationEditor extends LitElement { font-weight: normal; line-height: 0; } + ha-alert.re-order { + display: block; + margin-bottom: 16px; + border-radius: var(--ha-card-border-radius, 12px); + overflow: hidden; + } `, ]; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 5a4acb43c6..7b80340958 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -2,6 +2,8 @@ import { consume } from "@lit-labs/context"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { + mdiArrowDown, + mdiArrowUp, mdiCheck, mdiContentCopy, mdiContentCut, @@ -11,11 +13,17 @@ import { mdiIdentifier, mdiPlayCircleOutline, mdiRenameBox, - mdiSort, mdiStopCircleOutline, } from "@mdi/js"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { storage } from "../../../../common/decorators/storage"; @@ -44,8 +52,9 @@ import { showPromptDialog, } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; -import type { HomeAssistant } from "../../../../types"; +import type { HomeAssistant, ItemPath } from "../../../../types"; import "./types/ha-automation-trigger-calendar"; +import "./types/ha-automation-trigger-conversation"; import "./types/ha-automation-trigger-device"; import "./types/ha-automation-trigger-event"; import "./types/ha-automation-trigger-geo_location"; @@ -53,7 +62,6 @@ import "./types/ha-automation-trigger-homeassistant"; import "./types/ha-automation-trigger-mqtt"; import "./types/ha-automation-trigger-numeric_state"; import "./types/ha-automation-trigger-persistent_notification"; -import "./types/ha-automation-trigger-conversation"; import "./types/ha-automation-trigger-state"; import "./types/ha-automation-trigger-sun"; import "./types/ha-automation-trigger-tag"; @@ -97,10 +105,14 @@ export default class HaAutomationTriggerRow extends LitElement { @property({ attribute: false }) public trigger!: Trigger; - @property({ type: Boolean }) public hideMenu = false; - @property({ type: Boolean }) public disabled = false; + @property({ type: Array }) public path?: ItemPath; + + @property({ type: Boolean }) public first?: boolean; + + @property({ type: Boolean }) public last?: boolean; + @state() private _warnings?: string[]; @state() private _yamlMode = false; @@ -128,6 +140,8 @@ export default class HaAutomationTriggerRow extends LitElement { private _triggerUnsub?: Promise; protected render() { + if (!this.trigger) return nothing; + const supported = customElements.get(`ha-automation-trigger-${this.trigger.platform}`) !== undefined; @@ -144,7 +158,7 @@ export default class HaAutomationTriggerRow extends LitElement { )}
    ` - : ""} + : nothing}

    @@ -156,141 +170,136 @@ export default class HaAutomationTriggerRow extends LitElement {

    - ${this.hideMenu - ? "" - : html` - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.rename" - )} - - + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.re_order" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.rename" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.edit_id" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.edit_id" + )} + + -
  • +
  • - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.duplicate" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.duplicate" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.copy" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.cut" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + -
  • + + ${this.hass.localize("ui.panel.config.automation.editor.move_up")} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - )} - ${!yamlMode - ? html`` - : ``} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.move_down" + )} + - - ${this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - ${yamlMode - ? html`` - : ``} - +
  • -
  • + + ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} + ${!yamlMode + ? html`` + : ``} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + ${yamlMode + ? html`` + : ``} + + +
  • + + + ${this.trigger.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    - - ${this.trigger.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    - `}
    @@ -470,34 +480,37 @@ export default class HaAutomationTriggerRow extends LitElement { await this._renameTrigger(); break; case 1: - fireEvent(this, "re-order"); - break; - case 2: this._requestShowId = true; this.expand(); break; - case 3: + case 2: fireEvent(this, "duplicate"); break; + case 3: + this._setClipboard(); + break; case 4: - this._setClipboard(); - break; - case 5: this._setClipboard(); fireEvent(this, "value-changed", { value: null }); break; + case 5: + fireEvent(this, "move-up"); + break; case 6: + fireEvent(this, "move-down"); + break; + case 7: this._switchUiMode(); this.expand(); break; - case 7: + case 8: this._switchYamlMode(); this.expand(); break; - case 8: + case 9: this._onDisable(); break; - case 9: + case 10: this._onDelete(); break; } @@ -660,6 +673,8 @@ export default class HaAutomationTriggerRow extends LitElement { color: var(--secondary-text-color); opacity: 0.9; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } } .card-content { @@ -702,6 +717,9 @@ export default class HaAutomationTriggerRow extends LitElement { mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } + mwc-list-item.hidden { + display: none; + } ha-textfield { display: block; margin-bottom: 24px; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 90467f61e0..6de4247300 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -1,37 +1,43 @@ -import "@material/mwc-button"; -import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js"; +import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; -import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit"; -import { customElement, property } from "lit/decorators"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import type { SortableEvent } from "sortablejs"; import { storage } from "../../../../common/decorators/storage"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { listenMediaQuery } from "../../../../common/dom/media_query"; +import { nestedArrayMove } from "../../../../common/util/array-move"; import "../../../../components/ha-button"; import "../../../../components/ha-button-menu"; +import "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import { AutomationClipboard, Trigger } from "../../../../data/automation"; -import { sortableStyles } from "../../../../resources/ha-sortable-style"; -import type { SortableInstance } from "../../../../resources/sortable"; -import { HomeAssistant } from "../../../../types"; -import "./ha-automation-trigger-row"; -import type HaAutomationTriggerRow from "./ha-automation-trigger-row"; +import { HomeAssistant, ItemPath } from "../../../../types"; import { PASTE_VALUE, showAddAutomationElementDialog, } from "../show-add-automation-element-dialog"; +import "./ha-automation-trigger-row"; +import type HaAutomationTriggerRow from "./ha-automation-trigger-row"; @customElement("ha-automation-trigger") export default class HaAutomationTrigger extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public triggers!: Trigger[]; + @property({ attribute: false }) public triggers!: Trigger[]; @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean }) public nested = false; + @property({ type: Array }) public path?: ItemPath; - @property({ type: Boolean }) public reOrderMode = false; + @state() private _showReorder: boolean = false; @storage({ key: "automationClipboard", @@ -45,85 +51,74 @@ export default class HaAutomationTrigger extends LitElement { private _triggerKeys = new WeakMap(); - private _sortable?: SortableInstance; + private _unsubMql?: () => void; + + public connectedCallback() { + super.connectedCallback(); + this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => { + this._showReorder = matches; + }); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._unsubMql?.(); + this._unsubMql = undefined; + } + + private get nested() { + return this.path !== undefined; + } protected render() { return html` - ${this.reOrderMode && !this.nested - ? html` - - ${this.hass.localize( - "ui.panel.config.automation.editor.re_order_mode.description_triggers" - )} - - ${this.hass.localize( - "ui.panel.config.automation.editor.re_order_mode.exit" - )} - - - ` - : null} -
    - ${repeat( - this.triggers, - (trigger) => this._getKey(trigger), - (trg, idx) => html` - - ${this.reOrderMode - ? html` - - -
    - -
    - ` - : ""} -
    - ` - )} - +
    + ${repeat( + this.triggers, + (trigger) => this._getKey(trigger), + (trg, idx) => html` + + ${this._showReorder && !this.disabled + ? html` +
    + +
    + ` + : nothing} +
    + ` )} - .disabled=${this.disabled} - @click=${this._addTriggerDialog} - > - - -
    +
    + + + + `; } @@ -158,14 +153,6 @@ export default class HaAutomationTrigger extends LitElement { protected updated(changedProps: PropertyValues) { super.updated(changedProps); - if (changedProps.has("reOrderMode")) { - if (this.reOrderMode) { - this._createSortable(); - } else { - this._destroySortable(); - } - } - if (changedProps.has("triggers") && this._focusLastTriggerOnChange) { this._focusLastTriggerOnChange = false; @@ -180,46 +167,6 @@ export default class HaAutomationTrigger extends LitElement { } } - private async _enterReOrderMode(ev: CustomEvent) { - if (this.nested) return; - ev.stopPropagation(); - this.reOrderMode = true; - } - - private async _exitReOrderMode() { - this.reOrderMode = false; - } - - private async _createSortable() { - const Sortable = (await import("../../../../resources/sortable")).default; - this._sortable = new Sortable( - this.shadowRoot!.querySelector(".triggers")!, - { - animation: 150, - fallbackClass: "sortable-fallback", - handle: ".handle", - onChoose: (evt: SortableEvent) => { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._dragged(evt); - }, - } - ); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private _getKey(action: Trigger) { if (!this._triggerKeys.has(action)) { this._triggerKeys.set(action, Math.random().toString()); @@ -240,16 +187,28 @@ export default class HaAutomationTrigger extends LitElement { this._move(index, newIndex); } - private _dragged(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) return; - this._move(ev.oldIndex!, ev.newIndex!); + private _move( + oldIndex: number, + newIndex: number, + oldPath?: ItemPath, + newPath?: ItemPath + ) { + const triggers = nestedArrayMove( + this.triggers, + oldIndex, + newIndex, + oldPath, + newPath + ); + + fireEvent(this, "value-changed", { value: triggers }); } - private _move(index: number, newIndex: number) { - const triggers = this.triggers.concat(); - const trigger = triggers.splice(index, 1)[0]; - triggers.splice(newIndex, 0, trigger); - fireEvent(this, "value-changed", { value: triggers }); + private _triggerMoved(ev: CustomEvent): void { + if (this.nested) return; + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + this._move(oldIndex, newIndex, oldPath, newPath); } private _triggerChanged(ev: CustomEvent) { @@ -280,34 +239,31 @@ export default class HaAutomationTrigger extends LitElement { } static get styles(): CSSResultGroup { - return [ - sortableStyles, - css` - ha-automation-trigger-row { - display: block; - margin-bottom: 16px; - scroll-margin-top: 48px; - } - ha-svg-icon { - height: 20px; - } - ha-alert { - display: block; - margin-bottom: 16px; - border-radius: var(--ha-card-border-radius, 16px); - overflow: hidden; - } - .handle { - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - padding: 12px; - } - .handle ha-svg-icon { - pointer-events: none; - height: 24px; - } - `, - ]; + return css` + ha-automation-trigger-row { + display: block; + margin-bottom: 16px; + scroll-margin-top: 48px; + } + ha-svg-icon { + height: 20px; + } + ha-alert { + display: block; + margin-bottom: 16px; + border-radius: var(--ha-card-border-radius, 16px); + overflow: hidden; + } + .handle { + padding: 12px 4px; + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; + } + .handle ha-svg-icon { + pointer-events: none; + height: 24px; + } + `; } } diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-conversation.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-conversation.ts index aebc2a899e..4cb5abd86b 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-conversation.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-conversation.ts @@ -157,6 +157,8 @@ export class HaConversationTrigger } mwc-button { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } ha-textfield { display: block; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts index 3d997e81a4..5904efdba5 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts @@ -15,7 +15,7 @@ import { export class HaEventTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: EventTrigger; + @property({ attribute: false }) public trigger!: EventTrigger; @property({ type: Boolean }) public disabled = false; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification.ts index 0e942c1860..ee3a1743d4 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification.ts @@ -23,7 +23,8 @@ export class HaPersistentNotificationTrigger { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: PersistentNotificationTrigger; + @property({ attribute: false }) + public trigger!: PersistentNotificationTrigger; @property({ type: Boolean }) public disabled = false; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts index be5a8c7fee..7857ea17b5 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts @@ -13,7 +13,7 @@ import { TriggerElement } from "../ha-automation-trigger-row"; export class HaTagTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: TagTrigger; + @property({ attribute: false }) public trigger!: TagTrigger; @property({ type: Boolean }) public disabled = false; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts index 300b7215c8..e090209a22 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts @@ -27,7 +27,7 @@ const DEFAULT_WEBHOOK_ID = ""; export class HaWebhookTrigger extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: WebhookTrigger; + @property({ attribute: false }) public trigger!: WebhookTrigger; @property({ type: Boolean }) public disabled = false; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts index 3b9942efc5..7ddf87ccdf 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts @@ -19,7 +19,7 @@ const includeDomains = ["zone"]; export class HaZoneTrigger extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: ZoneTrigger; + @property({ attribute: false }) public trigger!: ZoneTrigger; @property({ type: Boolean }) public disabled = false; diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index 746bfdc8e2..9455a4d1b1 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -39,9 +39,9 @@ import { fileDownload } from "../../../util/file_download"; class HaConfigBackup extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ attribute: false }) public route!: Route; diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index 1123e51d03..3a290835d3 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -80,9 +80,9 @@ const createNewFunctions = { class HaBlueprintOverview extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ attribute: false }) public route!: Route; diff --git a/src/panels/config/blueprint/ha-config-blueprint.ts b/src/panels/config/blueprint/ha-config-blueprint.ts index 68f50c94b4..4a371a2cf0 100644 --- a/src/panels/config/blueprint/ha-config-blueprint.ts +++ b/src/panels/config/blueprint/ha-config-blueprint.ts @@ -19,13 +19,14 @@ declare global { class HaConfigBlueprint extends HassRouterPage { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced = false; - @property() public blueprints: Record = {}; + @property({ attribute: false }) + public blueprints: Record = {}; protected routerOptions: RouterOptions = { defaultPage: "dashboard", diff --git a/src/panels/config/cloud/account/cloud-account.ts b/src/panels/config/cloud/account/cloud-account.ts index a1946add28..293576fa8f 100644 --- a/src/panels/config/cloud/account/cloud-account.ts +++ b/src/panels/config/cloud/account/cloud-account.ts @@ -1,6 +1,4 @@ import "@material/mwc-button"; -import "@material/mwc-list/mwc-list-item"; -import "@polymer/paper-item/paper-item-body"; import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { formatDateTime } from "../../../../common/datetime/format_date_time"; @@ -10,6 +8,7 @@ import { debounce } from "../../../../common/util/debounce"; import "../../../../components/ha-alert"; import "../../../../components/ha-card"; import "../../../../components/ha-tip"; +import "../../../../components/ha-list-item"; import { cloudLogout, CloudStatusLoggedIn, @@ -65,12 +64,12 @@ export class CloudAccount extends SubscribeMixin(LitElement) { )} > ${this.cloudStatus.cloud === "connecting" && @@ -103,12 +102,10 @@ export class CloudAccount extends SubscribeMixin(LitElement) { : ""}
    diff --git a/src/panels/config/cloud/account/cloud-remote-pref.ts b/src/panels/config/cloud/account/cloud-remote-pref.ts index b77159d8e6..e5d0771b39 100644 --- a/src/panels/config/cloud/account/cloud-remote-pref.ts +++ b/src/panels/config/cloud/account/cloud-remote-pref.ts @@ -22,7 +22,7 @@ import { showCloudCertificateDialog } from "../dialog-cloud-certificate/show-dia export class CloudRemotePref extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public cloudStatus?: CloudStatusLoggedIn; + @property({ attribute: false }) public cloudStatus?: CloudStatusLoggedIn; protected render() { if (!this.cloudStatus) { @@ -189,8 +189,9 @@ export class CloudRemotePref extends LitElement { } .header-actions .icon-link { margin-top: -16px; - margin-inline-end: 8px; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; direction: var(--direction); color: var(--secondary-text-color); } diff --git a/src/panels/config/cloud/account/cloud-tts-pref.ts b/src/panels/config/cloud/account/cloud-tts-pref.ts index ce9cc85dab..052fad179b 100644 --- a/src/panels/config/cloud/account/cloud-tts-pref.ts +++ b/src/panels/config/cloud/account/cloud-tts-pref.ts @@ -24,7 +24,7 @@ import { showTryTtsDialog } from "./show-dialog-cloud-tts-try"; export class CloudTTSPref extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public cloudStatus?: CloudStatusLoggedIn; + @property({ attribute: false }) public cloudStatus?: CloudStatusLoggedIn; @state() private savingPreferences = false; @@ -191,9 +191,13 @@ export class CloudTTSPref extends LitElement { } .row > *:first-child { margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } .row > *:last-child { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } .card-actions { display: flex; diff --git a/src/panels/config/cloud/account/cloud-webhooks.ts b/src/panels/config/cloud/account/cloud-webhooks.ts index 776ffa5d93..35b340bda6 100644 --- a/src/panels/config/cloud/account/cloud-webhooks.ts +++ b/src/panels/config/cloud/account/cloud-webhooks.ts @@ -22,7 +22,7 @@ export class CloudWebhooks extends LitElement { @property({ attribute: false }) public cloudStatus?: CloudStatusLoggedIn; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _cloudHooks?: { [webhookId: string]: CloudWebhook; @@ -223,6 +223,8 @@ export class CloudWebhooks extends LitElement { } .progress { margin-right: 16px; + margin-inline-end: 16px; + margin-inline-start: initial; display: flex; flex-direction: column; justify-content: center; diff --git a/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts b/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts index 15e2baafb8..91ee82e241 100644 --- a/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts +++ b/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts @@ -1,6 +1,6 @@ import "@material/mwc-button"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, state } from "lit/decorators"; import { formatDateTime } from "../../../../common/datetime/format_date_time"; import { fireEvent } from "../../../../common/dom/fire_event"; import { createCloseHeading } from "../../../../components/ha-dialog"; @@ -12,8 +12,7 @@ import type { CloudCertificateParams as CloudCertificateDialogParams } from "./s class DialogCloudCertificate extends LitElement { public hass!: HomeAssistant; - @property() - private _params?: CloudCertificateDialogParams; + @state() private _params?: CloudCertificateDialogParams; public showDialog(params: CloudCertificateDialogParams) { this._params = params; diff --git a/src/panels/config/cloud/ha-config-cloud.ts b/src/panels/config/cloud/ha-config-cloud.ts index c48600fc3e..08b4c303c3 100644 --- a/src/panels/config/cloud/ha-config-cloud.ts +++ b/src/panels/config/cloud/ha-config-cloud.ts @@ -16,13 +16,13 @@ const NOT_LOGGED_IN_URLS = ["login", "register", "forgot-password"]; class HaConfigCloud extends HassRouterPage { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; - @property() public cloudStatus!: CloudStatus; + @property({ attribute: false }) public cloudStatus!: CloudStatus; protected routerOptions: RouterOptions = { defaultPage: "login", diff --git a/src/panels/config/cloud/login/cloud-login.ts b/src/panels/config/cloud/login/cloud-login.ts index 696b70fb08..3b468707eb 100644 --- a/src/panels/config/cloud/login/cloud-login.ts +++ b/src/panels/config/cloud/login/cloud-login.ts @@ -1,6 +1,5 @@ import "@material/mwc-button"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; +import "@material/mwc-list/mwc-list"; import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -9,6 +8,7 @@ import "../../../../components/buttons/ha-progress-button"; import "../../../../components/ha-alert"; import "../../../../components/ha-card"; import "../../../../components/ha-icon-next"; +import "../../../../components/ha-list-item"; import type { HaTextField } from "../../../../components/ha-textfield"; import "../../../../components/ha-textfield"; import { cloudLogin } from "../../../../data/cloud"; @@ -166,19 +166,19 @@ export class CloudLogin extends LitElement { - - + + ${this.hass.localize( "ui.panel.config.cloud.login.start_trial" )} -
    + ${this.hass.localize( "ui.panel.config.cloud.login.trial_info" )} -
    -
    - -
    + + + +
    @@ -293,9 +293,6 @@ export class CloudLogin extends LitElement { [slot="introduction"] a { color: var(--primary-color); } - paper-item { - cursor: pointer; - } ha-card { overflow: hidden; } diff --git a/src/panels/config/core/ha-config-section-analytics.ts b/src/panels/config/core/ha-config-section-analytics.ts index 085e4691e5..75658e6a47 100644 --- a/src/panels/config/core/ha-config-section-analytics.ts +++ b/src/panels/config/core/ha-config-section-analytics.ts @@ -10,7 +10,7 @@ class HaConfigSectionAnalytics extends LitElement { @property({ attribute: false }) public route!: Route; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { return html` diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts index 1d9e3794c0..1a8b4a7263 100644 --- a/src/panels/config/core/ha-config-section-general.ts +++ b/src/panels/config/core/ha-config-section-general.ts @@ -20,6 +20,7 @@ import type { HaRadio } from "../../../components/ha-radio"; import "../../../components/ha-select"; import "../../../components/ha-settings-row"; import "../../../components/ha-textfield"; +import type { HaTextField } from "../../../components/ha-textfield"; import "../../../components/ha-timezone-picker"; import "../../../components/map/ha-locations-editor"; import type { MarkerLocation } from "../../../components/map/ha-locations-editor"; @@ -28,13 +29,12 @@ import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, ValueChangedEvent } from "../../../types"; -import type { HaTextField } from "../../../components/ha-textfield"; @customElement("ha-config-section-general") class HaConfigSectionGeneral extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _submitting = false; @@ -294,10 +294,7 @@ class HaConfigSectionGeneral extends LitElement { this._country = this.hass.config.country; this._language = this.hass.config.language; this._elevation = this.hass.config.elevation; - this._timeZone = - this.hass.config.time_zone || - Intl.DateTimeFormat?.().resolvedOptions?.().timeZone || - "Etc/GMT"; + this._timeZone = this.hass.config.time_zone || "Etc/GMT"; this._name = this.hass.config.location_name; this._updateUnits = true; } diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index d10b79fae3..6493cd1456 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -34,7 +34,7 @@ import { showJoinBetaDialog } from "./updates/show-dialog-join-beta"; class HaConfigSectionUpdates extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _showSkipped = false; diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index c2d0f73c77..9f46770667 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -29,14 +29,13 @@ import { configSections } from "../ha-panel-config"; class HaConfigSystemNavigation extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean, reflect: true }) - public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; - @property({ type: Boolean }) public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; @property({ attribute: false }) public cloudStatus?: CloudStatus; - @property({ type: Boolean }) public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced = false; @state() private _latestBackupDate?: string; @@ -177,8 +176,9 @@ class HaConfigSystemNavigation extends LitElement { const hardwareInfo: HardwareInfo = await this.hass.callWS({ type: "hardware/info", }); - this._boardName = hardwareInfo?.hardware.find((hw) => hw.board !== null) - ?.name; + this._boardName = hardwareInfo?.hardware.find( + (hw) => hw.board !== null + )?.name; } else if (isHassioLoaded) { const osData: HassioHassOSInfo = await fetchHassioHassOsInfo(this.hass); if (osData.board) { diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 2ed6a9bb8f..918b8c888e 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -114,14 +114,13 @@ const randomTip = (hass: HomeAssistant, narrow: boolean) => { class HaConfigDashboard extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean, reflect: true }) - public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property() public cloudStatus?: CloudStatus; + @property({ attribute: false }) public cloudStatus?: CloudStatus; - @property() public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced = false; @state() private _tip?: string; diff --git a/src/panels/config/dashboard/ha-config-navigation.ts b/src/panels/config/dashboard/ha-config-navigation.ts index 39e098b5da..8bf75aeb47 100644 --- a/src/panels/config/dashboard/ha-config-navigation.ts +++ b/src/panels/config/dashboard/ha-config-navigation.ts @@ -14,7 +14,7 @@ import type { HomeAssistant } from "../../../types"; class HaConfigNavigation extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ attribute: false }) public pages!: PageNavigation[]; diff --git a/src/panels/config/dashboard/ha-config-updates.ts b/src/panels/config/dashboard/ha-config-updates.ts index e12e776096..254815b82c 100644 --- a/src/panels/config/dashboard/ha-config-updates.ts +++ b/src/panels/config/dashboard/ha-config-updates.ts @@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; @@ -28,39 +28,35 @@ import type { HomeAssistant } from "../../../types"; class HaConfigUpdates extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property({ attribute: false }) - public updateEntities?: UpdateEntity[]; + @property({ attribute: false }) public updateEntities?: UpdateEntity[]; - @property({ attribute: false, type: Array }) - private devices?: DeviceRegistryEntry[]; + @property({ type: Number }) public total?: number; - @property({ attribute: false, type: Array }) - private entities?: EntityRegistryEntry[]; + @state() private _devices?: DeviceRegistryEntry[]; - @property({ type: Number }) - public total?: number; + @state() private _entities?: EntityRegistryEntry[]; public hassSubscribe(): UnsubscribeFunc[] { return [ subscribeDeviceRegistry(this.hass.connection, (entries) => { - this.devices = entries; + this._devices = entries; }), subscribeEntityRegistry(this.hass.connection!, (entities) => { - this.entities = entities.filter((entity) => entity.device_id !== null); + this._entities = entities.filter((entity) => entity.device_id !== null); }), ]; } private getDeviceEntry = memoizeOne( (deviceId: string): DeviceRegistryEntry | undefined => - this.devices?.find((device) => device.id === deviceId) + this._devices?.find((device) => device.id === deviceId) ); private getEntityEntry = memoizeOne( (entityId: string): EntityRegistryEntry | undefined => - this.entities?.find((entity) => entity.entity_id === entityId) + this._entities?.find((entity) => entity.entity_id === entityId) ); protected render() { @@ -99,6 +95,7 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) { slot="graphic" .title=${entity.attributes.title || entity.attributes.friendly_name} + .hass=${this.hass} .stateObj=${entity} class=${ifDefined( this.narrow && entity.attributes.in_progress diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index fb5aad023e..d594df6952 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -1,6 +1,4 @@ -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; +import "@material/mwc-list/mwc-list"; import { css, CSSResultGroup, @@ -10,17 +8,17 @@ import { TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { computeDomain } from "../../../../common/entity/compute_domain"; +import { until } from "lit/directives/until"; import { computeStateName } from "../../../../common/entity/compute_state_name"; -import { domainIcon } from "../../../../common/entity/domain_icon"; import { stripPrefixFromEntityName } from "../../../../common/entity/strip_prefix_from_entity_name"; -import "../../../../components/entity/state-badge"; import "../../../../components/ha-card"; import "../../../../components/ha-icon"; +import "../../../../components/ha-list-item"; import { ExtEntityRegistryEntry, getExtendedEntityRegistryEntry, } from "../../../../data/entity_registry"; +import { entryIcon } from "../../../../data/icons"; import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog"; import type { HomeAssistant } from "../../../../types"; import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card"; @@ -29,6 +27,7 @@ import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types"; import { LovelaceRow } from "../../../lovelace/entity-rows/types"; import { EntityRegistryStateEntry } from "../ha-config-device-page"; +import { computeCards } from "../../../lovelace/common/generate-lovelace-config"; @customElement("ha-device-entities-card") export class HaDeviceEntitiesCard extends LitElement { @@ -38,9 +37,9 @@ export class HaDeviceEntitiesCard extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public entities!: EntityRegistryStateEntry[]; + @property({ attribute: false }) public entities!: EntityRegistryStateEntry[]; - @property() public showHidden = false; + @property({ type: Boolean }) public showHidden = false; @state() private _extDisabledEntityEntries?: Record< string, @@ -91,11 +90,13 @@ export class HaDeviceEntitiesCard extends LitElement { return html`
    - ${shownEntities.map((entry) => - this.hass.states[entry.entity_id] - ? this._renderEntity(entry) - : this._renderEntry(entry) - )} + + ${shownEntities.map((entry) => + this.hass.states[entry.entity_id] + ? this._renderEntity(entry) + : this._renderEntry(entry) + )} +
    ${hiddenEntities.length ? !this.showHidden @@ -108,7 +109,9 @@ export class HaDeviceEntitiesCard extends LitElement { ` : html` - ${hiddenEntities.map((entry) => this._renderEntry(entry))} + + ${hiddenEntities.map((entry) => this._renderEntry(entry))} + +
    ` : ""} diff --git a/src/panels/developer-tools/debug/developer-tools-debug.ts b/src/panels/developer-tools/debug/developer-tools-debug.ts index 6a5aa730cd..33ebe8ae6f 100644 --- a/src/panels/developer-tools/debug/developer-tools-debug.ts +++ b/src/panels/developer-tools/debug/developer-tools-debug.ts @@ -1,5 +1,6 @@ -import { CSSResultGroup, LitElement, css, html } from "lit"; +import { LitElement, css, html } from "lit"; import { customElement, property } from "lit/decorators"; +import "../../../components/ha-card"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; @@ -9,7 +10,7 @@ import "./ha-debug-connection-row"; class HaPanelDevDebug extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render() { return html` @@ -18,31 +19,27 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) { .header=${this.hass.localize( "ui.panel.developer-tools.tabs.debug.title" )} - class="form" > -
    - +
    `; } - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - .content { - padding: 28px 20px 16px; - display: block; - max-width: 600px; - margin: 0 auto; - } - `, - ]; - } + static styles = [ + haStyle, + css` + .content { + padding: 28px 20px 16px; + display: block; + max-width: 600px; + margin: 0 auto; + } + `, + ]; } declare global { diff --git a/src/panels/developer-tools/debug/ha-debug-connection-row.ts b/src/panels/developer-tools/debug/ha-debug-connection-row.ts index e4b1c5c3ff..764931f649 100644 --- a/src/panels/developer-tools/debug/ha-debug-connection-row.ts +++ b/src/panels/developer-tools/debug/ha-debug-connection-row.ts @@ -10,7 +10,7 @@ import { storeState } from "../../../util/ha-pref-storage"; class HaDebugConnectionRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { return html` diff --git a/src/panels/developer-tools/developer-tools-router.ts b/src/panels/developer-tools/developer-tools-router.ts index a205fa810d..6fdf6aa010 100644 --- a/src/panels/developer-tools/developer-tools-router.ts +++ b/src/panels/developer-tools/developer-tools-router.ts @@ -6,7 +6,7 @@ import { HomeAssistant } from "../../types"; class DeveloperToolsRouter extends HassRouterPage { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected routerOptions: RouterOptions = { // defaultPage: "info", diff --git a/src/panels/developer-tools/event/developer-tools-event.ts b/src/panels/developer-tools/event/developer-tools-event.ts index d2ac42a2fd..cd29d3176d 100644 --- a/src/panels/developer-tools/event/developer-tools-event.ts +++ b/src/panels/developer-tools/event/developer-tools-event.ts @@ -15,7 +15,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; class HaPanelDevEvent extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _eventType: string = ""; diff --git a/src/panels/developer-tools/event/events-list.ts b/src/panels/developer-tools/event/events-list.ts index 7007ca6eab..a509651f2d 100644 --- a/src/panels/developer-tools/event/events-list.ts +++ b/src/panels/developer-tools/event/events-list.ts @@ -13,7 +13,7 @@ interface EventListenerCount { class EventsList extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public events: EventListenerCount[] = []; + @property({ attribute: false }) public events: EventListenerCount[] = []; protected render(): TemplateResult { return html` diff --git a/src/panels/developer-tools/ha-panel-developer-tools.ts b/src/panels/developer-tools/ha-panel-developer-tools.ts index de7ec5777c..52e98b3770 100644 --- a/src/panels/developer-tools/ha-panel-developer-tools.ts +++ b/src/panels/developer-tools/ha-panel-developer-tools.ts @@ -17,9 +17,9 @@ import "./developer-tools-router"; class PanelDeveloperTools extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected firstUpdated(changedProps) { super.firstUpdated(changedProps); @@ -147,7 +147,7 @@ class PanelDeveloperTools extends LitElement { } } .main-title { - margin: 0 0 0 24px; + margin: var(--margin-title); line-height: 20px; flex-grow: 1; } @@ -163,6 +163,8 @@ class PanelDeveloperTools extends LitElement { paper-tabs { margin-left: max(env(safe-area-inset-left), 24px); margin-right: max(env(safe-area-inset-right), 24px); + margin-inline-start: max(env(safe-area-inset-left), 24px); + margin-inline-end: max(env(safe-area-inset-right), 24px); --paper-tabs-selection-bar-color: var( --app-header-selection-bar-color, var(--app-header-text-color, #fff) diff --git a/src/panels/developer-tools/service/developer-tools-service.ts b/src/panels/developer-tools/service/developer-tools-service.ts index 394ed6d9fa..d920065d5a 100644 --- a/src/panels/developer-tools/service/developer-tools-service.ts +++ b/src/panels/developer-tools/service/developer-tools-service.ts @@ -34,7 +34,7 @@ import { documentationUrl } from "../../../util/documentation-url"; class HaPanelDevService extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _uiAvailable = true; @@ -73,8 +73,8 @@ class HaPanelDevService extends LitElement { data: {}, }; if (this._yamlMode) { - this.updateComplete.then( - () => this._yamlEditor?.setValue(this._serviceData) + this.updateComplete.then(() => + this._yamlEditor?.setValue(this._serviceData) ); } } else if (!this._serviceData?.service) { @@ -86,8 +86,8 @@ class HaPanelDevService extends LitElement { data: {}, }; if (this._yamlMode) { - this.updateComplete.then( - () => this._yamlEditor?.setValue(this._serviceData) + this.updateComplete.then(() => + this._yamlEditor?.setValue(this._serviceData) ); } } @@ -580,6 +580,8 @@ class HaPanelDevService extends LitElement { } .switch-mode-container .error { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } .attributes { width: 100%; diff --git a/src/panels/developer-tools/state/developer-tools-state.ts b/src/panels/developer-tools/state/developer-tools-state.ts index a2682f0863..d82d2bc2bb 100644 --- a/src/panels/developer-tools/state/developer-tools-state.ts +++ b/src/panels/developer-tools/state/developer-tools-state.ts @@ -684,6 +684,8 @@ class HaPanelDevState extends LitElement { cursor: pointer; flex-shrink: 0; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } .entities td:nth-child(1) { min-width: 300px; diff --git a/src/panels/developer-tools/statistics/developer-tools-statistics.ts b/src/panels/developer-tools/statistics/developer-tools-statistics.ts index 878a4fa739..f85744ec45 100644 --- a/src/panels/developer-tools/statistics/developer-tools-statistics.ts +++ b/src/panels/developer-tools/statistics/developer-tools-statistics.ts @@ -51,7 +51,7 @@ type DisplayedStatisticData = StatisticData & { class HaPanelDevStatistics extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _data: StatisticData[] = [] as StatisticsMetaData[]; @@ -177,7 +177,9 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { .hass=${this.hass} .columns=${this._columns(this.hass.localize)} .data=${this._displayData(this._data, this.hass.localize)} - noDataText="No statistics" + .noDataText=${this.hass.localize( + "ui.panel.developer-tools.tabs.statistics.data_table.no_statistics" + )} id="statistic_id" clickable @row-click=${this._rowClicked} diff --git a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts index b622e6261e..abc264adce 100644 --- a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts +++ b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts @@ -1,6 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; -import { mdiChevronRight } from "@mdi/js"; import formatISO9075 from "date-fns/formatISO9075"; import { css, @@ -21,6 +20,7 @@ import "../../../components/ha-form/ha-form"; import "../../../components/ha-selector/ha-selector-datetime"; import "../../../components/ha-selector/ha-selector-number"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-icon-next"; import { adjustStatisticsSum, fetchStatistics, @@ -165,7 +165,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { this.hass.config )} - + `); } diff --git a/src/panels/developer-tools/template/developer-tools-template.ts b/src/panels/developer-tools/template/developer-tools-template.ts index 734dff2ff4..7732ad7760 100644 --- a/src/panels/developer-tools/template/developer-tools-template.ts +++ b/src/panels/developer-tools/template/developer-tools-template.ts @@ -39,9 +39,9 @@ For loop example getting entity values in the weather domain: @customElement("developer-tools-template") class HaPanelDevTemplate extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _error?: string; diff --git a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts index 21d12a86f4..d35bfa5e8c 100644 --- a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts +++ b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts @@ -10,6 +10,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { componentsWithService } from "../../../common/config/components_with_service"; import "../../../components/buttons/ha-call-service-button"; +import "../../../components/ha-alert"; import "../../../components/ha-card"; import "../../../components/ha-circular-progress"; import { CheckConfigResult, checkCoreConfig } from "../../../data/core"; @@ -27,13 +28,13 @@ type ReloadableDomain = Exclude< export class DeveloperYamlConfig extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public isWide!: boolean; + @property({ type: Boolean }) public isWide = false; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ attribute: false }) public route!: Route; - @property({ type: Boolean }) public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced = false; @state() private _validating = false; diff --git a/src/panels/energy/ha-panel-energy.ts b/src/panels/energy/ha-panel-energy.ts index 26cdc94284..a9462d0edb 100644 --- a/src/panels/energy/ha-panel-energy.ts +++ b/src/panels/energy/ha-panel-energy.ts @@ -34,7 +34,7 @@ const ENERGY_LOVELACE_CONFIG: LovelaceConfig = { class PanelEnergy extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean, reflect: true }) public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; @state() private _viewIndex = 0; @@ -139,6 +139,7 @@ class PanelEnergy extends LitElement { width: 100%; padding-left: 32px; padding-inline-start: 32px; + padding-inline-end: initial; --disabled-text-color: rgba(var(--rgb-text-primary-color), 0.5); direction: var(--direction); --date-range-picker-max-height: calc(100vh - 80px); @@ -146,6 +147,7 @@ class PanelEnergy extends LitElement { :host([narrow]) hui-energy-period-selector { padding-left: 0px; padding-inline-start: 0px; + padding-inline-end: initial; } :host { -ms-user-select: none; @@ -189,7 +191,7 @@ class PanelEnergy extends LitElement { } } .main-title { - margin: 0 0 0 24px; + margin: var(--margin-title); line-height: 20px; flex-grow: 1; } @@ -200,7 +202,9 @@ class PanelEnergy extends LitElement { min-height: 100vh; box-sizing: border-box; padding-left: env(safe-area-inset-left); + padding-inline-start: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); + padding-inline-end: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); } hui-view { diff --git a/src/panels/energy/strategies/energy-view-strategy.ts b/src/panels/energy/strategies/energy-view-strategy.ts index 0ca612065d..27a0a91371 100644 --- a/src/panels/energy/strategies/energy-view-strategy.ts +++ b/src/panels/energy/strategies/energy-view-strategy.ts @@ -166,3 +166,9 @@ export class EnergyViewStrategy extends ReactiveElement { return view; } } + +declare global { + interface HTMLElementTagNameMap { + "energy-view-strategy": EnergyViewStrategy; + } +} diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index 75a2b99265..fbed2990a9 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -1,4 +1,4 @@ -import { mdiFilterRemove, mdiRefresh } from "@mdi/js"; +import { mdiDownload, mdiFilterRemove, mdiRefresh } from "@mdi/js"; import { differenceInHours } from "date-fns/esm"; import { HassServiceTarget, @@ -53,11 +53,12 @@ import { getSensorNumericDeviceClasses } from "../../data/sensor"; import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { haStyle } from "../../resources/styles"; import { HomeAssistant } from "../../types"; +import { fileDownload } from "../../util/file_download"; class HaPanelHistory extends SubscribeMixin(LitElement) { @property({ attribute: false }) hass!: HomeAssistant; - @property({ reflect: true, type: Boolean }) narrow!: boolean; + @property({ reflect: true, type: Boolean }) public narrow = false; @property({ reflect: true, type: Boolean }) rtl = false; @@ -172,6 +173,13 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { .path=${mdiRefresh} .label=${this.hass.localize("ui.common.refresh")} > +
    @@ -630,6 +638,36 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { navigate(`/history?${createSearchParam(params)}`, { replace: true }); } + private _downloadHistory() { + const csv: string[] = ["entity_id,state,last_changed\n"]; + const formatDate = (number) => new Date(number).toISOString(); + + for (const line of this._mungedStateHistory!.line) { + for (const entity of line.data) { + const entityId = entity.entity_id; + for (const data of [entity.states, entity.statistics]) { + if (!data) { + continue; + } + for (const s of data) { + csv.push(`${entityId},${s.state},${formatDate(s.last_changed)}\n`); + } + } + } + } + for (const timeline of this._mungedStateHistory!.timeline) { + const entityId = timeline.entity_id; + for (const s of timeline.data) { + csv.push(`${entityId},${s.state},${formatDate(s.last_changed)}\n`); + } + } + const blob = new Blob(csv, { + type: "text/csv", + }); + const url = window.URL.createObjectURL(blob); + fileDownload(url, "history.csv"); + } + static get styles() { return [ haStyle, diff --git a/src/panels/iframe/ha-panel-iframe.ts b/src/panels/iframe/ha-panel-iframe.ts index 98080afac4..72b7e96d10 100644 --- a/src/panels/iframe/ha-panel-iframe.ts +++ b/src/panels/iframe/ha-panel-iframe.ts @@ -7,11 +7,11 @@ import { HomeAssistant, PanelInfo } from "../../types"; @customElement("ha-panel-iframe") class HaPanelIframe extends LitElement { - @property() hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() panel!: PanelInfo<{ url: string }>; + @property({ attribute: false }) panel!: PanelInfo<{ url: string }>; render() { if ( diff --git a/src/panels/logbook/ha-logbook-renderer.ts b/src/panels/logbook/ha-logbook-renderer.ts index fe5f955637..32545466db 100644 --- a/src/panels/logbook/ha-logbook-renderer.ts +++ b/src/panels/logbook/ha-logbook-renderer.ts @@ -711,8 +711,8 @@ class HaLogbookRenderer extends LitElement { .narrow .icon-message state-badge { margin-left: 0; margin-inline-start: 0; - margin-inline-end: 8px; margin-right: 8px; + margin-inline-end: 8px; direction: var(--direction); } `, diff --git a/src/panels/logbook/ha-logbook.ts b/src/panels/logbook/ha-logbook.ts index 984a6a694b..ea25e307b3 100644 --- a/src/panels/logbook/ha-logbook.ts +++ b/src/panels/logbook/ha-logbook.ts @@ -41,28 +41,24 @@ const idsChanged = (oldIds?: string[], newIds?: string[]) => { export class HaLogbook extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public time!: + @property({ attribute: false }) public time!: | { range: [Date, Date] } | { // Seconds recent: number; }; - @property() public entityIds?: string[]; + @property({ attribute: false }) public entityIds?: string[]; - @property() public deviceIds?: string[]; + @property({ attribute: false }) public deviceIds?: string[]; - @property({ type: Boolean, attribute: "narrow" }) - public narrow = false; + @property({ type: Boolean }) public narrow = false; - @property({ type: Boolean, attribute: "virtualize", reflect: true }) - public virtualize = false; + @property({ type: Boolean, reflect: true }) public virtualize = false; - @property({ type: Boolean, attribute: "no-icon" }) - public noIcon = false; + @property({ type: Boolean, attribute: "no-icon" }) public noIcon = false; - @property({ type: Boolean, attribute: "no-name" }) - public noName = false; + @property({ type: Boolean, attribute: "no-name" }) public noName = false; @property({ type: Boolean, attribute: "show-indicator" }) public showIndicator = false; diff --git a/src/panels/logbook/ha-panel-logbook.ts b/src/panels/logbook/ha-panel-logbook.ts index d192b53edb..cd9b6ed16e 100644 --- a/src/panels/logbook/ha-panel-logbook.ts +++ b/src/panels/logbook/ha-panel-logbook.ts @@ -21,9 +21,9 @@ import "./ha-logbook"; @customElement("ha-panel-logbook") export class HaPanelLogbook extends LitElement { - @property() hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property({ reflect: true, type: Boolean }) narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; @state() _time: { range: [Date, Date] }; @@ -274,3 +274,9 @@ export class HaPanelLogbook extends LitElement { ]; } } + +declare global { + interface HTMLElementTagNameMap { + "ha-panel-logbook": HaPanelLogbook; + } +} diff --git a/src/panels/lovelace/badges/hui-entity-filter-badge.ts b/src/panels/lovelace/badges/hui-entity-filter-badge.ts index a45799695a..04cdd94a0f 100644 --- a/src/panels/lovelace/badges/hui-entity-filter-badge.ts +++ b/src/panels/lovelace/badges/hui-entity-filter-badge.ts @@ -1,5 +1,5 @@ import { PropertyValues, ReactiveElement } from "lit"; -import { property, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { HomeAssistant } from "../../../types"; import { evaluateFilter } from "../common/evaluate-filter"; import { processConfigEntities } from "../common/process-config-entities"; @@ -8,7 +8,11 @@ import { EntityFilterEntityConfig } from "../entity-rows/types"; import { LovelaceBadge } from "../types"; import { EntityFilterBadgeConfig } from "./types"; -class EntityFilterBadge extends ReactiveElement implements LovelaceBadge { +@customElement("hui-entity-filter-badge") +export class HuiEntityFilterBadge + extends ReactiveElement + implements LovelaceBadge +{ @property({ attribute: false }) public hass!: HomeAssistant; @state() private _config?: EntityFilterBadgeConfig; @@ -153,4 +157,9 @@ class EntityFilterBadge extends ReactiveElement implements LovelaceBadge { return false; } } -customElements.define("hui-entity-filter-badge", EntityFilterBadge); + +declare global { + interface HTMLElementTagNameMap { + "hui-entity-filter-badge": HuiEntityFilterBadge; + } +} diff --git a/src/panels/lovelace/badges/hui-state-label-badge.ts b/src/panels/lovelace/badges/hui-state-label-badge.ts index cc9a769c80..92b06a1736 100644 --- a/src/panels/lovelace/badges/hui-state-label-badge.ts +++ b/src/panels/lovelace/badges/hui-state-label-badge.ts @@ -1,20 +1,20 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import "../../../components/entity/ha-state-label-badge"; +import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; import { HomeAssistant } from "../../../types"; import { actionHandler } from "../common/directives/action-handler-directive"; import { handleAction } from "../common/handle-action"; import { hasAction } from "../common/has-action"; import { LovelaceBadge } from "../types"; import { StateLabelBadgeConfig } from "./types"; -import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; @customElement("hui-state-label-badge") export class HuiStateLabelBadge extends LitElement implements LovelaceBadge { @property({ attribute: false }) public hass?: HomeAssistant; - @property() protected _config?: StateLabelBadgeConfig; + @state() protected _config?: StateLabelBadgeConfig; public setConfig(config: StateLabelBadgeConfig): void { this._config = config; diff --git a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts index bfdf039b94..78de1e6a4d 100644 --- a/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-fan-modes-card-feature.ts @@ -5,15 +5,12 @@ import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; import "../../../components/ha-control-select"; import type { ControlSelectOption } from "../../../components/ha-control-select"; import "../../../components/ha-control-select-menu"; import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu"; -import { - ClimateEntity, - ClimateEntityFeature, - computeFanModeIcon, -} from "../../../data/climate"; +import { ClimateEntity, ClimateEntityFeature } from "../../../data/climate"; import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; @@ -136,7 +133,13 @@ class HuiClimateFanModesCardFeature "fan_mode", mode ), - path: computeFanModeIcon(mode), + icon: html``, })); if (this._config.style === "icons") { @@ -171,12 +174,19 @@ class HuiClimateFanModesCardFeature @selected=${this._valueChanged} @closed=${stopPropagation} > - + ${this._currentFanMode + ? html`` + : html` `} ${options.map( (option) => html` - - ${option.label} + ${option.icon}${option.label} ` )} diff --git a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts index 4477359e1d..2cd020cc22 100644 --- a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts @@ -7,13 +7,13 @@ import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; import { stateColorCss } from "../../../common/entity/state_color"; import "../../../components/ha-control-select"; -import "../../../components/ha-control-select-menu"; import type { ControlSelectOption } from "../../../components/ha-control-select"; +import "../../../components/ha-control-select-menu"; import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu"; import { ClimateEntity, + climateHvacModeIcon, compareClimateHvacModes, - computeHvacModeIcon, HvacMode, } from "../../../data/climate"; import { UNAVAILABLE } from "../../../data/entity"; @@ -130,7 +130,12 @@ class HuiClimateHvacModesCardFeature .map((mode) => ({ value: mode, label: this.hass!.formatEntityState(this.stateObj!, mode), - path: computeHvacModeIcon(mode), + icon: html` + + `, })); if (this._config.style === "dropdown") { @@ -147,15 +152,20 @@ class HuiClimateHvacModesCardFeature @selected=${this._valueChanged} @closed=${stopPropagation} > - + ${this._currentHvacMode + ? html` + + ` + : html` + + `} ${options.map( (option) => html` - - ${option.label} + ${option.icon}${option.label} ` )} diff --git a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts index 2067a17739..410f8ce942 100644 --- a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts @@ -5,15 +5,12 @@ import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; import "../../../components/ha-control-select"; import type { ControlSelectOption } from "../../../components/ha-control-select"; import "../../../components/ha-control-select-menu"; import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu"; -import { - ClimateEntity, - ClimateEntityFeature, - computePresetModeIcon, -} from "../../../data/climate"; +import { ClimateEntity, ClimateEntityFeature } from "../../../data/climate"; import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; @@ -138,7 +135,13 @@ class HuiClimatePresetModesCardFeature "preset_mode", mode ), - path: computePresetModeIcon(mode), + icon: html``, })); if (this._config.style === "icons") { @@ -176,12 +179,21 @@ class HuiClimatePresetModesCardFeature @selected=${this._valueChanged} @closed=${stopPropagation} > - + ${this._currentPresetMode + ? html`` + : html` + + `} ${options.map( (option) => html` - - ${option.label} + ${option.icon}${option.label} ` )} diff --git a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts index a3e4c845aa..94a366712a 100644 --- a/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-humidifier-modes-card-feature.ts @@ -5,16 +5,16 @@ import { customElement, property, query, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; import "../../../components/ha-control-select"; import type { ControlSelectOption } from "../../../components/ha-control-select"; import "../../../components/ha-control-select-menu"; import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu"; -import { - HumidifierEntityFeature, - HumidifierEntity, - computeHumidiferModeIcon, -} from "../../../data/humidifier"; import { UNAVAILABLE } from "../../../data/entity"; +import { + HumidifierEntity, + HumidifierEntityFeature, +} from "../../../data/humidifier"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { HumidifierModesCardFeatureConfig } from "./types"; @@ -136,7 +136,13 @@ class HuiHumidifierModesCardFeature "mode", mode ), - path: computeHumidiferModeIcon(mode), + icon: html``, })); if (this._config.style === "icons") { @@ -168,12 +174,22 @@ class HuiHumidifierModesCardFeature @selected=${this._valueChanged} @closed=${stopPropagation} > - + ${this._currentMode + ? html`` + : html``} ${options.map( (option) => html` - - ${option.label} + ${option.icon}${option.label} ` )} diff --git a/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts b/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts index e6863fc9c0..638eeab585 100644 --- a/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts @@ -183,7 +183,7 @@ class HuiTargetTemperatureCardFeature - - +
    @@ -352,6 +355,8 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { .state { margin-left: 16px; + margin-inline-start: 16px; + margin-inline-end: initial; position: relative; bottom: 16px; color: var(--alarm-state-color); diff --git a/src/panels/lovelace/cards/hui-area-card.ts b/src/panels/lovelace/cards/hui-area-card.ts index eb80dbd2f5..ecd9dda6b3 100644 --- a/src/panels/lovelace/cards/hui-area-card.ts +++ b/src/panels/lovelace/cards/hui-area-card.ts @@ -1,22 +1,22 @@ import "@material/mwc-ripple"; import { + mdiFan, + mdiFanOff, mdiLightbulbMultiple, mdiLightbulbMultipleOff, mdiRun, - mdiThermometer, mdiToggleSwitch, mdiToggleSwitchOff, mdiWaterAlert, - mdiWaterPercent, } from "@mdi/js"; import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, TemplateResult, + css, + html, nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -26,16 +26,18 @@ import memoizeOne from "memoize-one"; import { STATES_OFF } from "../../../common/const"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { computeDomain } from "../../../common/entity/compute_domain"; -import { domainIcon } from "../../../common/entity/domain_icon"; import { navigate } from "../../../common/navigate"; -import { formatNumber } from "../../../common/number/format_number"; -import { subscribeOne } from "../../../common/util/subscribe-one"; +import { + formatNumber, + isNumericState, +} from "../../../common/number/format_number"; +import { blankBeforeUnit } from "../../../common/translations/blank_before_unit"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; -import "../../../components/entity/state-badge"; +import { subscribeOne } from "../../../common/util/subscribe-one"; import "../../../components/ha-card"; +import "../../../components/ha-domain-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-state-icon"; -import "../../../components/ha-svg-icon"; import { AreaRegistryEntry, subscribeAreaRegistry, @@ -67,7 +69,7 @@ const TOGGLE_DOMAINS = ["light", "switch", "fan"]; const OTHER_DOMAINS = ["camera"]; -const DEVICE_CLASSES = { +export const DEVICE_CLASSES = { sensor: ["temperature", "humidity"], binary_sensor: ["motion", "moisture"], }; @@ -75,11 +77,7 @@ const DEVICE_CLASSES = { const DOMAIN_ICONS = { light: { on: mdiLightbulbMultiple, off: mdiLightbulbMultipleOff }, switch: { on: mdiToggleSwitch, off: mdiToggleSwitchOff }, - fan: { on: domainIcon("fan"), off: domainIcon("fan") }, - sensor: { - temperature: mdiThermometer, - humidity: mdiWaterPercent, - }, + fan: { on: mdiFan, off: mdiFanOff }, binary_sensor: { motion: mdiRun, moisture: mdiWaterAlert, @@ -113,6 +111,8 @@ export class HuiAreaCard @state() private _areas?: AreaRegistryEntry[]; + private _deviceClasses: { [key: string]: string[] } = DEVICE_CLASSES; + private _ratio: { w: number; h: number; @@ -123,6 +123,7 @@ export class HuiAreaCard areaId: string, devicesInArea: Set, registryEntities: EntityRegistryEntry[], + deviceClasses: { [key: string]: string[] }, states: HomeAssistant["states"] ) => { const entitiesInArea = registryEntities @@ -156,7 +157,7 @@ export class HuiAreaCard if ( (SENSOR_DOMAINS.includes(domain) || ALERT_DOMAINS.includes(domain)) && - !DEVICE_CLASSES[domain].includes( + !deviceClasses[domain].includes( stateObj.attributes.device_class || "" ) ) { @@ -173,11 +174,12 @@ export class HuiAreaCard } ); - private _isOn(domain: string, deviceClass?: string): boolean | undefined { + private _isOn(domain: string, deviceClass?: string): HassEntity | undefined { const entities = this._entitiesByDomain( this._config!.area, this._devicesInArea(this._config!.area, this._devices!), this._entities!, + this._deviceClasses, this.hass.states )[domain]; if (!entities) { @@ -189,7 +191,7 @@ export class HuiAreaCard (entity) => entity.attributes.device_class === deviceClass ) : entities - ).some( + ).find( (entity) => !isUnavailableState(entity.state) && !STATES_OFF.includes(entity.state) ); @@ -200,6 +202,7 @@ export class HuiAreaCard this._config!.area, this._devicesInArea(this._config!.area, this._devices!), this._entities!, + this._deviceClasses, this.hass.states )[domain].filter((entity) => deviceClass ? entity.attributes.device_class === deviceClass : true @@ -209,10 +212,7 @@ export class HuiAreaCard } let uom; const values = entities.filter((entity) => { - if ( - !entity.attributes.unit_of_measurement || - isNaN(Number(entity.state)) - ) { + if (!isNumericState(entity) || isNaN(Number(entity.state))) { return false; } if (!uom) { @@ -230,7 +230,7 @@ export class HuiAreaCard ); return `${formatNumber(sum / values.length, this.hass!.locale, { maximumFractionDigits: 1, - })} ${uom}`; + })}${uom ? blankBeforeUnit(uom, this.hass!.locale) : ""}${uom || ""}`; } private _area = memoizeOne( @@ -273,6 +273,14 @@ export class HuiAreaCard } this._config = config; + + this._deviceClasses = { ...DEVICE_CLASSES }; + if (config.sensor_classes) { + this._deviceClasses.sensor = config.sensor_classes; + } + if (config.alert_classes) { + this._deviceClasses.binary_sensor = config.alert_classes; + } } protected shouldUpdate(changedProps: PropertyValues): boolean { @@ -314,6 +322,7 @@ export class HuiAreaCard this._config.area, this._devicesInArea(this._config.area, this._devices), this._entities, + this._deviceClasses, this.hass.states ); @@ -355,6 +364,7 @@ export class HuiAreaCard this._config.area, this._devicesInArea(this._config.area, this._devices), this._entities, + this._deviceClasses, this.hass.states ); const area = this._area(this._config.area, this._areas); @@ -372,19 +382,21 @@ export class HuiAreaCard if (!(domain in entitiesByDomain)) { return; } - DEVICE_CLASSES[domain].forEach((deviceClass) => { + this._deviceClasses[domain].forEach((deviceClass) => { if ( entitiesByDomain[domain].some( (entity) => entity.attributes.device_class === deviceClass ) ) { sensors.push(html` - ${DOMAIN_ICONS[domain][deviceClass] - ? html`` - : ""} - ${this._average(domain, deviceClass)} +
    + + ${this._average(domain, deviceClass)} +
    `); } }); @@ -425,19 +437,20 @@ export class HuiAreaCard
    ${ALERT_DOMAINS.map((domain) => { if (!(domain in entitiesByDomain)) { - return ""; + return nothing; } - return DEVICE_CLASSES[domain].map((deviceClass) => - this._isOn(domain, deviceClass) + return this._deviceClasses[domain].map((deviceClass) => { + const entity = this._isOn(domain, deviceClass); + return entity ? html` - ${DOMAIN_ICONS[domain][deviceClass] - ? html`` - : ""} + ` - : "" - ); + : nothing; + }); })}
    @@ -554,14 +567,32 @@ export class HuiAreaCard margin-top: 8px; } + .sensor { + white-space: nowrap; + float: left; + margin-right: 4px; + margin-inline-end: 4px; + margin-inline-start: initial; + } + .alerts { padding: 16px; } - .alerts ha-svg-icon { + ha-state-icon { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + } + + .alerts ha-state-icon { background: var(--accent-color); color: var(--text-accent-color, var(--text-primary-color)); padding: 8px; + margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; border-radius: 50%; } @@ -586,6 +617,8 @@ export class HuiAreaCard background-color: var(--area-button-color, #727272b2); border-radius: 50%; margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; --mdc-icon-button-size: 44px; } .on { diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index 9491dd07e3..703ad164cf 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -191,6 +191,8 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { @blur=${this.handleRippleBlur} @mousedown=${this.handleRippleActivate} @mouseup=${this.handleRippleDeactivate} + @mouseenter=${this.handleRippleMouseEnter} + @mouseleave=${this.handleRippleMouseLeave} @touchstart=${this.handleRippleActivate} @touchend=${this.handleRippleDeactivate} @touchcancel=${this.handleRippleDeactivate} @@ -204,6 +206,9 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { tabindex=${ifDefined( hasAction(this._config.tap_action) ? "0" : undefined )} + style=${styleMap({ + "--state-color": colored ? this._computeColor(stateObj) : undefined, + })} > ${this._config.show_icon ? html` @@ -214,9 +219,9 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { )} data-state=${ifDefined(stateObj?.state)} .icon=${this._config.icon} - .state=${stateObj} + .hass=${this.hass} + .stateObj=${stateObj} style=${styleMap({ - color: colored ? this._computeColor(stateObj) : undefined, filter: colored ? stateColorBrightness(stateObj) : undefined, height: this._config.icon_height ? this._config.icon_height @@ -279,23 +284,37 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { this._rippleHandlers.startPress(evt); } + @eventOptions({ passive: true }) private handleRippleDeactivate() { this._rippleHandlers.endPress(); } + @eventOptions({ passive: true }) private handleRippleFocus() { this._rippleHandlers.startFocus(); } + @eventOptions({ passive: true }) private handleRippleBlur() { this._rippleHandlers.endFocus(); } + @eventOptions({ passive: true }) + private handleRippleMouseEnter() { + this._rippleHandlers.startHover(); + } + + @eventOptions({ passive: true }) + private handleRippleMouseLeave() { + this._rippleHandlers.endHover(); + } + static get styles(): CSSResultGroup { return [ iconColorCSS, css` ha-card { + --mdc-ripple-color: var(--state-color); cursor: pointer; display: flex; flex-direction: column; @@ -317,9 +336,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { ha-state-icon { width: 40%; height: auto; - color: var(--paper-item-icon-color, #44739e); + max-height: 80%; + color: var(--state-color, var(--paper-item-icon-color, #44739e)); --mdc-icon-size: 100%; --state-inactive-color: var(--paper-item-icon-color, #44739e); + transition: transform 180ms ease-in-out; } ha-state-icon + span { @@ -331,6 +352,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { outline: none; } + :host(:focus-visible) ha-state-icon, + :host(:active) ha-state-icon { + transform: scale(1.2); + } + .state { font-size: 0.9rem; color: var(--secondary-text-color); diff --git a/src/panels/lovelace/cards/hui-calendar-card.ts b/src/panels/lovelace/cards/hui-calendar-card.ts index 3633244705..047118828a 100644 --- a/src/panels/lovelace/cards/hui-calendar-card.ts +++ b/src/panels/lovelace/cards/hui-calendar-card.ts @@ -59,7 +59,7 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { @property({ attribute: false }) public hass?: HomeAssistant; - @property({ attribute: false }) public _events: CalendarEvent[] = []; + @state() private _events: CalendarEvent[] = []; @state() private _config?: CalendarCardConfig; diff --git a/src/panels/lovelace/cards/hui-empty-state-card.ts b/src/panels/lovelace/cards/hui-empty-state-card.ts index 7202ae4ff2..4a61463c0c 100644 --- a/src/panels/lovelace/cards/hui-empty-state-card.ts +++ b/src/panels/lovelace/cards/hui-empty-state-card.ts @@ -60,6 +60,8 @@ export class HuiEmptyStateCard extends LitElement implements LovelaceCard { mwc-button { margin-left: -8px; + margin-inline-start: -8px; + margin-inline-end: initial; } `; } diff --git a/src/panels/lovelace/cards/hui-entity-card.ts b/src/panels/lovelace/cards/hui-entity-card.ts index 2c8b9bc679..fd1b63e13e 100644 --- a/src/panels/lovelace/cards/hui-entity-card.ts +++ b/src/panels/lovelace/cards/hui-entity-card.ts @@ -139,7 +139,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
    + + ${this._config.title + ? html` +

    + ${this._config.title} + +

    + ` + : nothing}
    `}
    @@ -231,6 +253,15 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard { ha-card { height: 100%; } + .card-header { + justify-content: space-between; + display: flex; + } + .card-header ha-icon-button { + --mdc-icon-button-size: 24px; + line-height: 24px; + color: var(--primary-text-color); + } .content { padding: 16px; } diff --git a/src/panels/lovelace/cards/hui-horizontal-stack-card.ts b/src/panels/lovelace/cards/hui-horizontal-stack-card.ts index 349f5867b7..57adc6f581 100644 --- a/src/panels/lovelace/cards/hui-horizontal-stack-card.ts +++ b/src/panels/lovelace/cards/hui-horizontal-stack-card.ts @@ -1,8 +1,10 @@ import { css, CSSResultGroup } from "lit"; +import { customElement } from "lit/decorators"; import { computeCardSize } from "../common/compute-card-size"; import { HuiStackCard } from "./hui-stack-card"; -class HuiHorizontalStackCard extends HuiStackCard { +@customElement("hui-horizontal-stack-card") +export class HuiHorizontalStackCard extends HuiStackCard { public async getCardSize(): Promise { if (!this._cards) { return 0; @@ -27,19 +29,26 @@ class HuiHorizontalStackCard extends HuiStackCard { display: flex; height: 100%; } + #root { + --stack-card-side-margin: 4px; + } #root > * { flex: 1 1 0; margin: var( --horizontal-stack-card-margin, - var(--stack-card-margin, 0 4px) + var(--stack-card-margin, 0 var(--stack-card-side-margin)) ); min-width: 0; } #root > *:first-child { margin-left: 0; + margin-inline-start: 0; + margin-inline-end: var(--stack-card-side-margin); } #root > *:last-child { margin-right: 0; + margin-inline-end: 0; + margin-inline-start: var(--stack-card-side-margin); } `, ]; @@ -48,8 +57,6 @@ class HuiHorizontalStackCard extends HuiStackCard { declare global { interface HTMLElementTagNameMap { - "hui-horitzontal-stack-card": HuiHorizontalStackCard; + "hui-horizontal-stack-card": HuiHorizontalStackCard; } } - -customElements.define("hui-horizontal-stack-card", HuiHorizontalStackCard); diff --git a/src/panels/lovelace/cards/hui-iframe-card.ts b/src/panels/lovelace/cards/hui-iframe-card.ts index f151501bcd..d81de3dd88 100644 --- a/src/panels/lovelace/cards/hui-iframe-card.ts +++ b/src/panels/lovelace/cards/hui-iframe-card.ts @@ -27,7 +27,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard { @property({ type: Boolean, reflect: true }) public isPanel = false; - @property() public hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @state() protected _config?: IframeCardConfig; diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index f7112c8fc1..3c87546d29 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -114,6 +114,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
    +
    diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index c3076985f4..f5ea4f1a10 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -166,7 +166,7 @@ class HuiMapCard extends LitElement implements LovelaceCard { .paths=${this._getHistoryPaths(this._config, this._stateHistory)} .autoFit=${this._config.auto_fit || false} .fitZones=${this._config.fit_zones} - .darkMode=${this._config.dark_mode} + ?darkMode=${this._config.dark_mode} interactiveZones renderPassive > diff --git a/src/panels/lovelace/cards/hui-markdown-card.ts b/src/panels/lovelace/cards/hui-markdown-card.ts index f32bd7b228..a6d75a00fa 100644 --- a/src/panels/lovelace/cards/hui-markdown-card.ts +++ b/src/panels/lovelace/cards/hui-markdown-card.ts @@ -38,7 +38,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { @property({ attribute: false }) public hass?: HomeAssistant; - @property({ type: Boolean }) public editMode?: boolean; + @property({ type: Boolean }) public editMode = false; @state() private _config?: MarkdownCardConfig; diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index 8127e215f4..7d5a1d8cc3 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -234,7 +234,11 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { >
    - +
    ${this._config!.name || computeStateName(this.hass!.states[this._config!.entity])} diff --git a/src/panels/lovelace/cards/hui-picture-card.ts b/src/panels/lovelace/cards/hui-picture-card.ts index 46b0484478..be5902ba84 100644 --- a/src/panels/lovelace/cards/hui-picture-card.ts +++ b/src/panels/lovelace/cards/hui-picture-card.ts @@ -6,7 +6,7 @@ import { nothing, PropertyValues, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; @@ -38,7 +38,7 @@ export class HuiPictureCard extends LitElement implements LovelaceCard { @property({ attribute: false }) public hass?: HomeAssistant; - @property() protected _config?: PictureCardConfig; + @state() private _config?: PictureCardConfig; public getCardSize(): number { return 5; diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts index db2068cb82..efded2eb6d 100644 --- a/src/panels/lovelace/cards/hui-picture-entity-card.ts +++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts @@ -181,6 +181,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard { hui-image { cursor: pointer; + height: 100%; } .footer { diff --git a/src/panels/lovelace/cards/hui-picture-glance-card.ts b/src/panels/lovelace/cards/hui-picture-glance-card.ts index e8f6fed98e..77fdcb3cce 100644 --- a/src/panels/lovelace/cards/hui-picture-glance-card.ts +++ b/src/panels/lovelace/cards/hui-picture-glance-card.ts @@ -278,7 +278,8 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { > @@ -345,6 +346,8 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { .box .title { font-weight: 500; margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } ha-icon-button { diff --git a/src/panels/lovelace/cards/hui-plant-status-card.ts b/src/panels/lovelace/cards/hui-plant-status-card.ts index 74c080031a..80435b1f0b 100644 --- a/src/panels/lovelace/cards/hui-plant-status-card.ts +++ b/src/panels/lovelace/cards/hui-plant-status-card.ts @@ -16,7 +16,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { fireEvent } from "../../../common/dom/fire_event"; -import { batteryIcon } from "../../../common/entity/battery_icon"; +import { batteryLevelIcon } from "../../../common/entity/battery_icon"; import { computeStateName } from "../../../common/entity/compute_state_name"; import "../../../components/ha-card"; import "../../../components/ha-svg-icon"; @@ -252,7 +252,7 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard { private computeIcon(attr: string, batLvl: number): string { if (attr === "battery") { - return batteryIcon(batLvl); + return batteryLevelIcon(batLvl); } return SENSOR_ICONS[attr]; } diff --git a/src/panels/lovelace/cards/hui-stack-card.ts b/src/panels/lovelace/cards/hui-stack-card.ts index dafda696dc..49ea00828a 100644 --- a/src/panels/lovelace/cards/hui-stack-card.ts +++ b/src/panels/lovelace/cards/hui-stack-card.ts @@ -28,9 +28,9 @@ export abstract class HuiStackCard @property({ attribute: false }) public hass?: HomeAssistant; - @property() public editMode?: boolean; + @property({ type: Boolean }) public editMode = false; - @property() protected _cards?: LovelaceCard[]; + @state() protected _cards?: LovelaceCard[]; @state() protected _config?: T; diff --git a/src/panels/lovelace/cards/hui-statistic-card.ts b/src/panels/lovelace/cards/hui-statistic-card.ts index edbd7c3fe1..e5beb4a209 100644 --- a/src/panels/lovelace/cards/hui-statistic-card.ts +++ b/src/panels/lovelace/cards/hui-statistic-card.ts @@ -144,7 +144,8 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard {
    @@ -298,6 +299,8 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard { .value { font-size: 28px; margin-right: 4px; + margin-inline-end: 4px; + margin-inline-start: initial; } .measurement { diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index b3470aeed5..98faa89eaf 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -28,27 +28,29 @@ import { DOMAINS_TOGGLE } from "../../../common/const"; import { computeDomain } from "../../../common/entity/compute_domain"; import { stateActive } from "../../../common/entity/state_active"; import { stateColorCss } from "../../../common/entity/state_color"; -import { stateIconPath } from "../../../common/entity/state_icon_path"; import "../../../components/ha-card"; +import "../../../components/ha-state-icon"; +import "../../../components/ha-svg-icon"; import "../../../components/tile/ha-tile-badge"; import "../../../components/tile/ha-tile-icon"; import "../../../components/tile/ha-tile-image"; +import type { TileImageStyle } from "../../../components/tile/ha-tile-image"; import "../../../components/tile/ha-tile-info"; import { cameraUrlWithWidthHeight } from "../../../data/camera"; import { isUnavailableState } from "../../../data/entity"; import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor"; +import { UpdateEntity, computeUpdateStateDisplay } from "../../../data/update"; import { HomeAssistant } from "../../../types"; +import "../card-features/hui-card-features"; import { actionHandler } from "../common/directives/action-handler-directive"; import { findEntities } from "../common/find-entities"; import { handleAction } from "../common/handle-action"; import { hasAction } from "../common/has-action"; import "../components/hui-timestamp-display"; -import "../card-features/hui-card-features"; import type { LovelaceCard, LovelaceCardEditor } from "../types"; -import { computeTileBadge } from "./tile/badges/tile-badge"; +import { renderTileBadge } from "./tile/badges/tile-badge"; import type { ThermostatCardConfig, TileCardConfig } from "./types"; -import { UpdateEntity, computeUpdateStateDisplay } from "../../../data/update"; const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"]; @@ -61,6 +63,11 @@ export const getEntityDefaultTileIconAction = (entityId: string) => { return supportsIconAction ? "toggle" : "more-info"; }; +const DOMAIN_IMAGE_STYLE: Record = { + update: "square", + media_player: "rounded-square", +}; + @customElement("hui-tile-card") export class HuiTileCard extends LitElement implements LovelaceCard { public static async getConfigElement(): Promise { @@ -335,17 +342,14 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
    - - + + + + + +
    @@ -354,9 +358,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard { `; } - const icon = this._config.icon || stateObj.attributes.icon; - const iconPath = stateIconPath(stateObj); - const name = this._config.name || stateObj.attributes.friendly_name; const localizedState = this._config.hide_state @@ -376,7 +377,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard { const imageUrl = this._config.show_entity_picture ? this._getImageUrl(stateObj) : undefined; - const badge = computeTileBadge(stateObj, this.hass); return html` @@ -413,7 +413,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { ${imageUrl ? html` ` @@ -421,27 +421,18 @@ export class HuiTileCard extends LitElement implements LovelaceCard { + > + + `} - ${badge - ? html` - - ` - : nothing} + ${renderTileBadge(stateObj, this.hass)}
    @@ -509,7 +500,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { margin-inline-start: initial; margin-inline-end: initial; } - .vertical .info { + .vertical ha-tile-info { width: 100%; } .icon-container { @@ -521,23 +512,27 @@ export class HuiTileCard extends LitElement implements LovelaceCard { direction: var(--direction); transition: transform 180ms ease-in-out; } - .icon-container .icon { + .icon-container ha-tile-icon, + .icon-container ha-tile-image { --tile-icon-color: var(--tile-color); user-select: none; -ms-user-select: none; -webkit-user-select: none; -moz-user-select: none; } - .icon-container .badge { + .icon-container ha-tile-badge { position: absolute; top: -3px; right: -3px; } + .icon-container:not([role="button"]) { + pointer-events: none; + } .icon-container[role="button"]:focus-visible, .icon-container[role="button"]:active { transform: scale(1.2); } - .info { + ha-tile-info { position: relative; padding: 12px; flex: 1; @@ -557,6 +552,10 @@ export class HuiTileCard extends LitElement implements LovelaceCard { animation: pulse 1s infinite; } + ha-tile-badge.not-found { + --tile-badge-background-color: var(--red-color); + } + @keyframes pulse { 0% { opacity: 1; diff --git a/src/panels/lovelace/cards/hui-todo-list-card.ts b/src/panels/lovelace/cards/hui-todo-list-card.ts index 1d1c6b6c82..5094ea3d73 100644 --- a/src/panels/lovelace/cards/hui-todo-list-card.ts +++ b/src/panels/lovelace/cards/hui-todo-list-card.ts @@ -20,11 +20,10 @@ import { html, nothing, } from "lit"; -import { customElement, property, query, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { repeat } from "lit/directives/repeat"; import memoizeOne from "memoize-one"; -import type { SortableEvent } from "sortablejs"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-card"; @@ -35,6 +34,7 @@ import "../../../components/ha-list-item"; import "../../../components/ha-markdown-element"; import "../../../components/ha-relative-time"; import "../../../components/ha-select"; +import "../../../components/ha-sortable"; import "../../../components/ha-svg-icon"; import "../../../components/ha-textfield"; import type { HaTextField } from "../../../components/ha-textfield"; @@ -50,14 +50,12 @@ import { updateItem, } from "../../../data/todo"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; -import type { SortableInstance } from "../../../resources/sortable"; import { HomeAssistant } from "../../../types"; import { showTodoItemEditDialog } from "../../todo/show-dialog-todo-item-editor"; import { findEntities } from "../common/find-entities"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { TodoListCardConfig } from "./types"; -import { sortableStyles } from "../../../resources/ha-sortable-style"; @customElement("hui-todo-list-card") export class HuiTodoListCard extends LitElement implements LovelaceCard { @@ -96,10 +94,6 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { private _unsubItems?: Promise; - private _sortable?: SortableInstance; - - @query("#unchecked") private _uncheckedContainer?: HTMLElement; - connectedCallback(): void { super.connectedCallback(); if (this.hasUpdated) { @@ -230,89 +224,98 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { ` : nothing}
    - ${uncheckedItems.length - ? html`
    - - ${this.hass!.localize( - "ui.panel.lovelace.cards.todo-list.unchecked_items" + + + ${uncheckedItems.length + ? html` +
    +

    + ${this.hass!.localize( + "ui.panel.lovelace.cards.todo-list.unchecked_items" + )} +

    + ${this.todoListSupportsFeature( + TodoListEntityFeature.MOVE_TODO_ITEM + ) + ? html` + + + ${this.hass!.localize( + this._reordering + ? "ui.panel.lovelace.cards.todo-list.exit_reorder_items" + : "ui.panel.lovelace.cards.todo-list.reorder_items" + )} + + + + ` + : nothing} +
    + ${this._renderItems(uncheckedItems, unavailable)} + ` + : html`

    + ${this.hass.localize( + "ui.panel.lovelace.cards.todo-list.no_unchecked_items" )} - - ${this.todoListSupportsFeature( - TodoListEntityFeature.MOVE_TODO_ITEM - ) - ? html` - - +

    `} + ${checkedItems.length + ? html` +
    +
    +
    +

    ${this.hass!.localize( - this._reordering - ? "ui.panel.lovelace.cards.todo-list.exit_reorder_items" - : "ui.panel.lovelace.cards.todo-list.reorder_items" + "ui.panel.lovelace.cards.todo-list.checked_items" )} - - - - ` - : nothing} -

    - - ${this._renderItems(uncheckedItems, unavailable)} - ` - : html`

    - ${this.hass.localize( - "ui.panel.lovelace.cards.todo-list.no_unchecked_items" - )} -

    `} - ${checkedItems.length - ? html` -
    -
    - - ${this.hass!.localize( - "ui.panel.lovelace.cards.todo-list.checked_items" - )} - - ${this.todoListSupportsFeature( - TodoListEntityFeature.DELETE_TODO_ITEM - ) - ? html` - - - ${this.hass!.localize( - "ui.panel.lovelace.cards.todo-list.clear_items" - )} - - - - ` - : nothing} -
    - - ${this._renderItems(checkedItems, unavailable)} - - ` - : ""} + + ${this.todoListSupportsFeature( + TodoListEntityFeature.DELETE_TODO_ITEM + ) + ? html` + + + ${this.hass!.localize( + "ui.panel.lovelace.cards.todo-list.clear_items" + )} + + + + ` + : nothing} +
    +
    + ${this._renderItems(checkedItems, unavailable)} + ` + : ""} + + `; } @@ -344,6 +347,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { left .hasMeta=${showReorder || showDelete} class="editRow ${classMap({ + draggable: item.status === TodoItemStatus.NeedsAction, completed: item.status === TodoItemStatus.Completed, multiline: Boolean(item.description || item.due), })}" @@ -471,6 +475,12 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { } private async _completeItem(ev): Promise { + let focusedIndex: number | undefined; + let list: List | undefined; + if (ev.type === "keydown") { + list = this.renderRoot.querySelector("mwc-list")!; + focusedIndex = list.getFocusedItemIndex(); + } const item = this._getItem(ev.currentTarget.itemId); if (!item) { return; @@ -483,17 +493,11 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { ? TodoItemStatus.Completed : TodoItemStatus.NeedsAction, }); - await this.updateComplete; - const newList: List = this.shadowRoot!.querySelector( - item.status === TodoItemStatus.NeedsAction ? "#checked" : "#unchecked" - )!; - await newList.updateComplete; - const items = - item.status === TodoItemStatus.NeedsAction - ? this._getCheckedItems(this._items) - : this._getUncheckedItems(this._items); - const index = items.findIndex((itm) => itm.uid === item.uid); - newList.focusItemAtIndex(index); + if (focusedIndex !== undefined && list) { + await this.updateComplete; + await list.updateComplete; + list.focusItemAtIndex(focusedIndex); + } } private async _clearCompletedItems(): Promise { @@ -553,46 +557,18 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { private async _toggleReorder() { this._reordering = !this._reordering; - await this.updateComplete; - if (this._reordering) { - this._createSortable(); - } else { - this._sortable?.destroy(); - this._sortable = undefined; - } } - private async _createSortable() { - const Sortable = (await import("../../../resources/sortable")).default; - this._sortable = new Sortable(this._uncheckedContainer!, { - animation: 150, - fallbackClass: "sortable-fallback", - dataIdAttr: "item-id", - handle: "ha-svg-icon", - onChoose: (evt: SortableEvent) => { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - if (evt.newIndex === undefined || evt.oldIndex === undefined) { - return; - } - // Since this is `onEnd` event, it's possible that - // an item was dragged away and was put back to its original position. - if (evt.oldIndex !== evt.newIndex) { - this._moveItem(evt.oldIndex, evt.newIndex); - } - }, - }); + private async _itemMoved(ev: CustomEvent) { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; + this._moveItem(oldIndex, newIndex); } private async _moveItem(oldIndex: number, newIndex: number) { + // correct index for header + oldIndex -= 1; + newIndex -= 1; const uncheckedItems = this._getUncheckedItems(this._items); const item = uncheckedItems[oldIndex]; let prevItem: TodoItem | undefined; @@ -621,165 +597,166 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { } static get styles(): CSSResultGroup { - return [ - sortableStyles, - css` - ha-card { - height: 100%; - box-sizing: border-box; - } + return css` + ha-card { + height: 100%; + box-sizing: border-box; + } - .has-header { - padding-top: 0; - } + .has-header { + padding-top: 0; + } - .addRow { - padding: 16px; - padding-bottom: 0; - position: relative; - } + .addRow { + padding: 16px; + padding-bottom: 0; + position: relative; + } - .addRow ha-icon-button { - position: absolute; - right: 16px; - inset-inline-start: initial; - inset-inline-end: 16px; - } + .addRow ha-icon-button { + position: absolute; + right: 16px; + inset-inline-start: initial; + inset-inline-end: 16px; + } - .addRow, - .header { - display: flex; - flex-direction: row; - align-items: center; - } + .addRow, + .header { + display: flex; + flex-direction: row; + align-items: center; + } - .header { - padding-left: 30px; - padding-right: 16px; - padding-inline-start: 30px; - padding-inline-end: 16px; - margin-top: 8px; - justify-content: space-between; - direction: var(--direction); - } + .header { + padding-left: 30px; + padding-right: 16px; + padding-inline-start: 30px; + padding-inline-end: 16px; + margin-top: 8px; + justify-content: space-between; + direction: var(--direction); + } - .header span { - color: var(--primary-text-color); - font-weight: 500; - } + .header h2 { + color: var(--primary-text-color); + font-size: inherit; + font-weight: 500; + } - .empty { - padding: 16px 32px; - } + .empty { + padding: 16px 32px; + display: inline-block; + } - .item { - margin-top: 8px; - } + .item { + margin-top: 8px; + } - ha-check-list-item { - --mdc-list-item-meta-size: 56px; - min-height: 56px; - height: auto; - } + ha-check-list-item { + --mdc-list-item-meta-size: 56px; + min-height: 56px; + height: auto; + } - ha-check-list-item.multiline { - align-items: flex-start; - --check-list-item-graphic-margin-top: 8px; - } + ha-check-list-item.multiline { + align-items: flex-start; + --check-list-item-graphic-margin-top: 8px; + } - .row { - display: flex; - justify-content: space-between; - } + .row { + display: flex; + justify-content: space-between; + } - .multiline .column { - display: flex; - flex-direction: column; - margin-top: 18px; - margin-bottom: 12px; - } + .multiline .column { + display: flex; + flex-direction: column; + margin-top: 18px; + margin-bottom: 12px; + } - .completed .summary { - text-decoration: line-through; - } + .completed .summary { + text-decoration: line-through; + } - .description, - .due { - font-size: 12px; - color: var(--secondary-text-color); - } + .description, + .due { + font-size: 12px; + color: var(--secondary-text-color); + } - .description { - white-space: initial; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 3; - line-clamp: 3; - -webkit-box-orient: vertical; - } + .description { + white-space: initial; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + line-clamp: 3; + -webkit-box-orient: vertical; + } - .description p { - margin: 0; - } + .description p { + margin: 0; + } - .description a { - color: var(--primary-color); - } + .description a { + color: var(--primary-color); + } - .due { - display: flex; - align-items: center; - } + .due { + display: flex; + align-items: center; + } - .due ha-svg-icon { - margin-right: 4px; - --mdc-icon-size: 14px; - } + .due ha-svg-icon { + margin-right: 4px; + margin-inline-end: 4px; + margin-inline-start: initial; + --mdc-icon-size: 14px; + } - .due.overdue { - color: var(--warning-color); - } + .due.overdue { + color: var(--warning-color); + } - .completed .due.overdue { - color: var(--secondary-text-color); - } + .completed .due.overdue { + color: var(--secondary-text-color); + } - .handle { - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - height: 24px; - padding: 16px 4px; - } + .handle { + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; + height: 24px; + padding: 16px 4px; + } - .deleteItemButton { - position: relative; - left: 8px; - } + .deleteItemButton { + position: relative; + left: 8px; + } - ha-textfield { - flex-grow: 1; - } + ha-textfield { + flex-grow: 1; + } - .divider { - height: 1px; - background-color: var(--divider-color); - margin: 10px 0; - } + .divider { + height: 1px; + background-color: var(--divider-color); + margin: 10px 0; + } - .clearall { - cursor: pointer; - } + .clearall { + cursor: pointer; + } - .todoList { - display: block; - padding: 8px; - } + .todoList { + display: block; + padding: 8px; + } - .warning { - color: var(--error-color); - } - `, - ]; + .warning { + color: var(--error-color); + } + `; } } diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 6a8e6e7dd5..781f02bd42 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -75,8 +75,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { @state() private _subscribed?: Promise<() => void>; - @property({ type: Boolean, reflect: true, attribute: "veryverynarrow" }) - private _veryVeryNarrow = false; + // @todo Consider reworking to eliminate need for attribute since it is manipulated internally + @property({ type: Boolean, reflect: true }) public veryVeryNarrow = false; private _resizeObserver?: ResizeObserver; @@ -227,7 +227,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { ); const forecast = this._config?.show_forecast !== false && forecastData?.forecast?.length - ? forecastData.forecast.slice(0, this._veryVeryNarrow ? 3 : 5) + ? forecastData.forecast.slice(0, this.veryVeryNarrow ? 3 : 5) : undefined; const weather = !forecast || this._config?.show_current !== false; @@ -256,7 +256,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { html` `}
    @@ -452,7 +453,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { } else { this.removeAttribute("verynarrow"); } - this._veryVeryNarrow = card.offsetWidth < 245; + this.veryVeryNarrow = card.offsetWidth < 245; } private _showValue(item?: any): boolean { diff --git a/src/panels/lovelace/cards/tile/badges/tile-badge-climate.ts b/src/panels/lovelace/cards/tile/badges/tile-badge-climate.ts index a00d773e56..ed75266f74 100644 --- a/src/panels/lovelace/cards/tile/badges/tile-badge-climate.ts +++ b/src/panels/lovelace/cards/tile/badges/tile-badge-climate.ts @@ -1,20 +1,36 @@ +import { html, nothing } from "lit"; +import { styleMap } from "lit/directives/style-map"; import { stateColorCss } from "../../../../../common/entity/state_color"; +import "../../../../../components/ha-attribute-icon"; +import "../../../../../components/tile/ha-tile-badge"; import { - CLIMATE_HVAC_ACTION_ICONS, CLIMATE_HVAC_ACTION_TO_MODE, ClimateEntity, } from "../../../../../data/climate"; -import { ComputeBadgeFunction } from "./tile-badge"; +import { RenderBadgeFunction } from "./tile-badge"; -export const computeClimateBadge: ComputeBadgeFunction = (stateObj) => { +export const renderClimateBadge: RenderBadgeFunction = (stateObj, hass) => { const hvacAction = (stateObj as ClimateEntity).attributes.hvac_action; if (!hvacAction || hvacAction === "off") { - return undefined; + return nothing; } - return { - iconPath: CLIMATE_HVAC_ACTION_ICONS[hvacAction], - color: stateColorCss(stateObj, CLIMATE_HVAC_ACTION_TO_MODE[hvacAction]), - }; + return html` + + + + + `; }; diff --git a/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts b/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts index 709f4e7c68..d4186ce0be 100644 --- a/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts +++ b/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts @@ -1,20 +1,32 @@ +import { html, nothing } from "lit"; +import { styleMap } from "lit/directives/style-map"; import { stateColorCss } from "../../../../../common/entity/state_color"; +import "../../../../../components/ha-attribute-icon"; +import "../../../../../components/tile/ha-tile-badge"; import { - HUMIDIFIER_ACTION_ICONS, HUMIDIFIER_ACTION_MODE, HumidifierEntity, } from "../../../../../data/humidifier"; -import { ComputeBadgeFunction } from "./tile-badge"; +import { RenderBadgeFunction } from "./tile-badge"; -export const computeHumidifierBadge: ComputeBadgeFunction = (stateObj) => { - const hvacAction = (stateObj as HumidifierEntity).attributes.action; +export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => { + const action = (stateObj as HumidifierEntity).attributes.action; - if (!hvacAction || hvacAction === "off") { - return undefined; + if (!action || action === "off") { + return nothing; } - return { - iconPath: HUMIDIFIER_ACTION_ICONS[hvacAction], - color: stateColorCss(stateObj, HUMIDIFIER_ACTION_MODE[hvacAction]), - }; + return html` + + + + + `; }; diff --git a/src/panels/lovelace/cards/tile/badges/tile-badge-person.ts b/src/panels/lovelace/cards/tile/badges/tile-badge-person.ts index 6d58f5af81..4c0fd8be80 100644 --- a/src/panels/lovelace/cards/tile/badges/tile-badge-person.ts +++ b/src/panels/lovelace/cards/tile/badges/tile-badge-person.ts @@ -1,8 +1,13 @@ import { mdiHome, mdiHomeExportOutline } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; +import { html } from "lit"; +import { styleMap } from "lit/directives/style-map"; import { stateColorCss } from "../../../../../common/entity/state_color"; +import "../../../../../components/ha-icon"; +import "../../../../../components/ha-svg-icon"; +import "../../../../../components/tile/ha-tile-badge"; import { HomeAssistant } from "../../../../../types"; -import { ComputeBadgeFunction } from "./tile-badge"; +import { RenderBadgeFunction } from "./tile-badge"; function getZone(entity: HassEntity, hass: HomeAssistant) { const state = entity.state; @@ -15,17 +20,33 @@ function getZone(entity: HassEntity, hass: HomeAssistant) { return zones.find((z) => state === z.attributes.friendly_name); } -function personBadgeIcon(entity: HassEntity) { - const state = entity.state; - return state === "not_home" ? mdiHomeExportOutline : mdiHome; -} - -export const computePersonBadge: ComputeBadgeFunction = (stateObj, hass) => { +export const renderPersonBadge: RenderBadgeFunction = (stateObj, hass) => { const zone = getZone(stateObj, hass); - return { - iconPath: personBadgeIcon(stateObj), - icon: zone?.attributes.icon, - color: stateColorCss(stateObj), - }; + const zoneIcon = zone?.attributes.icon; + + if (zoneIcon) { + return html` + + + + `; + } + + const defaultIcon = + stateObj.state === "not_home" ? mdiHomeExportOutline : mdiHome; + + return html` + + + + `; }; diff --git a/src/panels/lovelace/cards/tile/badges/tile-badge.ts b/src/panels/lovelace/cards/tile/badges/tile-badge.ts index 5281f3591d..e6bdc8fb0c 100644 --- a/src/panels/lovelace/cards/tile/badges/tile-badge.ts +++ b/src/panels/lovelace/cards/tile/badges/tile-badge.ts @@ -1,43 +1,46 @@ import { mdiExclamationThick } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; +import { TemplateResult, html, nothing } from "lit"; +import { styleMap } from "lit/directives/style-map"; import { computeDomain } from "../../../../../common/entity/compute_domain"; import { UNAVAILABLE, UNKNOWN } from "../../../../../data/entity"; import { HomeAssistant } from "../../../../../types"; -import { computeClimateBadge } from "./tile-badge-climate"; -import { computePersonBadge } from "./tile-badge-person"; -import { computeHumidifierBadge } from "./tile-badge-humidifier"; +import { renderClimateBadge } from "./tile-badge-climate"; +import { renderHumidifierBadge } from "./tile-badge-humidifier"; +import { renderPersonBadge } from "./tile-badge-person"; +import "../../../../../components/tile/ha-tile-badge"; +import "../../../../../components/ha-svg-icon"; -export type TileBadge = { - color?: string; - icon?: string; - iconPath?: string; -}; - -export type ComputeBadgeFunction = ( +export type RenderBadgeFunction = ( stateObj: HassEntity, hass: HomeAssistant -) => TileBadge | undefined; +) => TemplateResult | typeof nothing; -export const computeTileBadge: ComputeBadgeFunction = (stateObj, hass) => { +export const renderTileBadge: RenderBadgeFunction = (stateObj, hass) => { if (stateObj.state === UNKNOWN) { - return undefined; + return nothing; } if (stateObj.state === UNAVAILABLE) { - return { - color: "var(--orange-color)", - iconPath: mdiExclamationThick, - }; + return html` + + + + `; } const domain = computeDomain(stateObj.entity_id); switch (domain) { case "person": case "device_tracker": - return computePersonBadge(stateObj, hass); + return renderPersonBadge(stateObj, hass); case "climate": - return computeClimateBadge(stateObj, hass); + return renderClimateBadge(stateObj, hass); case "humidifier": - return computeHumidifierBadge(stateObj, hass); + return renderHumidifierBadge(stateObj, hass); default: - return undefined; + return nothing; } }; diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index cedd812916..d9a26be158 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -324,6 +324,9 @@ export interface HistoryGraphCardConfig extends LovelaceCardConfig { title?: string; show_names?: boolean; logarithmic_scale?: boolean; + min_y_axis?: number; + max_y_axis?: number; + fit_y_data?: boolean; split_device_classes?: boolean; } diff --git a/src/panels/lovelace/common/directives/action-handler-directive.ts b/src/panels/lovelace/common/directives/action-handler-directive.ts index 199cfafc9b..1336727046 100644 --- a/src/panels/lovelace/common/directives/action-handler-directive.ts +++ b/src/panels/lovelace/common/directives/action-handler-directive.ts @@ -14,12 +14,7 @@ import { ActionHandlerDetail, ActionHandlerOptions, } from "../../../../data/lovelace/action_handler"; - -const isTouch = - "ontouchstart" in window || - navigator.maxTouchPoints > 0 || - // @ts-ignore - navigator.msMaxTouchPoints > 0; +import { isTouch } from "../../../../util/is_touch"; interface ActionHandlerType extends HTMLElement { holdTime: number; diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts index a6999ee4cb..92fa589987 100644 --- a/src/panels/lovelace/components/hui-action-editor.ts +++ b/src/panels/lovelace/components/hui-action-editor.ts @@ -72,17 +72,17 @@ const ASSIST_SCHEMA = [ @customElement("hui-action-editor") export class HuiActionEditor extends LitElement { - @property() public config?: ActionConfig; + @property({ attribute: false }) public config?: ActionConfig; @property() public label?: string; - @property() public actions?: UiAction[]; + @property({ attribute: false }) public actions?: UiAction[]; - @property() public defaultAction?: UiAction; + @property({ attribute: false }) public defaultAction?: UiAction; @property() public tooltipText?: string; - @property() protected hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @query("ha-select") private _select!: HaSelect; diff --git a/src/panels/lovelace/components/hui-buttons-base.ts b/src/panels/lovelace/components/hui-buttons-base.ts index 0edeb4c2f2..8417be6bfb 100644 --- a/src/panels/lovelace/components/hui-buttons-base.ts +++ b/src/panels/lovelace/components/hui-buttons-base.ts @@ -17,7 +17,8 @@ import { haStyleScrollbar } from "../../../resources/styles"; export class HuiButtonsBase extends LitElement { @state() public hass!: HomeAssistant; - @property() public configEntities?: EntitiesCardEntityConfig[]; + @property({ attribute: false }) + public configEntities?: EntitiesCardEntityConfig[]; protected render(): TemplateResult { return html` @@ -94,16 +95,22 @@ export class HuiButtonsBase extends LitElement { width: 24px; height: 24px; margin-left: -4px; + margin-inline-start: -4px; + margin-inline-end: initial; margin-top: -2px; } state-badge.no-text { width: 26px; height: 26px; margin-left: -3px; + margin-inline-start: -3px; + margin-inline-end: initial; margin-top: -3px; } ha-assist-chip state-badge { margin-right: -4px; + margin-inline-end: -4px; + margin-inline-start: initial; --mdc-icon-size: 18px; } @media all and (max-width: 450px), all and (max-height: 500px) { diff --git a/src/panels/lovelace/components/hui-card-options.ts b/src/panels/lovelace/components/hui-card-options.ts index 8236d900e3..7f59aaf3e9 100644 --- a/src/panels/lovelace/components/hui-card-options.ts +++ b/src/panels/lovelace/components/hui-card-options.ts @@ -54,7 +54,7 @@ export class HuiCardOptions extends LitElement { @property({ attribute: false }) public lovelace?: Lovelace; - @property() public path?: [number, number]; + @property({ type: Array }) public path?: [number, number]; @queryAssignedNodes() private _assignedNodes?: NodeListOf; diff --git a/src/panels/lovelace/components/hui-color-picker.ts b/src/panels/lovelace/components/hui-color-picker.ts index 5928be7996..8417e9dbd5 100644 --- a/src/panels/lovelace/components/hui-color-picker.ts +++ b/src/panels/lovelace/components/hui-color-picker.ts @@ -17,7 +17,7 @@ export class HuiColorPicker extends LitElement { @property() public helper?: string; - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @property() public value?: string; diff --git a/src/panels/lovelace/components/hui-conditional-base.ts b/src/panels/lovelace/components/hui-conditional-base.ts index 3ffa839fdd..df8f268a26 100644 --- a/src/panels/lovelace/components/hui-conditional-base.ts +++ b/src/panels/lovelace/components/hui-conditional-base.ts @@ -1,5 +1,5 @@ import { PropertyValues, ReactiveElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { listenMediaQuery } from "../../../common/dom/media_query"; import { deepEqual } from "../../../common/util/deep-equal"; import { HomeAssistant } from "../../../types"; @@ -31,12 +31,12 @@ function extractMediaQueries( export class HuiConditionalBase extends ReactiveElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public editMode?: boolean; - - @property() protected _config?: ConditionalCardConfig | ConditionalRowConfig; + @property({ type: Boolean }) public editMode = false; @property({ type: Boolean, reflect: true }) public hidden = false; + @state() protected _config?: ConditionalCardConfig | ConditionalRowConfig; + protected _element?: LovelaceCard | LovelaceRow; private _mediaQueriesListeners: Array<() => void> = []; diff --git a/src/panels/lovelace/components/hui-energy-period-selector.ts b/src/panels/lovelace/components/hui-energy-period-selector.ts index d5d50f1af4..e2717f6908 100644 --- a/src/panels/lovelace/components/hui-energy-period-selector.ts +++ b/src/panels/lovelace/components/hui-energy-period-selector.ts @@ -627,6 +627,8 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { } :host([narrow]) .time-handle { margin-left: auto; + margin-inline-start: auto; + margin-inline-end: initial; } .label { display: flex; @@ -634,12 +636,18 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { justify-content: flex-end; font-size: 20px; margin-left: auto; + margin-inline-start: auto; + margin-inline-end: initial; } :host([narrow]) .label { margin-left: unset; + margin-inline-start: unset; + margin-inline-end: initial; } mwc-button { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; flex-shrink: 0; --mdc-button-outline-color: currentColor; --primary-color: currentColor; diff --git a/src/panels/lovelace/components/hui-entities-toggle.ts b/src/panels/lovelace/components/hui-entities-toggle.ts index 8c28f4b7be..0dfadc7924 100644 --- a/src/panels/lovelace/components/hui-entities-toggle.ts +++ b/src/panels/lovelace/components/hui-entities-toggle.ts @@ -18,7 +18,7 @@ import { turnOnOffEntities } from "../common/entity/turn-on-off-entities"; class HuiEntitiesToggle extends LitElement { @property({ type: Array }) public entities?: string[]; - @property({ attribute: false }) protected hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _toggleEntities?: string[]; diff --git a/src/panels/lovelace/components/hui-entity-editor.ts b/src/panels/lovelace/components/hui-entity-editor.ts index 22ac4f8aa0..77f22a85c0 100644 --- a/src/panels/lovelace/components/hui-entity-editor.ts +++ b/src/panels/lovelace/components/hui-entity-editor.ts @@ -2,7 +2,6 @@ import { mdiDrag } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import type { SortableEvent } from "sortablejs"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-picker"; import type { @@ -10,30 +9,23 @@ import type { HaEntityPickerEntityFilterFunc, } from "../../../components/entity/ha-entity-picker"; import "../../../components/ha-icon-button"; -import { sortableStyles } from "../../../resources/ha-sortable-style"; -import type { SortableInstance } from "../../../resources/sortable"; +import "../../../components/ha-sortable"; import { HomeAssistant } from "../../../types"; import { EntityConfig } from "../entity-rows/types"; @customElement("hui-entity-editor") export class HuiEntityEditor extends LitElement { - @property({ attribute: false }) protected hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; - @property({ attribute: false }) protected entities?: EntityConfig[]; + @property({ attribute: false }) public entities?: EntityConfig[]; - @property() protected entityFilter?: HaEntityPickerEntityFilterFunc; + @property({ attribute: false }) + public entityFilter?: HaEntityPickerEntityFilterFunc; - @property() protected label?: string; + @property() public label?: string; private _entityKeys = new WeakMap(); - private _sortable?: SortableInstance; - - public disconnectedCallback() { - super.disconnectedCallback(); - this._destroySortable(); - } - private _getKey(action: EntityConfig) { if (!this._entityKeys.has(action)) { this._entityKeys.set(action, Math.random().toString()); @@ -55,27 +47,29 @@ export class HuiEntityEditor extends LitElement { this.hass!.localize("ui.panel.lovelace.editor.card.config.required") + ")"} -
    - ${repeat( - this.entities, - (entityConf) => this._getKey(entityConf), - (entityConf, index) => html` -
    -
    - + +
    + ${repeat( + this.entities, + (entityConf) => this._getKey(entityConf), + (entityConf, index) => html` +
    +
    + +
    +
    - -
    - ` - )} -
    + ` + )} +
    + { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._entityMoved(evt); - }, - } - ); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private async _addEntity(ev: CustomEvent): Promise { const value = ev.detail.value; if (value === "") { @@ -132,14 +91,13 @@ export class HuiEntityEditor extends LitElement { fireEvent(this, "entities-changed", { entities: newConfigEntities }); } - private _entityMoved(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) { - return; - } + private _entityMoved(ev: CustomEvent): void { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; const newEntities = this.entities!.concat(); - newEntities.splice(ev.newIndex!, 0, newEntities.splice(ev.oldIndex!, 1)[0]); + newEntities.splice(newIndex, 0, newEntities.splice(oldIndex, 1)[0]); fireEvent(this, "entities-changed", { entities: newEntities }); } @@ -162,39 +120,36 @@ export class HuiEntityEditor extends LitElement { } static get styles(): CSSResultGroup { - return [ - sortableStyles, - css` - ha-entity-picker { - margin-top: 8px; - } - .add-entity { - display: block; - margin-left: 31px; - margin-inline-start: 31px; - margin-inline-end: initial; - direction: var(--direction); - } - .entity { - display: flex; - align-items: center; - } - .entity .handle { - padding-right: 8px; - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - padding-inline-end: 8px; - padding-inline-start: initial; - direction: var(--direction); - } - .entity .handle > * { - pointer-events: none; - } - .entity ha-entity-picker { - flex-grow: 1; - } - `, - ]; + return css` + ha-entity-picker { + margin-top: 8px; + } + .add-entity { + display: block; + margin-left: 31px; + margin-inline-start: 31px; + margin-inline-end: initial; + direction: var(--direction); + } + .entity { + display: flex; + align-items: center; + } + .entity .handle { + padding-right: 8px; + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; + padding-inline-end: 8px; + padding-inline-start: initial; + direction: var(--direction); + } + .entity .handle > * { + pointer-events: none; + } + .entity ha-entity-picker { + flex-grow: 1; + } + `; } } diff --git a/src/panels/lovelace/components/hui-generic-entity-row.ts b/src/panels/lovelace/components/hui-generic-entity-row.ts index 4a6c8d9209..f58677657e 100644 --- a/src/panels/lovelace/components/hui-generic-entity-row.ts +++ b/src/panels/lovelace/components/hui-generic-entity-row.ts @@ -6,7 +6,7 @@ import { html, nothing, } from "lit"; -import { property } from "lit/decorators"; +import { customElement, property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { DOMAINS_INPUT_ROW } from "../../../common/const"; @@ -24,10 +24,11 @@ import { handleAction } from "../common/handle-action"; import { hasAction } from "../common/has-action"; import { createEntityNotFoundWarning } from "./hui-warning"; -class HuiGenericEntityRow extends LitElement { +@customElement("hui-generic-entity-row") +export class HuiGenericEntityRow extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public config?: EntitiesCardEntityConfig; + @property({ attribute: false }) public config?: EntitiesCardEntityConfig; @property() public secondaryText?: string; @@ -204,6 +205,8 @@ class HuiGenericEntityRow extends LitElement { .info { margin-left: 16px; margin-right: 8px; + margin-inline-start: 16px; + margin-inline-end: 8px; flex: 1 1 30%; } .info, @@ -214,10 +217,14 @@ class HuiGenericEntityRow extends LitElement { } .flex ::slotted(*) { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; min-width: 0; } .flex ::slotted([slot="secondary"]) { margin-left: 0; + margin-inline-start: 0; + margin-inline-end: initial; } .secondary, ha-relative-time { @@ -249,4 +256,9 @@ class HuiGenericEntityRow extends LitElement { `; } } -customElements.define("hui-generic-entity-row", HuiGenericEntityRow); + +declare global { + interface HTMLElementTagNameMap { + "hui-generic-entity-row": HuiGenericEntityRow; + } +} diff --git a/src/panels/lovelace/components/hui-image.ts b/src/panels/lovelace/components/hui-image.ts index b46899a864..cbfe010120 100644 --- a/src/panels/lovelace/components/hui-image.ts +++ b/src/panels/lovelace/components/hui-image.ts @@ -400,12 +400,14 @@ export class HuiImage extends LitElement { .container { transition: filter 0.2s linear; + height: 100%; } img { display: block; - height: auto; + height: 100%; width: 100%; + object-fit: cover; } .progress-container { @@ -428,6 +430,12 @@ export class HuiImage extends LitElement { background-size: contain; background-repeat: no-repeat; } + .fill img { + object-fit: fill; + } + .contain img { + object-fit: contain; + } .ratio img, .ratio div { diff --git a/src/panels/lovelace/components/hui-input-list-editor.ts b/src/panels/lovelace/components/hui-input-list-editor.ts index 635eb84800..6616753fc8 100644 --- a/src/panels/lovelace/components/hui-input-list-editor.ts +++ b/src/panels/lovelace/components/hui-input-list-editor.ts @@ -9,11 +9,11 @@ import { EditorTarget } from "../editor/types"; @customElement("hui-input-list-editor") export class HuiInputListEditor extends LitElement { - @property() protected value?: string[]; + @property({ type: Array }) public value?: string[]; - @property() protected hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; - @property() protected inputLabel?: string; + @property() public inputLabel?: string; protected render() { if (!this.value) { @@ -105,6 +105,8 @@ export class HuiInputListEditor extends LitElement { return css` ha-icon-button { margin-right: -24px; + margin-inline-end: -24px; + margin-inline-start: initial; color: var(--secondary-text-color); } ha-textfield { diff --git a/src/panels/lovelace/components/hui-marquee.ts b/src/panels/lovelace/components/hui-marquee.ts index 1fb09284e8..3632c5dbc2 100644 --- a/src/panels/lovelace/components/hui-marquee.ts +++ b/src/panels/lovelace/components/hui-marquee.ts @@ -12,10 +12,10 @@ import { customElement, property } from "lit/decorators"; class HuiMarquee extends LitElement { @property() public text?: string; - @property({ type: Boolean }) public active?: boolean; + @property({ type: Boolean }) public active = false; - @property({ reflect: true, type: Boolean, attribute: "animating" }) - private _animating = false; + // @todo Consider reworking to eliminate need for attribute since it is manipulated internally + @property({ reflect: true, type: Boolean }) public animating = false; protected firstUpdated(changedProps) { super.firstUpdated(changedProps); @@ -33,8 +33,8 @@ class HuiMarquee extends LitElement { protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties); - if (changedProperties.has("text") && this._animating) { - this._animating = false; + if (changedProperties.has("text") && this.animating) { + this.animating = false; } if ( @@ -42,7 +42,7 @@ class HuiMarquee extends LitElement { this.active && this.offsetWidth < this.scrollWidth ) { - this._animating = true; + this.animating = true; } } @@ -54,14 +54,14 @@ class HuiMarquee extends LitElement { return html`
    ${this.text} - ${this._animating ? html` ${this.text} ` : ""} + ${this.animating ? html` ${this.text} ` : ""}
    `; } private _onIteration() { if (!this.active) { - this._animating = false; + this.animating = false; } } @@ -96,6 +96,8 @@ class HuiMarquee extends LitElement { :host([animating]) .marquee-inner span { padding-right: 16px; + padding-inline-end: 16px; + padding-inline-start: initial; } @keyframes marquee { diff --git a/src/panels/lovelace/components/hui-timestamp-display.ts b/src/panels/lovelace/components/hui-timestamp-display.ts index c3871ca099..c0b99d2c45 100644 --- a/src/panels/lovelace/components/hui-timestamp-display.ts +++ b/src/panels/lovelace/components/hui-timestamp-display.ts @@ -27,7 +27,7 @@ const INTERVAL_FORMAT = ["relative", "total"]; class HuiTimestampDisplay extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public ts?: Date; + @property({ attribute: false }) public ts?: Date; @property() public format?: TimestampRenderingFormat; diff --git a/src/panels/lovelace/editor/add-entities-to-view.ts b/src/panels/lovelace/editor/add-entities-to-view.ts index f28270a143..03b9a44ac2 100644 --- a/src/panels/lovelace/editor/add-entities-to-view.ts +++ b/src/panels/lovelace/editor/add-entities-to-view.ts @@ -1,4 +1,5 @@ import { LovelacePanelConfig } from "../../../data/lovelace"; +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { LovelaceConfig, fetchConfig, @@ -13,8 +14,8 @@ import { showSelectViewDialog } from "./select-view/show-select-view-dialog"; export const addEntitiesToLovelaceView = async ( element: HTMLElement, hass: HomeAssistant, - entities: string[], - cardTitle?: string + cardConfig: LovelaceCardConfig[], + entities?: string[] ) => { hass.loadFragmentTranslation("lovelace"); const dashboards = await fetchDashboards(hass); @@ -30,9 +31,9 @@ export const addEntitiesToLovelaceView = async ( if (mainLovelaceMode !== "storage" && !storageDashs.length) { // no storage dashboards, just show the YAML config showSuggestCardDialog(element, { + cardConfig, entities, yaml: true, - cardTitle, }); return; } @@ -69,9 +70,9 @@ export const addEntitiesToLovelaceView = async ( if (dashboards.length > storageDashs.length) { // all storage dashboards are generated, but we have YAML dashboards just show the YAML config showSuggestCardDialog(element, { + cardConfig, entities, yaml: true, - cardTitle, }); } else { // all storage dashboards are generated @@ -91,7 +92,7 @@ export const addEntitiesToLovelaceView = async ( if (!storageDashs.length && lovelaceConfig.views.length === 1) { showSuggestCardDialog(element, { - cardTitle, + cardConfig, lovelaceConfig: lovelaceConfig!, saveConfig: async (newConfig: LovelaceConfig): Promise => { try { @@ -114,7 +115,7 @@ export const addEntitiesToLovelaceView = async ( dashboards, viewSelectedCallback: (newUrlPath, selectedDashConfig, viewIndex) => { showSuggestCardDialog(element, { - cardTitle, + cardConfig, lovelaceConfig: selectedDashConfig, saveConfig: async (newConfig: LovelaceConfig): Promise => { try { diff --git a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts index 5512b1cfbc..a1c9582900 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts @@ -10,7 +10,7 @@ import { LovelaceCard } from "../../types"; export class HuiCardPreview extends ReactiveElement { @property({ attribute: false }) public hass?: HomeAssistant; - @property() public config?: LovelaceCardConfig; + @property({ attribute: false }) public config?: LovelaceCardConfig; private _element?: LovelaceCard; diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts index e23cafa985..1752a24174 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts @@ -37,7 +37,7 @@ export class HuiCreateDialogCard extends LitElement implements HassDialog { - @property({ attribute: false }) protected hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @state() private _params?: CreateCardDialogParams; diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-delete-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-delete-card.ts index ca730833d6..1483268554 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-delete-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-delete-card.ts @@ -10,7 +10,7 @@ import type { DeleteCardDialogParams } from "./show-delete-card-dialog"; @customElement("hui-dialog-delete-card") export class HuiDialogDeleteCard extends LitElement { - @property() protected hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @state() private _params?: DeleteCardDialogParams; diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index 2ab16a7853..98427c14f4 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -152,8 +152,10 @@ export class HuiDialogEditCard if (this._cardConfig && this._cardConfig.type) { let cardName: string | undefined; if (isCustomType(this._cardConfig.type)) { - cardName = getCustomCardEntry(stripCustomPrefix(this._cardConfig.type)) - ?.name; + // prettier-ignore + cardName = getCustomCardEntry( + stripCustomPrefix(this._cardConfig.type) + )?.name; // Trim names that end in " Card" so as not to redundantly duplicate it if (cardName?.toLowerCase().endsWith(" card")) { cardName = cardName.substring(0, cardName.length - 5); @@ -495,6 +497,8 @@ export class HuiDialogEditCard } .gui-mode-button { margin-right: auto; + margin-inline-end: auto; + margin-inline-start: initial; } .header { display: flex; diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts index e9fdddc5c6..8cf248ac01 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts @@ -8,7 +8,6 @@ import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; -import { computeCards } from "../../common/generate-lovelace-config"; import { addCards } from "../config-util"; import "./hui-card-preview"; import { showCreateCardDialog } from "./show-create-card-dialog"; @@ -28,11 +27,7 @@ export class HuiDialogSuggestCard extends LitElement { public showDialog(params: SuggestCardDialogParams): void { this._params = params; - this._cardConfig = - params.cardConfig || - computeCards(this.hass.states, params.entities, { - title: params.cardTitle, - }); + this._cardConfig = params.cardConfig; if (!Object.isFrozen(this._cardConfig)) { this._cardConfig = deepFreeze(this._cardConfig); } diff --git a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts index 6c2525dcb1..997a4ec22d 100644 --- a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts +++ b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts @@ -18,7 +18,7 @@ import type { HomeAssistant } from "../../../../types"; export class HuiEntityPickerTable extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow?: boolean; + @property({ type: Boolean }) public narrow = false; @property({ type: Boolean, attribute: "no-label-float" }) public noLabelFloat? = false; diff --git a/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts b/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts index 781753e65e..de8021649d 100644 --- a/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts +++ b/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts @@ -3,12 +3,11 @@ import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import { LovelaceConfig } from "../../../../data/lovelace/config/types"; export interface SuggestCardDialogParams { - cardTitle?: string; lovelaceConfig?: LovelaceConfig; yaml?: boolean; saveConfig?: (config: LovelaceConfig) => void; path?: [number]; - entities: string[]; // We can pass entity id's that will be added to the config when a card is picked + entities?: string[]; // Entities used to generate the card config. We pass this to create dialog when user chooses "Pick own" cardConfig?: LovelaceCardConfig[]; // We can pass a suggested config } diff --git a/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts b/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts index fb2433d4a6..f4f0e0e718 100644 --- a/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts +++ b/src/panels/lovelace/editor/conditions/ha-card-condition-editor.ts @@ -10,6 +10,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { handleStructError } from "../../../../common/structs/handle-errors"; import "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; +import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; import "../../../../components/ha-list-item"; @@ -324,6 +325,8 @@ export class HaCardConditionEditor extends LitElement { color: var(--secondary-text-color); opacity: 0.9; margin-right: 8px; + margin-inline-end: 8px; + margin-inline-start: initial; } } h3 { diff --git a/src/panels/lovelace/editor/conditions/ha-card-conditions-editor.ts b/src/panels/lovelace/editor/conditions/ha-card-conditions-editor.ts index b0f5eaf851..125d715bb9 100644 --- a/src/panels/lovelace/editor/conditions/ha-card-conditions-editor.ts +++ b/src/panels/lovelace/editor/conditions/ha-card-conditions-editor.ts @@ -46,7 +46,7 @@ export class HaCardConditionsEditor extends LitElement { | LegacyCondition )[]; - @property({ attribute: true, type: Boolean }) public nested?: boolean; + @property({ type: Boolean }) public nested = false; private _focusLastConditionOnChange = false; diff --git a/src/panels/lovelace/editor/conditions/types/ha-card-condition-and.ts b/src/panels/lovelace/editor/conditions/types/ha-card-condition-and.ts index b51e6b3bf1..a228145160 100644 --- a/src/panels/lovelace/editor/conditions/types/ha-card-condition-and.ts +++ b/src/panels/lovelace/editor/conditions/types/ha-card-condition-and.ts @@ -9,6 +9,7 @@ import { Condition, StateCondition, } from "../../../common/validate-condition"; +import "../ha-card-conditions-editor"; const andConditionStruct = object({ condition: literal("and"), diff --git a/src/panels/lovelace/editor/conditions/types/ha-card-condition-or.ts b/src/panels/lovelace/editor/conditions/types/ha-card-condition-or.ts index 3f7efd094a..d9fdfc87a5 100644 --- a/src/panels/lovelace/editor/conditions/types/ha-card-condition-or.ts +++ b/src/panels/lovelace/editor/conditions/types/ha-card-condition-or.ts @@ -9,6 +9,7 @@ import { OrCondition, StateCondition, } from "../../../common/validate-condition"; +import "../ha-card-conditions-editor"; const orConditionStruct = object({ condition: literal("or"), diff --git a/src/panels/lovelace/editor/config-elements/config-elements-style.ts b/src/panels/lovelace/editor/config-elements/config-elements-style.ts index 2f068a1f91..a3bf655e52 100644 --- a/src/panels/lovelace/editor/config-elements/config-elements-style.ts +++ b/src/panels/lovelace/editor/config-elements/config-elements-style.ts @@ -15,10 +15,14 @@ export const configElementStyle = css` .side-by-side > * { flex: 1; padding-right: 8px; + padding-inline-end: 8px; + padding-inline-start: initial; } .side-by-side > *:last-child { flex: 1; padding-right: 0; + padding-inline-end: 0; + padding-inline-start: initial; } .suffix { margin: 0 8px; diff --git a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts index c28567945a..f46f279b45 100644 --- a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts @@ -1,15 +1,30 @@ import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import { assert, assign, boolean, object, optional, string } from "superstruct"; +import { + assert, + array, + assign, + boolean, + object, + optional, + string, +} from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-form/ha-form"; -import { DEFAULT_ASPECT_RATIO } from "../../cards/hui-area-card"; +import { + DEFAULT_ASPECT_RATIO, + DEVICE_CLASSES, +} from "../../cards/hui-area-card"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { AreaCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import { computeDomain } from "../../../../common/entity/compute_domain"; +import { caseInsensitiveStringCompare } from "../../../../common/string/compare"; +import { SelectOption } from "../../../../data/selector"; +import { getSensorNumericDeviceClasses } from "../../../../data/sensor"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -20,8 +35,11 @@ const cardConfigStruct = assign( show_camera: optional(boolean()), camera_view: optional(string()), aspect_ratio: optional(string()), + alert_classes: optional(array(string())), + sensor_classes: optional(array(string())), }) ); + @customElement("hui-area-card-editor") export class HuiAreaCardEditor extends LitElement @@ -31,8 +49,14 @@ export class HuiAreaCardEditor @state() private _config?: AreaCardConfig; + @state() private _numericDeviceClasses?: string[]; + private _schema = memoizeOne( - (showCamera: boolean) => + ( + showCamera: boolean, + binaryClasses: SelectOption[], + sensorClasses: SelectOption[] + ) => [ { name: "area", selector: { area: {} } }, { name: "show_camera", required: false, selector: { boolean: {} } }, @@ -61,23 +85,142 @@ export class HuiAreaCardEditor }, ], }, + { + name: "alert_classes", + selector: { + select: { + reorder: true, + multiple: true, + custom_value: true, + options: binaryClasses, + }, + }, + }, + { + name: "sensor_classes", + selector: { + select: { + reorder: true, + multiple: true, + custom_value: true, + options: sensorClasses, + }, + }, + }, ] as const ); + private _binaryClassesForArea = memoizeOne((area: string): string[] => + this._classesForArea(area, "binary_sensor") + ); + + private _sensorClassesForArea = memoizeOne( + (area: string, numericDeviceClasses?: string[]): string[] => + this._classesForArea(area, "sensor", numericDeviceClasses) + ); + + private _classesForArea( + area: string, + domain: "sensor" | "binary_sensor", + numericDeviceClasses?: string[] | undefined + ): string[] { + const entities = Object.values(this.hass!.entities).filter( + (e) => + computeDomain(e.entity_id) === domain && + !e.entity_category && + !e.hidden && + (e.area_id === area || + (e.device_id && this.hass!.devices[e.device_id].area_id === area)) + ); + + const classes = entities + .map((e) => this.hass!.states[e.entity_id]?.attributes.device_class || "") + .filter( + (c) => + c && + (domain !== "sensor" || + !numericDeviceClasses || + numericDeviceClasses.includes(c)) + ); + + return [...new Set(classes)]; + } + + private _buildBinaryOptions = memoizeOne( + (possibleClasses: string[], currentClasses: string[]): SelectOption[] => + this._buildOptions("binary_sensor", possibleClasses, currentClasses) + ); + + private _buildSensorOptions = memoizeOne( + (possibleClasses: string[], currentClasses: string[]): SelectOption[] => + this._buildOptions("sensor", possibleClasses, currentClasses) + ); + + private _buildOptions( + domain: "sensor" | "binary_sensor", + possibleClasses: string[], + currentClasses: string[] + ): SelectOption[] { + const options = [...new Set([...possibleClasses, ...currentClasses])].map( + (deviceClass) => ({ + value: deviceClass, + label: + this.hass!.localize( + `component.${domain}.entity_component.${deviceClass}.name` + ) || deviceClass, + }) + ); + options.sort((a, b) => + caseInsensitiveStringCompare(a.label, b.label, this.hass!.locale.language) + ); + + return options; + } + public setConfig(config: AreaCardConfig): void { assert(config, cardConfigStruct); this._config = config; } + protected async updated() { + if (this.hass && !this._numericDeviceClasses) { + const { numeric_device_classes: sensorNumericDeviceClasses } = + await getSensorNumericDeviceClasses(this.hass); + this._numericDeviceClasses = sensorNumericDeviceClasses; + } + } + protected render() { if (!this.hass || !this._config) { return nothing; } - const schema = this._schema(this._config.show_camera || false); + const possibleBinaryClasses = this._binaryClassesForArea( + this._config.area || "" + ); + const possibleSensorClasses = this._sensorClassesForArea( + this._config.area || "", + this._numericDeviceClasses + ); + const binarySelectOptions = this._buildBinaryOptions( + possibleBinaryClasses, + this._config.alert_classes || DEVICE_CLASSES.binary_sensor + ); + const sensorSelectOptions = this._buildSensorOptions( + possibleSensorClasses, + this._config.sensor_classes || DEVICE_CLASSES.sensor + ); + + const schema = this._schema( + this._config.show_camera || false, + binarySelectOptions, + sensorSelectOptions + ); const data = { camera_view: "auto", + alert_classes: DEVICE_CLASSES.binary_sensor, + sensor_classes: DEVICE_CLASSES.sensor, ...this._config, }; diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index 8693be890d..d8fe23d0a7 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -3,13 +3,13 @@ import { HassEntity } from "home-assistant-js-websocket"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import type { SortableEvent } from "sortablejs"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../common/dom/stop_propagation"; import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-button"; import "../../../../components/ha-icon-button"; import "../../../../components/ha-list-item"; +import "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import { CUSTOM_TYPE_PREFIX, @@ -18,8 +18,6 @@ import { isCustomType, stripCustomPrefix, } from "../../../../data/lovelace_custom_cards"; -import { sortableStyles } from "../../../../resources/ha-sortable-style"; -import type { SortableInstance } from "../../../../resources/sortable"; import { HomeAssistant } from "../../../../types"; import { supportsAlarmModesCardFeature } from "../../card-features/hui-alarm-modes-card-feature"; import { supportsClimateFanModesCardFeature } from "../../card-features/hui-climate-fan-modes-card-feature"; @@ -39,11 +37,11 @@ import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature"; import { supportsTargetHumidityCardFeature } from "../../card-features/hui-target-humidity-card-feature"; import { supportsTargetTemperatureCardFeature } from "../../card-features/hui-target-temperature-card-feature"; +import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update-actions-card-feature"; import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuum-commands-card-feature"; import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature"; import { LovelaceCardFeatureConfig } from "../../card-features/types"; import { getCardFeatureElementClass } from "../../create-element/create-card-feature-element"; -import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update-actions-card-feature"; export type FeatureType = LovelaceCardFeatureConfig["type"]; type SupportsFeature = (stateObj: HassEntity) => boolean; @@ -149,13 +147,6 @@ export class HuiCardFeaturesEditor extends LitElement { private _featuresKeys = new WeakMap(); - private _sortable?: SortableInstance; - - public disconnectedCallback() { - super.disconnectedCallback(); - this._destroySortable(); - } - private _supportsFeatureType(type: string): boolean { if (!this.stateObj) return false; @@ -205,10 +196,6 @@ export class HuiCardFeaturesEditor extends LitElement { return this._featuresKeys.get(feature)!; } - protected firstUpdated() { - this._createSortable(); - } - private _getSupportedFeaturesType() { const featuresTypes = UI_FEATURE_TYPES.filter( (type) => !this.featuresTypes || this.featuresTypes.includes(type) @@ -249,61 +236,66 @@ export class HuiCardFeaturesEditor extends LitElement { ` : nothing} -
    - ${repeat( - this.features, - (featureConf) => this._getKey(featureConf), - (featureConf, index) => { - const type = featureConf.type; - const supported = this._supportsFeatureType(type); - const editable = this._isFeatureTypeEditable(type); - return html` -
    -
    - -
    -
    -
    - ${this._getFeatureTypeLabel(type)} - ${this.stateObj && !supported - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.editor.features.not_compatible" - )} - - ` - : nothing} + +
    + ${repeat( + this.features, + (featureConf) => this._getKey(featureConf), + (featureConf, index) => { + const type = featureConf.type; + const supported = this._supportsFeatureType(type); + const editable = this._isFeatureTypeEditable(type); + return html` +
    +
    +
    +
    +
    + ${this._getFeatureTypeLabel(type)} + ${this.stateObj && !supported + ? html` + + ${this.hass!.localize( + "ui.panel.lovelace.editor.features.not_compatible" + )} + + ` + : nothing} +
    +
    + ${editable + ? html` + + ` + : nothing} +
    - ${editable - ? html` - - ` - : nothing} - -
    - `; - } - )} -
    + `; + } + )} +
    + ${supportedFeaturesType.length > 0 ? html` { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._rowMoved(evt); - }, - } - ); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private async _addFeature(ev: CustomEvent): Promise { const index = ev.detail.index as number; @@ -395,14 +357,13 @@ export class HuiCardFeaturesEditor extends LitElement { fireEvent(this, "features-changed", { features: newConfigFeature }); } - private _rowMoved(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) { - return; - } + private _featureMoved(ev: CustomEvent): void { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; const newFeatures = this.features!.concat(); - newFeatures.splice(ev.newIndex!, 0, newFeatures.splice(ev.oldIndex!, 1)[0]); + newFeatures.splice(newIndex, 0, newFeatures.splice(oldIndex, 1)[0]); fireEvent(this, "features-changed", { features: newFeatures }); } @@ -428,79 +389,76 @@ export class HuiCardFeaturesEditor extends LitElement { } static get styles(): CSSResultGroup { - return [ - sortableStyles, - css` - :host { - display: flex !important; - flex-direction: column; - } - .content { - padding: 12px; - } - ha-expansion-panel { - display: block; - --expansion-panel-content-padding: 0; - border-radius: 6px; - } - h3 { - margin: 0; - font-size: inherit; - font-weight: inherit; - } - ha-svg-icon, - ha-icon { - color: var(--secondary-text-color); - } - ha-button-menu { - margin-top: 8px; - } - .feature { - display: flex; - align-items: center; - } - .feature .handle { - padding-right: 8px; - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - padding-inline-end: 8px; - padding-inline-start: initial; - direction: var(--direction); - } - .feature .handle > * { - pointer-events: none; - } + return css` + :host { + display: flex !important; + flex-direction: column; + } + .content { + padding: 12px; + } + ha-expansion-panel { + display: block; + --expansion-panel-content-padding: 0; + border-radius: 6px; + } + h3 { + margin: 0; + font-size: inherit; + font-weight: inherit; + } + ha-svg-icon, + ha-icon { + color: var(--secondary-text-color); + } + ha-button-menu { + margin-top: 8px; + } + .feature { + display: flex; + align-items: center; + } + .feature .handle { + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; + padding-right: 8px; + padding-inline-end: 8px; + padding-inline-start: initial; + direction: var(--direction); + } + .feature .handle > * { + pointer-events: none; + } - .feature-content { - height: 60px; - font-size: 16px; - display: flex; - align-items: center; - justify-content: space-between; - flex-grow: 1; - } + .feature-content { + height: 60px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: space-between; + flex-grow: 1; + } - .feature-content div { - display: flex; - flex-direction: column; - } + .feature-content div { + display: flex; + flex-direction: column; + } - .remove-icon, - .edit-icon { - --mdc-icon-button-size: 36px; - color: var(--secondary-text-color); - } + .remove-icon, + .edit-icon { + --mdc-icon-button-size: 36px; + color: var(--secondary-text-color); + } - .secondary { - font-size: 12px; - color: var(--secondary-text-color); - } + .secondary { + font-size: 12px; + color: var(--secondary-text-color); + } - li[divider] { - border-bottom-color: var(--divider-color); - } - `, - ]; + li[divider] { + border-bottom-color: var(--divider-color); + } + `; } } diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts index 7f01de6102..228a43740d 100644 --- a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts @@ -58,8 +58,8 @@ export class HuiClimateHvacModesCardFeatureEditor select: { multiple: true, mode: "list", - options: HVAC_MODES.filter( - (mode) => stateObj?.attributes.hvac_modes?.includes(mode) + options: HVAC_MODES.filter((mode) => + stateObj?.attributes.hvac_modes?.includes(mode) ).map((mode) => ({ value: mode, label: stateObj ? formatEntityState(stateObj, mode) : mode, diff --git a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts index f875c3ed7e..633c251519 100644 --- a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts @@ -249,6 +249,8 @@ export class HuiConditionalCardEditor } .gui-mode-button { margin-right: auto; + margin-inline-end: auto; + margin-inline-start: initial; } `, ]; diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index abcb662114..8e6eb4bbec 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -19,7 +19,6 @@ import { import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { customType } from "../../../../common/structs/is-custom-type"; import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import "../../../../components/entity/state-badge"; import "../../../../components/ha-card"; import "../../../../components/ha-formfield"; import "../../../../components/ha-icon"; diff --git a/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts index b683e8e13e..6105a3fa16 100644 --- a/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts @@ -33,6 +33,10 @@ const SCHEMA = [ type: "grid", name: "", schema: [ + { + name: "title", + selector: { text: {} }, + }, { name: "columns", default: DEFAULT_COLUMNS, diff --git a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts index adcd6a9314..862459b277 100644 --- a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts @@ -1,5 +1,6 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { array, assert, @@ -32,29 +33,12 @@ const cardConfigStruct = assign( refresh_interval: optional(number()), // deprecated show_names: optional(boolean()), logarithmic_scale: optional(boolean()), + min_y_axis: optional(number()), + max_y_axis: optional(number()), + fit_y_data: optional(boolean()), }) ); -const SCHEMA = [ - { name: "title", selector: { text: {} } }, - { - name: "", - type: "grid", - schema: [ - { - name: "hours_to_show", - default: DEFAULT_HOURS_TO_SHOW, - selector: { number: { min: 1, mode: "box" } }, - }, - ], - }, - { - name: "logarithmic_scale", - required: false, - selector: { boolean: {} }, - }, -] as const; - @customElement("hui-history-graph-card-editor") export class HuiHistoryGraphCardEditor extends LitElement @@ -72,16 +56,69 @@ export class HuiHistoryGraphCardEditor this._configEntities = processEditorEntities(config.entities); } + private _schema = memoizeOne( + (showFitOption: boolean) => + [ + { name: "title", selector: { text: {} } }, + { + name: "", + type: "grid", + schema: [ + { + name: "hours_to_show", + default: DEFAULT_HOURS_TO_SHOW, + selector: { number: { min: 1, mode: "box" } }, + }, + ], + }, + { + name: "logarithmic_scale", + required: false, + selector: { boolean: {} }, + }, + { + name: "", + type: "grid", + schema: [ + { + name: "min_y_axis", + required: false, + selector: { number: { mode: "box", step: "any" } }, + }, + { + name: "max_y_axis", + required: false, + selector: { number: { mode: "box", step: "any" } }, + }, + ], + }, + ...(showFitOption + ? [ + { + name: "fit_y_data", + required: false, + selector: { boolean: {} }, + }, + ] + : []), + ] as const + ); + protected render() { if (!this.hass || !this._config) { return nothing; } + const schema = this._schema( + this._config!.min_y_axis !== undefined || + this._config!.max_y_axis !== undefined + ); + return html` @@ -106,9 +143,14 @@ export class HuiHistoryGraphCardEditor fireEvent(this, "config-changed", { config }); } - private _computeLabelCallback = (schema: SchemaUnion) => { + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { switch (schema.name) { case "logarithmic_scale": + case "min_y_axis": + case "max_y_axis": + case "fit_y_data": return this.hass!.localize( `ui.panel.lovelace.editor.card.history-graph.${schema.name}` ); diff --git a/src/panels/lovelace/editor/config-elements/hui-stack-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-stack-card-editor.ts index b801108ff5..0655f6dddb 100644 --- a/src/panels/lovelace/editor/config-elements/hui-stack-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-stack-card-editor.ts @@ -1,6 +1,4 @@ import { - mdiArrowLeft, - mdiArrowRight, mdiCodeBraces, mdiContentCopy, mdiContentCut, @@ -25,6 +23,8 @@ import { import { storage } from "../../../../common/decorators/storage"; import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-icon-button"; +import "../../../../components/ha-icon-button-arrow-prev"; +import "../../../../components/ha-icon-button-arrow-next"; import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import type { LovelaceConfig } from "../../../../data/lovelace/config/types"; import { HomeAssistant } from "../../../../types"; @@ -131,25 +131,23 @@ export class HuiStackCardEditor .path=${isGuiMode ? mdiCodeBraces : mdiListBoxOutline} > - + > - + > - this._metaDatas?.some((metaData) => - statisticsMetaHasType(metaData, stat_type) - ) + : stat_types.filter((stat_type) => + this._metaDatas?.some((metaData) => + statisticsMetaHasType(metaData, stat_type) + ) ); const data = { chart_type: "line", diff --git a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts index a1f95b8a52..6e1a5f43ab 100644 --- a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts @@ -38,8 +38,8 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor select: { multiple: true, mode: "list", - options: OPERATION_MODES.filter( - (mode) => stateObj?.attributes.operation_list?.includes(mode) + options: OPERATION_MODES.filter((mode) => + stateObj?.attributes.operation_list?.includes(mode) ).map((mode) => ({ value: mode, label: stateObj ? formatEntityState(stateObj, mode) : mode, diff --git a/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts b/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts index f3d25a2130..8c13d58b65 100644 --- a/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts +++ b/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts @@ -16,7 +16,7 @@ export class HuiCreateDialogHeaderFooter extends LitElement implements HassDialog { - @property({ attribute: false }) protected hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @state() private _params?: CreateHeaderFooterDialogParams; diff --git a/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts b/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts index 1036e0437e..d73d3d28d1 100644 --- a/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts +++ b/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts @@ -120,6 +120,8 @@ export class HuiHeaderFooterEditor extends LitElement { .header-footer-icon { padding-right: 8px; + padding-inline-end: 8px; + padding-inline-start: initial; } `; } diff --git a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts index 231305a1a3..d492ee8918 100644 --- a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts +++ b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts @@ -1,15 +1,13 @@ import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import type { SortableEvent } from "sortablejs"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-picker"; import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker"; import "../../../components/ha-icon-button"; +import "../../../components/ha-sortable"; import "../../../components/ha-svg-icon"; -import { sortableStyles } from "../../../resources/ha-sortable-style"; -import type { SortableInstance } from "../../../resources/sortable"; import { HomeAssistant } from "../../../types"; import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types"; @@ -23,21 +21,14 @@ declare global { @customElement("hui-entities-card-row-editor") export class HuiEntitiesCardRowEditor extends LitElement { - @property({ attribute: false }) protected hass?: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; - @property({ attribute: false }) protected entities?: LovelaceRowConfig[]; + @property({ attribute: false }) public entities?: LovelaceRowConfig[]; - @property() protected label?: string; + @property() public label?: string; private _entityKeys = new WeakMap(); - private _sortable?: SortableInstance; - - public disconnectedCallback() { - super.disconnectedCallback(); - this._destroySortable(); - } - private _getKey(action: LovelaceRowConfig) { if (!this._entityKeys.has(action)) { this._entityKeys.set(action, Math.random().toString()); @@ -60,64 +51,66 @@ export class HuiEntitiesCardRowEditor extends LitElement { "ui.panel.lovelace.editor.card.config.required" )})`} -
    - ${repeat( - this.entities, - (entityConf) => this._getKey(entityConf), - (entityConf, index) => html` -
    -
    - -
    - ${entityConf.type - ? html` -
    -
    - - ${this.hass!.localize( - `ui.panel.lovelace.editor.card.entities.entity_row.${entityConf.type}` - )} - - ${this.hass!.localize( - "ui.panel.lovelace.editor.card.entities.edit_special_row" - )} + +
    + ${repeat( + this.entities, + (entityConf) => this._getKey(entityConf), + (entityConf, index) => html` +
    +
    + +
    + ${entityConf.type + ? html` +
    +
    + + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.entities.entity_row.${entityConf.type}` + )} + + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.entities.edit_special_row" + )} +
    -
    - ` - : html` - - `} - - -
    - ` - )} -
    + ` + : html` + + `} + + +
    + ` + )} +
    + { - (evt.item as any).placeholder = - document.createComment("sort-placeholder"); - evt.item.after((evt.item as any).placeholder); - }, - onEnd: (evt: SortableEvent) => { - // put back in original location - if ((evt.item as any).placeholder) { - (evt.item as any).placeholder.replaceWith(evt.item); - delete (evt.item as any).placeholder; - } - this._rowMoved(evt); - }, - } - ); - } - - private _destroySortable() { - this._sortable?.destroy(); - this._sortable = undefined; - } - private async _addEntity(ev: CustomEvent): Promise { const value = ev.detail.value; if (value === "") { @@ -172,14 +131,13 @@ export class HuiEntitiesCardRowEditor extends LitElement { fireEvent(this, "entities-changed", { entities: newConfigEntities }); } - private _rowMoved(ev: SortableEvent): void { - if (ev.oldIndex === ev.newIndex) { - return; - } + private _rowMoved(ev: CustomEvent): void { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; const newEntities = this.entities!.concat(); - newEntities.splice(ev.newIndex!, 0, newEntities.splice(ev.oldIndex!, 1)[0]); + newEntities.splice(newIndex, 0, newEntities.splice(oldIndex, 1)[0]); fireEvent(this, "entities-changed", { entities: newEntities }); } @@ -222,67 +180,64 @@ export class HuiEntitiesCardRowEditor extends LitElement { } static get styles(): CSSResultGroup { - return [ - sortableStyles, - css` - ha-entity-picker { - margin-top: 8px; - } - .add-entity { - display: block; - margin-left: 31px; - margin-right: 71px; - margin-inline-start: 31px; - margin-inline-end: 71px; - direction: var(--direction); - } - .entity { - display: flex; - align-items: center; - } + return css` + ha-entity-picker { + margin-top: 8px; + } + .add-entity { + display: block; + margin-left: 31px; + margin-right: 71px; + margin-inline-start: 31px; + margin-inline-end: 71px; + direction: var(--direction); + } + .entity { + display: flex; + align-items: center; + } - .entity .handle { - padding-right: 8px; - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - padding-inline-end: 8px; - padding-inline-start: initial; - direction: var(--direction); - } - .entity .handle > * { - pointer-events: none; - } + .entity .handle { + padding-right: 8px; + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; + padding-inline-end: 8px; + padding-inline-start: initial; + direction: var(--direction); + } + .entity .handle > * { + pointer-events: none; + } - .entity ha-entity-picker { - flex-grow: 1; - } + .entity ha-entity-picker { + flex-grow: 1; + } - .special-row { - height: 60px; - font-size: 16px; - display: flex; - align-items: center; - justify-content: space-between; - flex-grow: 1; - } + .special-row { + height: 60px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: space-between; + flex-grow: 1; + } - .special-row div { - display: flex; - flex-direction: column; - } + .special-row div { + display: flex; + flex-direction: column; + } - .remove-icon, - .edit-icon { - --mdc-icon-button-size: 36px; - color: var(--secondary-text-color); - } + .remove-icon, + .edit-icon { + --mdc-icon-button-size: 36px; + color: var(--secondary-text-color); + } - .secondary { - font-size: 12px; - color: var(--secondary-text-color); - } - `, - ]; + .secondary { + font-size: 12px; + color: var(--secondary-text-color); + } + `; } } diff --git a/src/panels/lovelace/editor/hui-sub-element-editor.ts b/src/panels/lovelace/editor/hui-sub-element-editor.ts index b46a03a80a..62848734dd 100644 --- a/src/panels/lovelace/editor/hui-sub-element-editor.ts +++ b/src/panels/lovelace/editor/hui-sub-element-editor.ts @@ -1,9 +1,10 @@ import "@material/mwc-button"; -import { mdiArrowLeft, mdiCodeBraces, mdiListBoxOutline } from "@mdi/js"; +import { mdiCodeBraces, mdiListBoxOutline } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-icon-button"; +import "../../../components/ha-icon-button-prev"; import type { HomeAssistant } from "../../../types"; import type { LovelaceRowConfig } from "../entity-rows/types"; import type { LovelaceHeaderFooterConfig } from "../header-footer/types"; @@ -39,11 +40,10 @@ export class HuiSubElementEditor extends LitElement { return html`
    - + > ${this.hass.localize( `ui.panel.lovelace.editor.sub-element-editor.types.${this.config?.type}` diff --git a/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts b/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts index e3805d93f5..b735db367c 100644 --- a/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts +++ b/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts @@ -18,7 +18,7 @@ declare global { export class HuiLovelaceEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public config?: LovelaceConfig; + @property({ attribute: false }) public config?: LovelaceConfig; get _title(): string { if (!this.config) { diff --git a/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts b/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts index 5f3e073f3a..2524ef0ab9 100644 --- a/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts +++ b/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts @@ -1,3 +1,4 @@ +import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-radio-list-item"; @@ -5,10 +6,10 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../common/dom/stop_propagation"; +import "../../../../components/ha-alert"; import { createCloseHeading } from "../../../../components/ha-dialog"; import "../../../../components/ha-icon"; import "../../../../components/ha-select"; -import "../../../../components/ha-alert"; import { fetchConfig, LovelaceConfig, diff --git a/src/panels/lovelace/editor/structs/action-struct.ts b/src/panels/lovelace/editor/structs/action-struct.ts index 591b9c0e8f..d0229a9908 100644 --- a/src/panels/lovelace/editor/structs/action-struct.ts +++ b/src/panels/lovelace/editor/structs/action-struct.ts @@ -58,10 +58,6 @@ const actionConfigStructAssist = type({ start_listening: optional(boolean()), }); -const actionConfigStructCustom = type({ - action: literal("fire-dom-event"), -}); - export const actionConfigStructType = object({ action: enums([ "none", @@ -81,9 +77,6 @@ export const actionConfigStruct = dynamic((value) => { case "call-service": { return actionConfigStructService; } - case "fire-dom-event": { - return actionConfigStructCustom; - } case "navigate": { return actionConfigStructNavigate; } diff --git a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts index e633861493..0f56db97c8 100644 --- a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts +++ b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts @@ -29,7 +29,7 @@ export class HuiUnusedEntities extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow?: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _unusedEntities: string[] = []; @@ -182,6 +182,8 @@ export class HuiUnusedEntities extends LitElement { bottom: 0; padding-right: 16px; padding-left: calc(16px + env(safe-area-inset-left)); + padding-inline-end: 16px; + padding-inline-start: calc(16px + env(safe-area-inset-left)); } ha-fab { position: relative; diff --git a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts index d2899a7de6..a16f8237f4 100644 --- a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts @@ -524,6 +524,8 @@ export class HuiDialogEditView extends LitElement { } mwc-button.warning { margin-right: auto; + margin-inline-end: auto; + margin-inline-start: initial; } ha-circular-progress { display: none; diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index 29aa461de2..b693848a8d 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -26,7 +26,7 @@ declare global { export class HuiViewEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public isNew!: boolean; + @property({ type: Boolean }) public isNew = false; @state() private _config!: LovelaceViewConfig; diff --git a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts index c599b15bab..af0920eba8 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts @@ -38,7 +38,7 @@ export class HuiViewVisibilityEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public _config!: LovelaceViewConfig; + @state() private _config!: LovelaceViewConfig; @state() private _users!: User[]; diff --git a/src/panels/lovelace/elements/hui-state-icon-element.ts b/src/panels/lovelace/elements/hui-state-icon-element.ts index 0e90e2d4fd..1f21100479 100644 --- a/src/panels/lovelace/elements/hui-state-icon-element.ts +++ b/src/panels/lovelace/elements/hui-state-icon-element.ts @@ -59,6 +59,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement { return html` - - - `; - } + const showToggle = + stateObj.state === "open" || + stateObj.state === "closed" || + isUnavailableState(stateObj.state); - static get styles(): CSSResultGroup { - return css` - ha-valve-controls { - margin-right: -0.57em; - } + return html` + + ${showToggle + ? html` + + ` + : html` +
    + ${this.hass.formatEntityState(stateObj)} +
    + `} +
    `; } } diff --git a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts index cfea1c4542..870feb1cc0 100644 --- a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts @@ -10,7 +10,6 @@ import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/entity/state-badge"; import { isUnavailableState } from "../../../data/entity"; import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; import { @@ -145,7 +144,8 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { html` `}
    @@ -227,6 +227,8 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { .info { margin-left: 16px; + margin-inline-start: 16px; + margin-inline-end: initial; flex: 1 0 60px; } @@ -273,6 +275,8 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { justify-content: center; text-align: right; margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } .secondary { diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 8b4cf33945..4319c94d5b 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -1,7 +1,7 @@ import "@material/mwc-button"; import deepFreeze from "deep-freeze"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { html, LitElement, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { constructUrlCurrentPath } from "../../common/url/construct-url"; import { @@ -52,16 +52,15 @@ let resourcesLoaded = false; @customElement("ha-panel-lovelace") export class LovelacePanel extends LitElement { - @property() public panel?: PanelInfo; + @property({ attribute: false }) public panel?: PanelInfo; @property({ attribute: false }) public hass?: HomeAssistant; - @property() public narrow?: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public route?: Route; + @property({ attribute: false }) public route?: Route; - @property() - private _panelState?: "loading" | "loaded" | "error" | "yaml-editor" = + @state() private _panelState: "loading" | "loaded" | "error" | "yaml-editor" = "loading"; @state() private _errorMsg?: string; @@ -114,7 +113,7 @@ export class LovelacePanel extends LitElement { } protected render(): TemplateResult | void { - const panelState = this._panelState!; + const panelState = this._panelState; if (panelState === "loaded") { return html` @@ -161,10 +160,15 @@ export class LovelacePanel extends LitElement { `; } - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); + protected willUpdate(changedProps: PropertyValues) { + super.willUpdate(changedProps); + if (!this.lovelace && this._panelState !== "error") { + this._fetchConfig(false); + } + } - this._fetchConfig(false); + protected firstUpdated(changedProps: PropertyValues): void { + super.firstUpdated(changedProps); if (!this._unsubUpdates) { this._subscribeUpdates(); } @@ -267,6 +271,10 @@ export class LovelacePanel extends LitElement { // If strategy defined, apply it here. if (isStrategyDashboard(rawConf)) { + if (!this.hass?.entities || !this.hass.devices || !this.hass.areas) { + // We need these to generate a dashboard, wait for them + return; + } conf = await generateLovelaceDashboardStrategy( rawConf.strategy, this.hass! @@ -282,6 +290,10 @@ export class LovelacePanel extends LitElement { this._errorMsg = err.message; return; } + if (!this.hass?.entities || !this.hass.devices || !this.hass.areas) { + // We need these to generate a dashboard, wait for them + return; + } conf = await generateLovelaceDashboardStrategy( DEFAULT_CONFIG.strategy, this.hass! @@ -459,3 +471,9 @@ export class LovelacePanel extends LitElement { } } } + +declare global { + interface HTMLElementTagNameMap { + "ha-panel-lovelace": LovelacePanel; + } +} diff --git a/src/panels/lovelace/header-footer/hui-graph-header-footer.ts b/src/panels/lovelace/header-footer/hui-graph-header-footer.ts index 27701acfe4..ac466673bc 100644 --- a/src/panels/lovelace/header-footer/hui-graph-header-footer.ts +++ b/src/panels/lovelace/header-footer/hui-graph-header-footer.ts @@ -1,10 +1,10 @@ import { HassEntity } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, + css, + html, nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -62,7 +62,7 @@ export class HuiGraphHeaderFooter @property() public type!: "header" | "footer"; - @property() protected _config?: GraphHeaderFooterConfig; + @state() protected _config?: GraphHeaderFooterConfig; @state() private _coordinates?: number[][]; diff --git a/src/panels/lovelace/header-footer/hui-picture-header-footer.ts b/src/panels/lovelace/header-footer/hui-picture-header-footer.ts index f2a9f9d0ae..f43c560921 100644 --- a/src/panels/lovelace/header-footer/hui-picture-header-footer.ts +++ b/src/panels/lovelace/header-footer/hui-picture-header-footer.ts @@ -6,7 +6,7 @@ import { html, nothing, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import "../../../components/ha-card"; @@ -36,7 +36,7 @@ export class HuiPictureHeaderFooter @property() public type!: "header" | "footer"; - @property() protected _config?: PictureHeaderFooterConfig; + @state() protected _config?: PictureHeaderFooterConfig; public getCardSize(): number { return 3; @@ -68,7 +68,7 @@ export class HuiPictureHeaderFooter return html` ${this._config!.alt_text} void; + @property({ attribute: false }) public closeEditor?: () => void; @state() private _saving?: boolean; diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 77ed7aedd6..8257f0e2cb 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -7,7 +7,6 @@ import { mdiDotsVertical, mdiFileMultiple, mdiFormatListBulletedTriangle, - mdiHelp, mdiHelpCircle, mdiMagnify, mdiPencil, @@ -75,6 +74,7 @@ import { } from "../../data/lovelace/config/types"; import { showSaveDialog } from "./editor/show-save-config-dialog"; import { isLegacyStrategyConfig } from "./strategies/legacy-strategy"; +import { LocalizeKeys } from "../../common/translations/localize"; @customElement("hui-root") class HUIRoot extends LitElement { @@ -110,6 +110,168 @@ class HUIRoot extends LitElement { ); } + private _renderActionItems(): TemplateResult { + const result: TemplateResult[] = []; + if (this._editMode) { + result.push( + html` + + + ` + ); + } + + const items: { + icon: string; + key: LocalizeKeys; + overflowAction?: any; + buttonAction?: any; + visible: boolean | undefined; + overflow: boolean; + overflow_can_promote?: boolean; + }[] = [ + { + icon: mdiFormatListBulletedTriangle, + key: "ui.panel.lovelace.unused_entities.title", + overflowAction: this._handleUnusedEntities, + visible: this._editMode && !__DEMO__, + overflow: true, + }, + { + icon: mdiCodeBraces, + key: "ui.panel.lovelace.editor.menu.raw_editor", + overflowAction: this._handleRawEditor, + visible: this._editMode, + overflow: true, + }, + { + icon: mdiViewDashboard, + key: "ui.panel.lovelace.editor.menu.manage_dashboards", + overflowAction: this._handleManageDashboards, + visible: this._editMode && !__DEMO__, + overflow: true, + }, + { + icon: mdiFileMultiple, + key: "ui.panel.lovelace.editor.menu.manage_resources", + overflowAction: this._handleManageResources, + visible: this._editMode && this.hass.userData?.showAdvanced, + overflow: true, + }, + { + icon: mdiMagnify, + key: "ui.panel.lovelace.menu.search", + buttonAction: this._showQuickBar, + overflowAction: this._handleShowQuickBar, + visible: !this._editMode, + overflow: this.narrow, + }, + { + icon: mdiCommentProcessingOutline, + key: "ui.panel.lovelace.menu.assist", + buttonAction: this._showVoiceCommandDialog, + overflowAction: this._handleShowVoiceCommandDialog, + visible: + !this._editMode && this._conversation(this.hass.config.components), + overflow: this.narrow, + }, + { + icon: mdiRefresh, + key: "ui.common.refresh", + overflowAction: this._handleRefresh, + visible: !this._editMode && this._yamlMode, + overflow: true, + }, + { + icon: mdiShape, + key: "ui.panel.lovelace.unused_entities.title", + overflowAction: this._handleUnusedEntities, + visible: !this._editMode && this._yamlMode, + overflow: true, + }, + { + icon: mdiRefresh, + key: "ui.panel.lovelace.menu.reload_resources", + overflowAction: this._handleReloadResources, + visible: + !this._editMode && + (this.hass.panels.lovelace?.config as LovelacePanelConfig)?.mode === + "yaml", + overflow: true, + }, + { + icon: mdiPencil, + key: "ui.panel.lovelace.menu.configure_ui", + overflowAction: this._handleEnableEditMode, + buttonAction: this._enableEditMode, + visible: + !this._editMode && + this.hass!.user?.is_admin && + !this.hass!.config.recovery_mode, + overflow: true, + overflow_can_promote: true, + }, + ]; + + const overflowItems = items.filter((i) => i.visible && i.overflow); + const overflowCanPromote = + overflowItems.length === 1 && overflowItems[0].overflow_can_promote; + const buttonItems = items.filter( + (i) => i.visible && (!i.overflow || overflowCanPromote) + ); + + buttonItems.forEach((i) => { + result.push( + html`` + ); + }); + if (overflowItems.length && !overflowCanPromote) { + const listItems: TemplateResult[] = []; + overflowItems.forEach((i) => { + listItems.push( + html` + ${this.hass!.localize(i.key)} + + ` + ); + }); + result.push( + html` + + ${listItems} + ` + ); + } + return html`${result}`; + } + protected render(): TemplateResult { const views = this.lovelace?.config.views ?? []; @@ -139,96 +301,7 @@ class HUIRoot extends LitElement { @click=${this._editLovelace} >
    -
    - - - - - - - ${__DEMO__ /* No unused entities available in the demo */ - ? "" - : html` - - - - ${this.hass!.localize( - "ui.panel.lovelace.unused_entities.title" - )} - - `} - - - ${this.hass!.localize( - "ui.panel.lovelace.editor.menu.raw_editor" - )} - - ${__DEMO__ /* No config available in the demo */ - ? "" - : html` - - ${this.hass!.localize( - "ui.panel.lovelace.editor.menu.manage_dashboards" - )} - - ${this.hass.userData?.showAdvanced - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.editor.menu.manage_resources" - )} - ` - : ""} `} - -
    +
    ${this._renderActionItems()}
    ` : html` ${curViewConfig?.subview @@ -289,174 +362,7 @@ class HUIRoot extends LitElement { : html`
    ${this.config.title}
    `} -
    - ${!this.narrow - ? html` - - ` - : ""} - ${!this.narrow && - this._conversation(this.hass.config.components) - ? html` - - ` - : ""} - ${this._showButtonMenu - ? html` - - - - ${this.narrow - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.menu.search" - )} - - - - ` - : ""} - ${this.narrow && - this._conversation(this.hass.config.components) - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.menu.assist" - )} - - - - ` - : ""} - ${this._yamlMode - ? html` - - ${this.hass!.localize("ui.common.refresh")} - - - - - ${this.hass!.localize( - "ui.panel.lovelace.unused_entities.title" - )} - - - - ` - : ""} - ${( - this.hass.panels.lovelace - ?.config as LovelacePanelConfig - )?.mode === "yaml" - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.menu.reload_resources" - )} - - - ` - : ""} - ${this.hass!.user?.is_admin && - !this.hass!.config.recovery_mode - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.menu.configure_ui" - )} - - - ` - : ""} - ${this._editMode - ? html` - - - ${this.hass!.localize( - "ui.panel.lovelace.menu.help" - )} - - - - ` - : ""} - - ` - : ""} -
    +
    ${this._renderActionItems()}
    `}
    ${this._editMode @@ -698,17 +604,6 @@ class HUIRoot extends LitElement { return this.shadowRoot!.getElementById("view") as HTMLDivElement; } - private get _showButtonMenu(): boolean { - return ( - (this.narrow && this._conversation(this.hass.config.components)) || - this._editMode || - (this.hass!.user?.is_admin && !this.hass!.config.recovery_mode) || - (this.hass.panels.lovelace?.config as LovelacePanelConfig)?.mode === - "yaml" || - this._yamlMode - ); - } - private _handleRefresh(ev: CustomEvent): void { if (!shouldHandleRequestSelectedEvent(ev)) { return; @@ -811,6 +706,10 @@ class HUIRoot extends LitElement { if (!shouldHandleRequestSelectedEvent(ev)) { return; } + this._enableEditMode(); + } + + private _enableEditMode(): void { if (this._yamlMode) { showAlertDialog(this, { text: this.hass!.localize("ui.panel.lovelace.editor.yaml_unsupported"), @@ -1027,7 +926,7 @@ class HUIRoot extends LitElement { } } .main-title { - margin: 0 0 0 24px; + margin: var(--margin-title); line-height: 20px; flex-grow: 1; } @@ -1040,6 +939,8 @@ class HUIRoot extends LitElement { width: 100%; height: 100%; margin-left: 4px; + margin-inline-start: 4px; + margin-inline-end: initial; } ha-tabs, paper-tabs { @@ -1092,6 +993,8 @@ class HUIRoot extends LitElement { box-sizing: border-box; padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); + padding-inline-start: env(safe-area-inset-left); + padding-inline-end: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); } hui-view { diff --git a/src/panels/lovelace/special-rows/hui-button-row.ts b/src/panels/lovelace/special-rows/hui-button-row.ts index f74d444604..61302bb8a8 100644 --- a/src/panels/lovelace/special-rows/hui-button-row.ts +++ b/src/panels/lovelace/special-rows/hui-button-row.ts @@ -53,7 +53,11 @@ export class HuiButtonRow extends LitElement implements LovelaceRow { this._config.name ?? (stateObj ? computeStateName(stateObj) : ""); return html` - +
    ${name}
    @@ -85,6 +89,8 @@ export class HuiButtonRow extends LitElement implements LovelaceRow { flex: 1; overflow: hidden; margin-left: 16px; + margin-inline-start: 16px; + margin-inline-end: initial; display: flex; justify-content: space-between; align-items: center; @@ -96,6 +102,8 @@ export class HuiButtonRow extends LitElement implements LovelaceRow { } mwc-button { margin-right: -0.57em; + margin-inline-end: -0.57em; + margin-inline-start: initial; } `; } diff --git a/src/panels/lovelace/special-rows/hui-cast-row.ts b/src/panels/lovelace/special-rows/hui-cast-row.ts index 35ebe527e8..1ca54f0857 100644 --- a/src/panels/lovelace/special-rows/hui-cast-row.ts +++ b/src/panels/lovelace/special-rows/hui-cast-row.ts @@ -138,6 +138,8 @@ class HuiCastRow extends LitElement implements LovelaceRow { .flex { flex: 1; margin-left: 16px; + margin-inline-start: 16px; + margin-inline-end: initial; display: flex; justify-content: space-between; align-items: center; @@ -153,6 +155,8 @@ class HuiCastRow extends LitElement implements LovelaceRow { } google-cast-launcher { margin-right: 0.57em; + margin-inline-end: -0.57em; + margin-inline-start: initial; cursor: pointer; display: inline-block; height: 24px; diff --git a/src/panels/lovelace/special-rows/hui-section-row.ts b/src/panels/lovelace/special-rows/hui-section-row.ts index 2af962d209..cc954ecfb2 100644 --- a/src/panels/lovelace/special-rows/hui-section-row.ts +++ b/src/panels/lovelace/special-rows/hui-section-row.ts @@ -39,6 +39,8 @@ class HuiSectionRow extends LitElement implements LovelaceRow { .label { color: var(--section-header-text-color, var(--primary-text-color)); margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; margin-bottom: 8px; margin-top: 16px; font-weight: 500; @@ -48,6 +50,8 @@ class HuiSectionRow extends LitElement implements LovelaceRow { background-color: var(--entities-divider-color, var(--divider-color)); margin-left: -16px; margin-right: -16px; + margin-inline-start: -16px; + margin-inline-end: -16px; margin-top: 8px; } `; diff --git a/src/panels/lovelace/special-rows/hui-text-row.ts b/src/panels/lovelace/special-rows/hui-text-row.ts index aa1eabff0c..6ac2764297 100644 --- a/src/panels/lovelace/special-rows/hui-text-row.ts +++ b/src/panels/lovelace/special-rows/hui-text-row.ts @@ -45,9 +45,11 @@ class HuiTextRow extends LitElement implements LovelaceRow { } .name { margin-left: 16px; + margin-inline-start: 16px; + margin-inline-end: initial; } .text { - text-align: right; + text-align: var(--float-end); } `; } diff --git a/src/panels/lovelace/special-rows/hui-weblink-row.ts b/src/panels/lovelace/special-rows/hui-weblink-row.ts index 04d00240f0..50098a47ab 100644 --- a/src/panels/lovelace/special-rows/hui-weblink-row.ts +++ b/src/panels/lovelace/special-rows/hui-weblink-row.ts @@ -58,6 +58,8 @@ class HuiWeblinkRow extends LitElement implements LovelaceRow { overflow: hidden; text-overflow: ellipsis; margin-left: 16px; + margin-inline-start: 16px; + margin-inline-end: initial; } `; } diff --git a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts index 86f010d9a4..0c7ae3c14f 100644 --- a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts @@ -33,3 +33,9 @@ export class OriginalStatesDashboardStrategy extends ReactiveElement { ); } } + +declare global { + interface HTMLElementTagNameMap { + "original-states-dashboard-strategy": OriginalStatesDashboardStrategy; + } +} diff --git a/src/panels/lovelace/strategies/original-states-view-strategy.ts b/src/panels/lovelace/strategies/original-states-view-strategy.ts index 9fcf20e974..386f953fcf 100644 --- a/src/panels/lovelace/strategies/original-states-view-strategy.ts +++ b/src/panels/lovelace/strategies/original-states-view-strategy.ts @@ -75,3 +75,9 @@ export class OriginalStatesViewStrategy extends ReactiveElement { return view; } } + +declare global { + interface HTMLElementTagNameMap { + "original-states-view-strategy": OriginalStatesViewStrategy; + } +} diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts index 58ebdbd216..16f3d60882 100644 --- a/src/panels/lovelace/views/hui-masonry-view.ts +++ b/src/panels/lovelace/views/hui-masonry-view.ts @@ -44,7 +44,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement { @property({ attribute: false }) public lovelace?: Lovelace; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ type: Number }) public index?: number; diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 96c5dab601..1d78c5e7a8 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -47,7 +47,7 @@ export class HUIView extends ReactiveElement { @property({ attribute: false }) public lovelace!: Lovelace; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @property({ type: Number }) public index!: number; diff --git a/src/panels/mailbox/ha-panel-mailbox.ts b/src/panels/mailbox/ha-panel-mailbox.ts index 22d297e744..643112c7bd 100644 --- a/src/panels/mailbox/ha-panel-mailbox.ts +++ b/src/panels/mailbox/ha-panel-mailbox.ts @@ -226,6 +226,8 @@ class HaPanelMailbox extends LitElement { ha-tabs { margin-left: max(env(safe-area-inset-left), 24px); margin-right: max(env(safe-area-inset-right), 24px); + margin-inline-start: max(env(safe-area-inset-left), 24px); + margin-inline-end: max(env(safe-area-inset-right), 24px); --paper-tabs-selection-bar-color: #fff; text-transform: uppercase; } diff --git a/src/panels/map/ha-panel-map.ts b/src/panels/map/ha-panel-map.ts index e190bd7e29..b7698ca186 100644 --- a/src/panels/map/ha-panel-map.ts +++ b/src/panels/map/ha-panel-map.ts @@ -14,7 +14,7 @@ import { HomeAssistant } from "../../types"; class HaPanelMap extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; private _entities: string[] = []; diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index ee8f238f24..ac9f0f5d63 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -1,7 +1,5 @@ -import "@material/mwc-button/mwc-button"; import "@material/mwc-linear-progress/mwc-linear-progress"; import type { LinearProgress } from "@material/mwc-linear-progress/mwc-linear-progress"; -import "@material/mwc-list/mwc-list-item"; import { mdiChevronDown, mdiMonitor, @@ -12,11 +10,11 @@ import { mdiVolumeHigh, } from "@mdi/js"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, + css, + html, nothing, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; @@ -26,25 +24,29 @@ import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; -import { domainIcon } from "../../common/entity/domain_icon"; import { supportsFeature } from "../../common/entity/supports-feature"; +import { debounce } from "../../common/util/debounce"; import "../../components/ha-button"; import "../../components/ha-button-menu"; import "../../components/ha-circular-progress"; +import "../../components/ha-domain-icon"; import "../../components/ha-icon-button"; +import "../../components/ha-list-item"; +import "../../components/ha-state-icon"; +import "../../components/ha-svg-icon"; import { UNAVAILABLE } from "../../data/entity"; import { BROWSER_PLAYER, - cleanupMediaTitle, - computeMediaControls, - computeMediaDescription, ControlButton, - formatMediaTime, - getCurrentProgress, - handleMediaControlClick, MediaPlayerEntity, MediaPlayerEntityFeature, MediaPlayerItem, + cleanupMediaTitle, + computeMediaControls, + computeMediaDescription, + formatMediaTime, + getCurrentProgress, + handleMediaControlClick, setMediaPlayerVolume, } from "../../data/media-player"; import { ResolvedMediaSource } from "../../data/media_source"; @@ -56,7 +58,6 @@ import { BrowserMediaPlayer, ERR_UNSUPPORTED_MEDIA, } from "./browser-media-player"; -import { debounce } from "../../common/util/debounce"; declare global { interface HASSDomEvents { @@ -70,8 +71,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { @property({ attribute: false }) public entityId!: string; - @property({ type: Boolean, reflect: true }) - public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; @query("mwc-linear-progress") private _progressBar?: LinearProgress; @@ -321,22 +321,19 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { : "" } - + ${ this.narrow ? html` - + + ${this._renderIcon(isBrowser, stateObj)} + ` : html` - + + ${this._renderIcon(isBrowser, stateObj)} + ` } - ${this.hass.localize("ui.components.media-browser.web-browser")} - + ${this._mediaPlayerEntities.map( (source) => html` - ${computeStateName(source)} - + ` )} @@ -383,6 +377,23 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { `; } + private _renderIcon(isBrowser: boolean, stateObj?: MediaPlayerEntity) { + if (isBrowser) { + return html``; + } + if (stateObj) { + return html` + + `; + } + return html` + + `; + } + public willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); if (changedProps.has("entityId")) { @@ -572,9 +583,10 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { --mdc-theme-primary: var(--secondary-text-color); } - mwc-button[slot="trigger"] { + ha-button-menu ha-button[slot="trigger"] { + line-height: 1; --mdc-theme-primary: var(--primary-text-color); - --mdc-icon-size: 36px; + --mdc-icon-size: 16px; } .info { @@ -583,6 +595,9 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { align-items: center; width: 100%; margin-right: 16px; + margin-inline-end: 16px; + margin-inline-start: initial; + text-overflow: ellipsis; white-space: nowrap; overflow: hidden; @@ -634,6 +649,8 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { white-space: nowrap; overflow: hidden; padding-left: 16px; + padding-inline-start: 16px; + padding-inline-end: initial; width: 100%; } @@ -651,10 +668,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { margin: 16px 0 16px 16px; } - ha-button-menu mwc-button { - line-height: 1; - } - :host([narrow]) { height: 57px; } @@ -670,6 +683,8 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { :host([narrow]) .media-info { padding-left: 8px; + padding-inline-start: 8px; + padding-inline-end: initial; } :host([narrow]) .controls { @@ -681,6 +696,8 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { :host([narrow]) .choose-player { padding-left: 0; padding-right: 8px; + padding-inline-start: 0; + padding-inline-end: 8px; min-width: 48px; flex: unset; justify-content: center; @@ -697,10 +714,15 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { left: 0; } - mwc-list-item[selected] { + ha-list-item[selected] { font-weight: bold; } + span[slot="icon"] { + display: flex; + align-items: center; + } + ha-svg-icon[slot="trailingIcon"] { margin-inline-start: 8px !important; margin-inline-end: 0px !important; diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 56bca1c6bf..8713bed2a4 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -65,10 +65,9 @@ const createMediaPanelUrl = (entityId: string, items: MediaPlayerItemId[]) => { class PanelMediaBrowser extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean, reflect: true }) - public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow = false; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; @state() _currentItem?: MediaPlayerItem; diff --git a/src/panels/media-browser/hui-dialog-web-browser-play-media.ts b/src/panels/media-browser/hui-dialog-web-browser-play-media.ts index ac10b2f91f..1a7bf5e2fe 100644 --- a/src/panels/media-browser/hui-dialog-web-browser-play-media.ts +++ b/src/panels/media-browser/hui-dialog-web-browser-play-media.ts @@ -1,5 +1,5 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { createCloseHeading } from "../../components/ha-dialog"; import "../../components/ha-hls-player"; @@ -11,8 +11,7 @@ import { WebBrowserPlayMediaDialogParams } from "./show-media-player-dialog"; export class HuiDialogWebBrowserPlayMedia extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) - private _params?: WebBrowserPlayMediaDialogParams; + @state() private _params?: WebBrowserPlayMediaDialogParams; public showDialog(params: WebBrowserPlayMediaDialogParams): void { this._params = params; diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index 55888fe09d..bac84192a2 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -189,6 +189,9 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ }, logs: { redirect: "/config/logs", + params: { + provider: "string?", + }, }, repairs: { component: "repairs", @@ -297,7 +300,7 @@ export interface Redirect { class HaPanelMy extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; @state() public _error?: string; diff --git a/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts b/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts index d218f03cdd..0ca1b026e6 100644 --- a/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts +++ b/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts @@ -17,7 +17,7 @@ let instance = 0; @customElement("ha-mfa-module-setup-flow") class HaMfaModuleSetupFlow extends LitElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; @state() private _dialogClosedCallback?: (params: { flowFinished: boolean; @@ -166,12 +166,23 @@ class HaMfaModuleSetupFlow extends LitElement { ha-markdown a { color: var(--primary-color); } + ha-markdown-element p { + text-align: center; + } + ha-markdown-element code { + background-color: transparent; + } + ha-markdown-element > *:last-child { + margin-bottom: revert; + } .init-spinner { padding: 10px 100px 34px; text-align: center; } .submit-spinner { margin-right: 16px; + margin-inline-end: 16px; + margin-inline-start: initial; } `, ]; diff --git a/src/panels/profile/ha-advanced-mode-row.ts b/src/panels/profile/ha-advanced-mode-row.ts index ce0c9ec50f..1877e04121 100644 --- a/src/panels/profile/ha-advanced-mode-row.ts +++ b/src/panels/profile/ha-advanced-mode-row.ts @@ -13,9 +13,9 @@ import { HomeAssistant } from "../../types"; class AdvancedModeRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; - @property() public coreUserData?: CoreFrontendUserData; + @property({ attribute: false }) public coreUserData?: CoreFrontendUserData; protected render(): TemplateResult { return html` diff --git a/src/panels/profile/ha-enable-shortcuts-row.ts b/src/panels/profile/ha-enable-shortcuts-row.ts index 429c439c69..4d2c54d1bf 100644 --- a/src/panels/profile/ha-enable-shortcuts-row.ts +++ b/src/panels/profile/ha-enable-shortcuts-row.ts @@ -10,7 +10,7 @@ import type { HomeAssistant } from "../../types"; class HaEnableShortcutsRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { return html` diff --git a/src/panels/profile/ha-force-narrow-row.ts b/src/panels/profile/ha-force-narrow-row.ts index 771f49c7d9..e42002e9f3 100644 --- a/src/panels/profile/ha-force-narrow-row.ts +++ b/src/panels/profile/ha-force-narrow-row.ts @@ -10,7 +10,7 @@ import type { HomeAssistant } from "../../types"; class HaForcedNarrowRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { return html` diff --git a/src/panels/profile/ha-mfa-modules-card.ts b/src/panels/profile/ha-mfa-modules-card.ts index 76eaabf877..fcf980d29a 100644 --- a/src/panels/profile/ha-mfa-modules-card.ts +++ b/src/panels/profile/ha-mfa-modules-card.ts @@ -42,6 +42,8 @@ class HaMfaModulesCard extends LitElement { return css` mwc-button { margin-right: -0.57em; + margin-inline-end: -0.57em; + margin-inline-start: initial; } ha-list-item { --mdc-list-item-meta-size: auto; diff --git a/src/panels/profile/ha-panel-profile.ts b/src/panels/profile/ha-panel-profile.ts index 9e82e842df..932163a928 100644 --- a/src/panels/profile/ha-panel-profile.ts +++ b/src/panels/profile/ha-panel-profile.ts @@ -38,7 +38,7 @@ import "./ha-set-vibrate-row"; class HaPanelProfile extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _refreshTokens?: RefreshToken[]; diff --git a/src/panels/profile/ha-pick-dashboard-row.ts b/src/panels/profile/ha-pick-dashboard-row.ts index eebac9fe47..a94fb594d8 100644 --- a/src/panels/profile/ha-pick-dashboard-row.ts +++ b/src/panels/profile/ha-pick-dashboard-row.ts @@ -14,7 +14,7 @@ import { HomeAssistant } from "../../types"; class HaPickDashboardRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() private _dashboards?: LovelaceDashboard[]; diff --git a/src/panels/profile/ha-pick-date-format-row.ts b/src/panels/profile/ha-pick-date-format-row.ts index ec4e1dae7c..500ab75d57 100644 --- a/src/panels/profile/ha-pick-date-format-row.ts +++ b/src/panels/profile/ha-pick-date-format-row.ts @@ -13,7 +13,7 @@ import { HomeAssistant } from "../../types"; class DateFormatRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { const date = new Date(); diff --git a/src/panels/profile/ha-pick-first-weekday-row.ts b/src/panels/profile/ha-pick-first-weekday-row.ts index 6a615cc433..aca53ca9d6 100644 --- a/src/panels/profile/ha-pick-first-weekday-row.ts +++ b/src/panels/profile/ha-pick-first-weekday-row.ts @@ -12,7 +12,7 @@ import { HomeAssistant } from "../../types"; class FirstWeekdayRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { return html` diff --git a/src/panels/profile/ha-pick-language-row.ts b/src/panels/profile/ha-pick-language-row.ts index 1ee3074b09..44fc5de54a 100644 --- a/src/panels/profile/ha-pick-language-row.ts +++ b/src/panels/profile/ha-pick-language-row.ts @@ -9,7 +9,7 @@ import { HomeAssistant } from "../../types"; export class HaPickLanguageRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render() { return html` diff --git a/src/panels/profile/ha-pick-number-format-row.ts b/src/panels/profile/ha-pick-number-format-row.ts index 6c9a739dd7..18f9f14050 100644 --- a/src/panels/profile/ha-pick-number-format-row.ts +++ b/src/panels/profile/ha-pick-number-format-row.ts @@ -13,7 +13,7 @@ import { HomeAssistant } from "../../types"; class NumberFormatRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { return html` diff --git a/src/panels/profile/ha-pick-theme-row.ts b/src/panels/profile/ha-pick-theme-row.ts index dc1eee7015..5f955a16dd 100644 --- a/src/panels/profile/ha-pick-theme-row.ts +++ b/src/panels/profile/ha-pick-theme-row.ts @@ -30,7 +30,7 @@ const HOME_ASSISTANT_THEME = "default"; export class HaPickThemeRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; @state() _themeNames: string[] = []; diff --git a/src/panels/profile/ha-pick-time-format-row.ts b/src/panels/profile/ha-pick-time-format-row.ts index b9d24d597f..fa4ddcbe95 100644 --- a/src/panels/profile/ha-pick-time-format-row.ts +++ b/src/panels/profile/ha-pick-time-format-row.ts @@ -13,7 +13,7 @@ import { HomeAssistant } from "../../types"; class TimeFormatRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { const date = new Date(); diff --git a/src/panels/profile/ha-pick-time-zone-row.ts b/src/panels/profile/ha-pick-time-zone-row.ts index c1bd070406..ef732bec1a 100644 --- a/src/panels/profile/ha-pick-time-zone-row.ts +++ b/src/panels/profile/ha-pick-time-zone-row.ts @@ -2,6 +2,7 @@ import "@material/mwc-list/mwc-list-item"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { formatDateTimeNumeric } from "../../common/datetime/format_date_time"; +import { resolveTimeZone } from "../../common/datetime/resolve-time-zone"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-card"; import "../../components/ha-select"; @@ -13,7 +14,7 @@ import { HomeAssistant } from "../../types"; class TimeZoneRow extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow = false; protected render(): TemplateResult { const date = new Date(); @@ -48,10 +49,9 @@ class TimeZoneRow extends LitElement { >${this.hass.localize( `ui.panel.profile.time_zone.options.${format}`, { - timezone: (format === "server" - ? this.hass.config.time_zone - : Intl.DateTimeFormat?.().resolvedOptions?.().timeZone || - "" + timezone: resolveTimeZone( + format, + this.hass.config.time_zone ).replace("_", " "), } )} - ${list.name} ` ); @@ -204,19 +208,30 @@ class PanelTodo extends LitElement { > ${listItems} -
  • - - - ${this.hass.localize("ui.panel.todo.create_list")} - + ${this.hass.user?.is_admin + ? html`
  • + + + ${this.hass.localize("ui.panel.todo.create_list")} + ` + : nothing}
    ` : this.hass.localize("panel.todo")}
    ${listItems} - - - ${this.hass.localize("ui.panel.todo.create_list")} - + ${showPane && this.hass.user?.is_admin + ? html` + + ${this.hass.localize("ui.panel.todo.create_list")} + ` + : nothing} ( - (str, variable) => `var(${variable}${str ? `, ${str}` : ""})`, - undefined - ); + .reduce< + string | undefined + >((str, variable) => `var(${variable}${str ? `, ${str}` : ""})`, undefined); } return `var(${props})`; } diff --git a/src/resources/ha-sortable-style.ts b/src/resources/ha-sidebar-edit-style.ts similarity index 74% rename from src/resources/ha-sortable-style.ts rename to src/resources/ha-sidebar-edit-style.ts index a1fcfe9209..50bf37c756 100644 --- a/src/resources/ha-sortable-style.ts +++ b/src/resources/ha-sidebar-edit-style.ts @@ -1,7 +1,7 @@ import { css } from "lit"; -export const sortableStyles = css` - #sortable a:nth-of-type(2n) paper-icon-item { +export const sidebarEditStyle = css` + .reorder-list a:nth-of-type(2n) paper-icon-item { animation-name: keyframes1; animation-iteration-count: infinite; transform-origin: 50% 10%; @@ -9,7 +9,7 @@ export const sortableStyles = css` animation-duration: 0.25s; } - #sortable a:nth-of-type(2n-1) paper-icon-item { + .reorder-list a:nth-of-type(2n-1) paper-icon-item { animation-name: keyframes2; animation-iteration-count: infinite; animation-direction: alternate; @@ -18,12 +18,12 @@ export const sortableStyles = css` animation-duration: 0.33s; } - #sortable a { + .reorder-list a { height: 48px; display: flex; } - #sortable { + .reorder-list { outline: none; display: block !important; } @@ -32,26 +32,6 @@ export const sortableStyles = css` display: flex !important; } - .sortable-fallback { - display: none; - opacity: 0; - } - - .sortable-ghost { - border: 2px solid var(--primary-color); - background: rgba(var(--rgb-primary-color), 0.25); - border-radius: 4px; - opacity: 0.4; - } - - .sortable-drag { - border-radius: 4px; - opacity: 1; - background: var(--card-background-color); - box-shadow: 0px 4px 8px 3px #00000026; - cursor: grabbing; - } - @keyframes keyframes1 { 0% { transform: rotate(-1deg); diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 21d9b18214..e06a943982 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -429,6 +429,8 @@ const mainStyles = css` --direction: ltr; --float-start: left; --float-end: right; + --margin-title-ltr: 0 0 0 24px; + --margin-title-rtl: 0 24px 0 0; ${unsafeCSS( Object.entries(derivedStyles) diff --git a/src/resources/intl-polyfill-legacy.ts b/src/resources/intl-polyfill-legacy.ts deleted file mode 100644 index 01a057d75a..0000000000 --- a/src/resources/intl-polyfill-legacy.ts +++ /dev/null @@ -1,19 +0,0 @@ -// This module is a simpler version of `intl-polyfill` without top level await, and replaces it for legacy builds. -// Babel cannot transform TLA, and Webpack uses an async function to support it, -// so builds with browser targets without async support will be broken. - -import "@formatjs/intl-getcanonicallocales/polyfill"; -import "@formatjs/intl-locale/polyfill"; -import "@formatjs/intl-pluralrules/polyfill"; -import "@formatjs/intl-pluralrules/locale-data/en"; -import "@formatjs/intl-numberformat/polyfill"; -import "@formatjs/intl-numberformat/locale-data/en"; -import "@formatjs/intl-relativetimeformat/polyfill"; -import "@formatjs/intl-relativetimeformat/locale-data/en"; -import "@formatjs/intl-datetimeformat/polyfill"; -import "@formatjs/intl-datetimeformat/locale-data/en"; -import "@formatjs/intl-datetimeformat/add-all-tz"; -import "@formatjs/intl-displaynames/polyfill"; -import "@formatjs/intl-displaynames/locale-data/en"; -import "@formatjs/intl-listformat/polyfill"; -import "@formatjs/intl-listformat/locale-data/en"; diff --git a/src/resources/sortable.ts b/src/resources/sortable.ts index 32514c4aa3..27b2f27953 100644 --- a/src/resources/sortable.ts +++ b/src/resources/sortable.ts @@ -1,4 +1,4 @@ -import Sortable from "sortablejs"; +import type Sortable from "sortablejs"; import SortableCore, { OnSpill, AutoScroll, diff --git a/src/resources/styles.ts b/src/resources/styles.ts index 80eef864e7..a2ed64635e 100644 --- a/src/resources/styles.ts +++ b/src/resources/styles.ts @@ -29,6 +29,8 @@ export const haStyle = css` app-toolbar [main-title] { margin-left: 20px; + margin-inline-start: 20px; + margin-inline-end: initial; } h1 { diff --git a/src/state-control/climate/ha-state-control-climate-humidity.ts b/src/state-control/climate/ha-state-control-climate-humidity.ts index 9918db1044..e8830eea59 100644 --- a/src/state-control/climate/ha-state-control-climate-humidity.ts +++ b/src/state-control/climate/ha-state-control-climate-humidity.ts @@ -27,10 +27,10 @@ export class HaStateControlClimateHumidity extends LitElement { @property({ attribute: false }) public stateObj!: ClimateEntity; @property({ attribute: "show-current", type: Boolean }) - public showCurrent?: boolean; + public showCurrent = false; @property({ type: Boolean, attribute: "prevent-interaction-on-scroll" }) - public preventInteractionOnScroll?: boolean; + public preventInteractionOnScroll = false; @state() private _targetHumidity?: number; diff --git a/src/state-control/climate/ha-state-control-climate-temperature.ts b/src/state-control/climate/ha-state-control-climate-temperature.ts index 0264af5ec7..2ecc9d95e3 100644 --- a/src/state-control/climate/ha-state-control-climate-temperature.ts +++ b/src/state-control/climate/ha-state-control-climate-temperature.ts @@ -55,13 +55,13 @@ export class HaStateControlClimateTemperature extends LitElement { @property({ attribute: false }) public stateObj!: ClimateEntity; @property({ attribute: "show-secondary", type: Boolean }) - public showSecondary?: boolean; + public showSecondary = false; @property({ attribute: "use-current-as-primary", type: Boolean }) - public showCurrentAsPrimary?: boolean; + public showCurrentAsPrimary = false; @property({ type: Boolean, attribute: "prevent-interaction-on-scroll" }) - public preventInteractionOnScroll?: boolean; + public preventInteractionOnScroll = false; @state() private _targetTemperature: Partial> = {}; @@ -261,7 +261,7 @@ export class HaStateControlClimateTemperature extends LitElement { this.hass.locale, formatOptions ); - return html`${formatted}${blankBeforeUnit(unit)}${unit}`; + return html`${formatted}${blankBeforeUnit(unit, this.hass.locale)}${unit}`; } private _renderCurrent(temperature: number, style: "normal" | "big") { @@ -337,14 +337,14 @@ export class HaStateControlClimateTemperature extends LitElement { private _renderSecondary() { if (!this.showSecondary) { - return html`

    `; + return html`

    `; } const currentTemperature = this.stateObj.attributes.current_temperature; if (currentTemperature && !this.showCurrentAsPrimary) { return html` -

    +

    ${this._renderCurrent(currentTemperature, "normal")}

    @@ -353,7 +353,7 @@ export class HaStateControlClimateTemperature extends LitElement { if (this._supportsTargetTemperature && this.showCurrentAsPrimary) { return html` -

    +

    ${this._renderTarget(this._targetTemperature.value!, "normal")}

    @@ -362,7 +362,7 @@ export class HaStateControlClimateTemperature extends LitElement { if (this._supportsTargetTemperatureRange && this.showCurrentAsPrimary) { return html` -

    +

    `; @@ -102,8 +106,6 @@ export class HaStateControlCoverToggle extends LitElement { return html` + + `; } diff --git a/src/state-control/humidifier/ha-state-control-humidifier-humidity.ts b/src/state-control/humidifier/ha-state-control-humidifier-humidity.ts index 459e8d6117..351fc92c19 100644 --- a/src/state-control/humidifier/ha-state-control-humidifier-humidity.ts +++ b/src/state-control/humidifier/ha-state-control-humidifier-humidity.ts @@ -30,13 +30,13 @@ export class HaStateControlHumidifierHumidity extends LitElement { @property({ attribute: false }) public stateObj!: HumidifierEntity; @property({ attribute: "show-secondary", type: Boolean }) - public showSecondary?: boolean; + public showSecondary = false; @property({ attribute: "use-current-as-primary", type: Boolean }) - public showCurrentAsPrimary?: boolean; + public showCurrentAsPrimary = false; @property({ type: Boolean, attribute: "prevent-interaction-on-scroll" }) - public preventInteractionOnScroll?: boolean; + public preventInteractionOnScroll = false; @state() private _targetHumidity?: number; diff --git a/src/state-control/lock/ha-state-control-lock-toggle.ts b/src/state-control/lock/ha-state-control-lock-toggle.ts index 2914e17587..06e975ee59 100644 --- a/src/state-control/lock/ha-state-control-lock-toggle.ts +++ b/src/state-control/lock/ha-state-control-lock-toggle.ts @@ -9,10 +9,10 @@ import { import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; -import { domainIcon } from "../../common/entity/domain_icon"; import { stateColorCss } from "../../common/entity/state_color"; import "../../components/ha-control-button"; import "../../components/ha-control-switch"; +import "../../components/ha-state-icon"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { forwardHaptic } from "../../data/haptics"; import { callProtectedLockService, LockEntity } from "../../data/lock"; @@ -81,18 +81,6 @@ export class HaStateControlLockToggle extends LitElement { const color = stateColorCss(this.stateObj); - const onIcon = domainIcon( - "lock", - this.stateObj, - locking ? "locking" : "locked" - ); - - const offIcon = domainIcon( - "lock", - this.stateObj, - unlocking ? "unlocking" : "unlocked" - ); - if (this.stateObj.state === UNKNOWN) { return html`
    @@ -100,13 +88,21 @@ export class HaStateControlLockToggle extends LitElement { .label=${this.hass.localize("ui.card.lock.lock")} @click=${this._turnOn} > - + - +
    `; @@ -127,16 +123,20 @@ export class HaStateControlLockToggle extends LitElement { })} .disabled=${this.stateObj.state === UNAVAILABLE} > - - + + > `; } diff --git a/src/state-control/state-control-circular-slider-style.ts b/src/state-control/state-control-circular-slider-style.ts index b5d4d5281a..41b77d0d9e 100644 --- a/src/state-control/state-control-circular-slider-style.ts +++ b/src/state-control/state-control-circular-slider-style.ts @@ -65,17 +65,19 @@ export const stateControlCircularSliderStyle = css` flex-direction: row; align-items: center; justify-content: center; + pointer-events: none; + } + .buttons > * { + pointer-events: auto; } .primary-state { font-size: 36px; } - .buttons ha-outlined-icon-button { --md-outlined-icon-button-container-width: 48px; --md-outlined-icon-button-container-height: 48px; --md-outlined-icon-button-icon-size: 24px; } - .container.md ha-big-number { font-size: 44px; } diff --git a/src/state-control/valve/ha-state-control-valve-toggle.ts b/src/state-control/valve/ha-state-control-valve-toggle.ts index 2eb0dbd934..b543b539d8 100644 --- a/src/state-control/valve/ha-state-control-valve-toggle.ts +++ b/src/state-control/valve/ha-state-control-valve-toggle.ts @@ -3,10 +3,10 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; -import { domainIcon } from "../../common/entity/domain_icon"; import { stateColorCss } from "../../common/entity/state_color"; import "../../components/ha-control-button"; import "../../components/ha-control-switch"; +import "../../components/ha-state-icon"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { forwardHaptic } from "../../data/haptics"; import { HomeAssistant } from "../../types"; @@ -77,9 +77,11 @@ export class HaStateControlValveToggle extends LitElement { "--color": onColor, })} > - + - +
    `; @@ -102,8 +106,6 @@ export class HaStateControlValveToggle extends LitElement { return html` + + `; } diff --git a/src/state-control/water_heater/ha-state-control-water_heater-temperature.ts b/src/state-control/water_heater/ha-state-control-water_heater-temperature.ts index 4e52499618..53a41cd1fe 100644 --- a/src/state-control/water_heater/ha-state-control-water_heater-temperature.ts +++ b/src/state-control/water_heater/ha-state-control-water_heater-temperature.ts @@ -30,10 +30,10 @@ export class HaStateControlWaterHeaterTemperature extends LitElement { @property({ attribute: false }) public stateObj!: WaterHeaterEntity; @property({ attribute: "show-current", type: Boolean }) - public showCurrent?: boolean; + public showCurrent = false; @property({ type: Boolean, attribute: "prevent-interaction-on-scroll" }) - public preventInteractionOnScroll?: boolean; + public preventInteractionOnScroll = false; @state() private _targetTemperature?: number; diff --git a/src/state-summary/state-card-alert.ts b/src/state-summary/state-card-alert.ts index 2575bc0257..e6ee8d3e05 100755 --- a/src/state-summary/state-card-alert.ts +++ b/src/state-summary/state-card-alert.ts @@ -3,6 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { stateActive } from "../common/entity/state_active"; import { computeRTL } from "../common/util/compute_rtl"; +import "../components/entity/ha-entity-toggle"; import "../components/entity/state-info"; import { haStyle } from "../resources/styles"; import type { HomeAssistant } from "../types"; diff --git a/src/state-summary/state-card-button.ts b/src/state-summary/state-card-button.ts index 45b2d6a958..71944de9a4 100644 --- a/src/state-summary/state-card-button.ts +++ b/src/state-summary/state-card-button.ts @@ -12,7 +12,7 @@ import { HomeAssistant } from "../types"; class StateCardButton extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj!: HassEntity; + @property({ attribute: false }) public stateObj!: HassEntity; @property({ type: Boolean }) public inDialog = false; diff --git a/src/state-summary/state-card-climate.ts b/src/state-summary/state-card-climate.ts index 828f422315..561990e7f5 100644 --- a/src/state-summary/state-card-climate.ts +++ b/src/state-summary/state-card-climate.ts @@ -40,7 +40,9 @@ class StateCardClimate extends LitElement { ha-climate-state { margin-left: 16px; - text-align: right; + margin-inline-start: 16px; + margin-inline-end: initial; + text-align: var(--float-end); } `, ]; diff --git a/src/state-summary/state-card-configurator.ts b/src/state-summary/state-card-configurator.ts index adc4ad23af..9f114b72a2 100644 --- a/src/state-summary/state-card-configurator.ts +++ b/src/state-summary/state-card-configurator.ts @@ -46,6 +46,8 @@ class StateCardConfigurator extends LitElement { top: 3px; height: 37px; margin-right: -0.57em; + margin-inline-end: -0.57em; + margin-inline-start: initial; } `, ]; diff --git a/src/state-summary/state-card-event.ts b/src/state-summary/state-card-event.ts index e80985d95a..b9e9a59e09 100644 --- a/src/state-summary/state-card-event.ts +++ b/src/state-summary/state-card-event.ts @@ -10,7 +10,7 @@ import { HomeAssistant } from "../types"; class StateCardEvent extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj!: HassEntity; + @property({ attribute: false }) public stateObj!: HassEntity; @property({ type: Boolean }) public inDialog = false; diff --git a/src/state-summary/state-card-humidifier.ts b/src/state-summary/state-card-humidifier.ts index f1d24b94b7..f3362fc3bf 100644 --- a/src/state-summary/state-card-humidifier.ts +++ b/src/state-summary/state-card-humidifier.ts @@ -42,7 +42,9 @@ class StateCardHumidifier extends LitElement { ha-humidifier-state { margin-left: 16px; - text-align: right; + margin-inline-start: 16px; + margin-inline-end: initial; + text-align: var(--float-end); } `, ]; diff --git a/src/state-summary/state-card-input_button.ts b/src/state-summary/state-card-input_button.ts index 2de153cb54..1c130c9a5f 100644 --- a/src/state-summary/state-card-input_button.ts +++ b/src/state-summary/state-card-input_button.ts @@ -12,7 +12,7 @@ import { HomeAssistant } from "../types"; class StateCardInputButton extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj!: HassEntity; + @property({ attribute: false }) public stateObj!: HassEntity; @property({ type: Boolean }) public inDialog = false; diff --git a/src/state-summary/state-card-input_number.ts b/src/state-summary/state-card-input_number.ts index a389da6bd2..c5b2e177ec 100644 --- a/src/state-summary/state-card-input_number.ts +++ b/src/state-summary/state-card-input_number.ts @@ -15,7 +15,7 @@ import { HomeAssistant } from "../types"; class StateCardInputNumber extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj!: HassEntity; + @property({ attribute: false }) public stateObj!: HassEntity; @property({ type: Boolean }) public inDialog = false; diff --git a/src/state-summary/state-card-input_select.ts b/src/state-summary/state-card-input_select.ts index bf276776c8..5b2cf12370 100644 --- a/src/state-summary/state-card-input_select.ts +++ b/src/state-summary/state-card-input_select.ts @@ -21,7 +21,7 @@ import type { HaSelect } from "../components/ha-select"; class StateCardInputSelect extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public stateObj!: InputSelectEntity; + @property({ attribute: false }) public stateObj!: InputSelectEntity; @query("ha-select", true) private _haSelect!: HaSelect; @@ -40,7 +40,7 @@ class StateCardInputSelect extends LitElement { protected render(): TemplateResult { return html` - + + + >( ? entityReg.entity_categories[entity.ec] : undefined, name: entity.en, + icon: entity.ic, hidden: entity.hb, display_precision: entity.dp, }; @@ -259,17 +260,7 @@ export const connectionMixin = >( } this._updateHass({ areas }); }); - subscribeConfig(conn, (config) => { - if (this.hass?.config?.time_zone !== config.time_zone) { - import("../resources/intl-polyfill").then(() => { - if ("__setDefaultTimeZone" in Intl.DateTimeFormat) { - // @ts-ignore - Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone); - } - }); - } - this._updateHass({ config }); - }); + subscribeConfig(conn, (config) => this._updateHass({ config })); subscribeServices(conn, (services) => this._updateHass({ services })); subscribePanels(conn, (panels) => this._updateHass({ panels })); subscribeFrontendUserData(conn, "core", (userData) => diff --git a/src/state/context-mixin.ts b/src/state/context-mixin.ts index d3bbc3ead6..d0ad7266f5 100644 --- a/src/state/context-mixin.ts +++ b/src/state/context-mixin.ts @@ -2,6 +2,7 @@ import { ContextProvider } from "@lit-labs/context"; import { areasContext, configContext, + connectionContext, devicesContext, entitiesContext, localeContext, @@ -24,6 +25,12 @@ export const contextMixin = >( string, ContextProvider | undefined > = { + connection: new ContextProvider(this, { + context: connectionContext, + initialValue: this.hass + ? this.hass.connection + : this._pendingHass.connection, + }), states: new ContextProvider(this, { context: statesContext, initialValue: this.hass ? this.hass.states : this._pendingHass.states, diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index f60cf0344f..c1868f699d 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -10,6 +10,7 @@ import { Constructor, HomeAssistant } from "../types"; import { storeState } from "../util/ha-pref-storage"; import { showToast } from "../util/toast"; import { HassElement } from "./hass-element"; +import { extractSearchParamsObject } from "../common/url/search-params"; declare global { interface HASSDomEvents { @@ -117,6 +118,14 @@ export default >(superClass: T) => )) { if (targetPath.startsWith(redirect.redirect)) { myParams.append("redirect", slug); + if (redirect.params) { + const params = extractSearchParamsObject(); + for (const key of Object.keys(redirect.params)) { + if (key in params) { + myParams.append(key, params[key]); + } + } + } window.open( `https://my.home-assistant.io/create-link/?${myParams.toString()}`, "_blank" diff --git a/src/translations/en.json b/src/translations/en.json index 74467bcbf1..5a887448f7 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1149,6 +1149,14 @@ "wind_speed_unit": "Wind speed unit", "device_class": "Show as", "switch_as_x": "Show switch as", + "invert": { + "label": "Invert state", + "descriptions": { + "cover": "Show as open when the switch is off", + "lock": "Show as locked when the switch is off", + "valve": "[%key:ui::dialogs::entity_registry::editor::invert::descriptions::cover%]" + } + }, "device_classes": { "binary_sensor": { "door": "Door", @@ -1763,6 +1771,7 @@ "update_area": "Update area", "delete": "Delete", "name": "Name", + "icon": "Icon", "name_required": "Name is required", "area_id": "Area ID", "unknown_error": "Unknown error", @@ -2157,7 +2166,9 @@ "custom_integration": "custom integration", "error_from_custom_integration": "This error originated from a custom integration.", "show_full_logs": "Show full logs", - "download_full_log": "Download full log" + "download_full_log": "Download full log", + "provider_not_found": "Log provider not found", + "provider_not_available": "Logs for ''{provider}'' are not available on your system." }, "lovelace": { "caption": "Dashboards", @@ -2255,10 +2266,17 @@ "pipeline": { "add_assistant": "Add assistant", "exposed_entities": "[%key:ui::panel::config::cloud::account::google::exposed_entities%]", + "assist_devices": "{number} Assist {number, plural,\n one {device}\n other {devices}\n}", "delete": { "confirm_title": "Delete {name}?", "confirm_text": "{name} will be permanently deleted." }, + "devices": { + "title": "Assist devices", + "device": "Device", + "pipeline": "Assistant", + "area": "Area" + }, "detail": { "update_assistant_action": "Update", "add_assistant_title": "Add assistant", @@ -2267,7 +2285,7 @@ "debug": "Debug", "set_as_preferred": "Set as preferred", "form": { - "name": "Name", + "name": "[%key:ui::common::name%]", "conversation_engine": "Conversation agent", "conversation_language": "[%key:ui::panel::config::voice_assistants::assistants::pipeline::detail::form::language%]", "language": "Language", @@ -2429,14 +2447,6 @@ "automation_settings": "Automation settings", "move_up": "Move up", "move_down": "Move down", - "re_order": "Re-order", - "re_order_mode": { - "title": "Re-order mode", - "description_triggers": "You are in re-order mode, you can re-order your triggers.", - "description_conditions": "You are in re-order mode, you can re-order your conditions.", - "description_actions": "You are in re-order mode, you can re-order your actions.", - "exit": "Exit" - }, "description": { "label": "Description", "placeholder": "Optional description", @@ -2498,7 +2508,9 @@ "label": "Time and location", "description": "When someone enters or leaves a zone, or at a specific time." }, - "other": { "label": "Other triggers" } + "other": { + "label": "Other triggers" + } }, "type": { "calendar": { @@ -2558,7 +2570,8 @@ "to": "To (optional)", "any_state_ignore_attributes": "Any state (ignoring attribute changes)", "description": { - "picker": "When the state of an entity (or attribute) changes." + "picker": "When the state of an entity (or attribute) changes.", + "full": "When{hasAttribute, select, \n true { {attribute} of} \n other {}\n} {hasEntity, select, \n true {{entity}} \n other {something}\n} changes{fromChoice, select, \n fromUsed { from {fromString}}\n null { from any state} \n other {}\n}{toChoice, select, \n toUsed { to {toString}} \n null { to any state} \n special { state or any attributes} \n other {}\n}{hasDuration, select, \n true { for {duration}} \n other {}\n}" } }, "homeassistant": { @@ -2669,7 +2682,11 @@ "minutes": "Minutes", "seconds": "Seconds", "description": { - "picker": "Periodically, at a defined interval." + "picker": "Periodically, at a defined interval.", + "initial": "When a time pattern matches", + "invalid": "Invalid Time Pattern for {parts}", + "full": "Trigger {secondsChoice, select, \n every {every second of }\n every_interval {every {seconds} seconds of }\n on_the_xth {on the {secondsWithOrdinal} second of }\n other {}\n} {minutesChoice, select, \n every {every minute of }\n every_interval {every {minutes} minutes of }\n has_seconds {the {minutesWithOrdinal} minute of }\n on_the_xth {on the {minutesWithOrdinal} minute of }\n other {}\n} {hoursChoice, select, \n every {every hour}\n every_interval {every {hours} hours}\n has_seconds_or_minutes {the {hoursWithOrdinal} hour}\n on_the_xth {on the {hoursWithOrdinal} hour}\n other {}\n}", + "ordinal": "{part, selectordinal, \none {#st}\ntwo {#nd}\nfew {#rd}\nother {#th}\n}" } }, "webhook": { @@ -2734,7 +2751,9 @@ "label": "Time and location", "description": "If someone is in a zone or if the current time is before or after a specified time." }, - "other": { "label": "Other conditions" }, + "other": { + "label": "Other conditions" + }, "building_blocks": { "label": "Building blocks", "description": "Build more complex conditions." @@ -2814,7 +2833,8 @@ "sunrise": "Sunrise", "sunset": "Sunset", "description": { - "picker": "If the sun is above or below the horizon." + "picker": "If the sun is above or below the horizon.", + "full": "Confirm sun{afterChoice, select, \n sunrise { after sunrise}\n sunset { after sunset}\n other {}\n}{afterOffsetChoice, select, \n offset { offset by {afterOffset}}\n other {}\n}{beforeChoice, select, \n sunrise { before sunrise}\n sunset { before sunset}\n other {}\n}{beforeOffsetChoice, select, \n offset { offset by {beforeOffset}}\n other {}\n}" } }, "template": { @@ -2898,8 +2918,12 @@ "type_select": "Action type", "continue_on_error": "Continue on error", "groups": { - "helpers": { "label": "Helpers" }, - "other": { "label": "Other actions" }, + "helpers": { + "label": "Helpers" + }, + "other": { + "label": "Other actions" + }, "building_blocks": { "label": "Building blocks", "description": "Build more complex sequences of actions." @@ -3094,6 +3118,13 @@ "full": "Test {condition}" } }, + "set_conversation_response": { + "label": "Set conversation response", + "description": { + "picker": "Set response of conversation when automation was triggered by conversation trigger.", + "full": "Set response of conversation to {response}" + } + }, "unknown": { "label": "Unknown" } @@ -4073,15 +4104,15 @@ "default_router_not_found": "The border router used for Android + iOS credentials for this network could not be found.", "default_router": "Used for Android + iOS credentials", "set_default_router": "Use router for Android + iOS credentials", - "no_routers_otbr_network": "No border routers where found, maybe the border router is not configured correctly. You can try to reset it to the factory settings.", + "no_routers_otbr_network": "No border routers were found. Check your border router is configured correctly or reset it to factory settings.", "add_dataset_from_tlv": "Add dataset from TLV", "add_dataset": "Add Thread dataset", "add_dataset_label": "Operational dataset TLV", "add_dataset_button": "Add dataset", "confirm_reset_border_router": "Reset border router?", - "confirm_reset_border_router_text": "This will reset the Home Assistant border router to its factory defaults and form a new Thread network. The old network may no longer be available, and any devices that were attached to this network may need to be recomissioned.", + "confirm_reset_border_router_text": "Home Assistant will create a new Thread network. Any devices that are currently joined on this Home Assistant Thread network will need to be re-joined.", "confirm_set_dataset_border_router": "Reconfigure border router?", - "confirm_set_dataset_border_router_text": "This will reconfigure the Home Assistant border router to use a different Thread network. The old network may no longer be available, and any devices that were attached to this network may need to be recomissioned.", + "confirm_set_dataset_border_router_text": "Home Assistant will join an existing Thread network. Any devices that are currently joined on this Home Assistant Thread network will need to be re-joined.", "otbr_config_failed": "Failed to configure the border router", "confirm_delete_dataset": "Delete {name} dataset?", "confirm_delete_dataset_text": "This network will be removed from Home Assistant.", @@ -4097,7 +4128,7 @@ "change_channel_multiprotocol_enabled_title": "The Thread radio has multiprotocol enabled", "change_channel_multiprotocol_enabled_text": "To change channel when the Thread radio has multiprotocol enabled, please use the hardware settings menu.", "change_channel_range": "Channel must be in the range 11 to 26", - "change_channel_text": "Initiate a channel change for your Thread networks. This is an advanced operation and can leave your Thread networks inoperable if the new channel is congested. Depending on existing network conditions, many of your devices may not migrate to the new channel and will require re-joining before they start working again. Use with caution." + "change_channel_text": "Initiating a channel change for your Home Assistant Thread network should be performed with caution. Some Thread devices may not migrate to the new channel automatically and, if the new channel is congested, your Thread devices may become intermittently unavailable. Some devices may need to be manually re-joined to your Thread network before they show in Home Assistant again. This action cannot be reversed (without performing another channel change)." }, "zha": { "common": { @@ -4119,7 +4150,8 @@ }, "add_device_page": { "spinner": "Searching for Zigbee devices…", - "pairing_mode": "Make sure your devices are in pairing mode. Check the instructions of your device on how to do this.", + "pairing_mode": "Make sure your devices are in pairing mode. Check the instructions of your device or {documentation_link} on how to do this.", + "pairing_mode_link": "the documentation", "discovered_text": "Devices will show up here once discovered.", "no_devices_found": "No devices were found, make sure they are in pairing mode and keep them awake while Home Assistant is searching.", "search_again": "Search again" @@ -4566,6 +4598,73 @@ "download_logs": "Download logs" } }, + "matter": { + "network_type": { + "thread": "Thread", + "wifi": "Wi-Fi", + "ethernet": "Ethernet", + "unknown": "Unknown" + }, + "node_type": { + "end_device": "End-device", + "sleepy_end_device": "Sleepy end device", + "routing_end_device": "Routing end device", + "bridge": "Bridge", + "unknown": "Unknown" + }, + "device_info": { + "device_info": "Device info", + "node_id": "Node ID", + "network_type": "Network Type", + "node_type": "Device type", + "network_name": "Network name", + "ip_adresses": "IP Address(es)", + "mac_address": "MAC address", + "available": "Available?" + }, + "device_actions": { + "reinterview_device": "Re-interview device", + "ping_device": "Ping device", + "open_commissioning_window": "Enable commisisioning mode", + "manage_fabrics": "Manage fabrics", + "view_thread_network": "View Thread network" + }, + "manage_fabrics": { + "title": "Connected fabrics", + "fabrics": "Manage the fabrics that have access to this device.", + "remove_fabric_confirm_header": "Remove {fabric} fabric from device", + "remove_fabric_confirm_text": "Are you sure you want to remove the {fabric} from the device? You will not be able to control/access the device from that ecosystem/fabric after this action!", + "remove_fabric_failed_header": "Remove {fabric} fabric failed", + "remove_fabric_failed_text": "The action did not succeed, check the logs for more information." + }, + "reinterview_node": { + "title": "Re-interview a Matter device", + "introduction": "Perform a full re-interview of a Matter device. Use this feature only if your device has missing or incorrect functionality.", + "battery_device_warning": "You will need to wake battery powered devices before starting the re-interview. Refer to your device's manual for instructions on how to wake the device.", + "run_in_background": "You can close this dialog and the interview will continue in the background.", + "start_reinterview": "Start re-interview", + "in_progress": "The device is being interviewed. This may take some time.", + "interview_failed": "The device interview failed. Additional information may be available in the logs.", + "interview_complete": "Device interview complete." + }, + "ping_node": { + "title": "Ping a Matter device", + "introduction": "Perform a (server-side) ping on your Matter device on all its (known) IP-addresses.", + "battery_device_warning": "Note that especially for battery powered devices this can take a a while. You may need to up powered devices before starting the pinging to speed up the process. Refer to your device's manual for instructions on how to wake the device.", + "start_ping": "Start ping", + "in_progress": "The device is being pinged. This may take some time.", + "ping_failed": "The device ping failed. Additional information may be available in the logs.", + "ping_complete": "Ping device complete." + }, + "open_commissioning_window": { + "title": "Enable commissioning mode", + "introduction": "Enable commissioning mode on the device to pair it to another Matter controller.", + "start_commissioning": "Enable commissioning mode", + "in_progress": "We're communicating with the device. This may take some time.", + "failed": "The command failed. Additional information may be available in the logs.", + "sharing_code": "Sharing code" + } + }, "tips": { "tip": "Tip!", "join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}", @@ -5119,6 +5218,8 @@ }, "area": { "name": "Area", + "alert_classes": "Alert Classes", + "sensor_classes": "Sensor Classes", "description": "The Area card automatically displays entities of a specific area.", "show_camera": "Show camera feed instead of area picture" }, @@ -5210,6 +5311,7 @@ "grid": { "name": "Grid", "description": "The Grid card allows you to show multiple cards in a grid.", + "title": "Title", "columns": "Columns", "square": "Render cards as squares" }, @@ -5220,7 +5322,10 @@ "history-graph": { "name": "History graph", "description": "The History graph card allows you to display a graph for each of the entities listed.", - "logarithmic_scale": "Logarithmic scale" + "logarithmic_scale": "Logarithmic scale", + "min_y_axis": "Y axis minimum", + "max_y_axis": "Y axis maximum", + "fit_y_data": "Extend Y axis limits to fit data" }, "statistics-graph": { "name": "Statistics graph", @@ -5820,14 +5925,14 @@ }, "refresh_tokens": { "header": "Refresh tokens", - "description": "Each refresh token represents a login session. Refresh tokens will be automatically removed when you click log out. The following refresh tokens are currently active for your account.", + "description": "Each refresh token represents a login session. Refresh tokens will be automatically removed when you click log out. Unused refresh tokens will be automatically removed after 90 days. The following refresh tokens are currently active for your account.", "token_title": "Refresh token for {clientId}", "created_at": "Created {date}", "last_used": "Last used {date} from {location}", "not_used": "Has never been used", "confirm_delete": "Are you sure you want to delete the refresh token for {name}?", "delete_all_tokens": "Delete all tokens", - "confirm_delete_all": "Are you sure you want to delete all refresh tokens? You will be logged out as part of this operation.", + "confirm_delete_all": "Are you sure you want to delete all refresh tokens? Your current session token will not be removed. Your long-lived access tokens will not be removed.", "delete_failed": "Failed to delete the refresh token.", "current_token_tooltip": "Unable to delete current refresh token" }, @@ -5878,6 +5983,7 @@ "previous": "Previous", "start_over": "Start over", "error": "Error: {error}", + "error_required": "[%key:ui::common::error_required%]", "hide_password": "Hide password", "show_password": "Show password", "providers": { @@ -6190,7 +6296,8 @@ "statistic_id": "Statistic id", "statistics_unit": "Statistics unit", "source": "Source", - "issue": "Issue" + "issue": "Issue", + "no_statistics": "[%key:ui::components::statistics_charts::no_statistics_found%]" } }, "yaml": { @@ -6397,7 +6504,8 @@ "history": { "start_search": "Start by selecting an area, device or entity above", "add_all": "Add all entities", - "remove_all": "Remove all selections" + "remove_all": "Remove all selections", + "download_data": "Download data" } }, "tips": { diff --git a/src/types.ts b/src/types.ts index 7238b5db6d..cb0e1931f9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -294,3 +294,5 @@ export type AsyncReturnType any> = T extends ( : never; export type Entries = [keyof T, T[keyof T]][]; + +export type ItemPath = (number | string)[]; diff --git a/src/util/is_touch.ts b/src/util/is_touch.ts new file mode 100644 index 0000000000..d5a0ad1235 --- /dev/null +++ b/src/util/is_touch.ts @@ -0,0 +1,5 @@ +export const isTouch = + "ontouchstart" in window || + navigator.maxTouchPoints > 0 || + // @ts-ignore + navigator.msMaxTouchPoints > 0; diff --git a/tsconfig.json b/tsconfig.json index a5bac525d4..72918ea5ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,9 @@ "resolveJsonModule": true, // Babel handles transpiling and no need for declaration files "noEmit": true, + // Caching + "incremental": true, + "tsBuildInfoFile": "node_modules/.cache/typescript/.tsbuildinfo", // Type checking options "noUnusedLocals": true, "noUnusedParameters": true, @@ -25,17 +28,27 @@ "plugins": [ { "name": "ts-lit-plugin", - "strict": false, + "strict": true, "rules": { + // Custom elements "no-unknown-tag-name": "error", "no-missing-import": "error", - "no-unclosed-tag": "error", + "no-missing-element-type-definition": "error", + // Binding names + "no-unknown-attribute": "off", + "no-legacy-attribute": "error", + // Binding types "no-incompatible-type-binding": "warning", - "no-invalid-css": "warning", - "no-missing-element-type-definition": "warning", - "no-property-visibility-mismatch": "error" - } - } - ] - } + // LitElement + "no-property-visibility-mismatch": "error", + // CSS + "no-invalid-css": "off", // warning does not work + }, + "globalTags": ["google-cast-launcher"], + "customHtmlData": [ + "./node_modules/@lrnwebcomponents/simple-tooltip/custom-elements.json", + ], + }, + ], + }, } diff --git a/yarn.lock b/yarn.lock index 3997cc2fa3..08e3c11db9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,7 +45,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5": +"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.23.5": version: 7.23.5 resolution: "@babel/code-frame@npm:7.23.5" dependencies: @@ -62,26 +62,26 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.23.6, @babel/core@npm:^7.11.1, @babel/core@npm:^7.12.3": - version: 7.23.6 - resolution: "@babel/core@npm:7.23.6" +"@babel/core@npm:7.23.9, @babel/core@npm:^7.0.0, @babel/core@npm:^7.11.1, @babel/core@npm:^7.12.3": + version: 7.23.9 + resolution: "@babel/core@npm:7.23.9" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.23.5" "@babel/generator": "npm:^7.23.6" "@babel/helper-compilation-targets": "npm:^7.23.6" "@babel/helper-module-transforms": "npm:^7.23.3" - "@babel/helpers": "npm:^7.23.6" - "@babel/parser": "npm:^7.23.6" - "@babel/template": "npm:^7.22.15" - "@babel/traverse": "npm:^7.23.6" - "@babel/types": "npm:^7.23.6" + "@babel/helpers": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@babel/template": "npm:^7.23.9" + "@babel/traverse": "npm:^7.23.9" + "@babel/types": "npm:^7.23.9" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: a72ba71d2f557d09ff58a5f0846344b9cea9dfcbd7418729a3a74d5b0f37a5ca024942fef4d19f248de751928a1be3d5cb0488746dd8896009dd55b974bb552e + checksum: 268cdbb86bef1b8ea5b1300f2f325e56a1740a5051360cb228ffeaa0f80282b6674f3a2b4d6466adb0691183759b88d4c37b4a4f77232c84a49ed771c84cdc27 languageName: node linkType: hard @@ -128,9 +128,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.22.15, @babel/helper-create-class-features-plugin@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/helper-create-class-features-plugin@npm:7.23.6" +"@babel/helper-create-class-features-plugin@npm:^7.22.15, @babel/helper-create-class-features-plugin@npm:^7.23.6, @babel/helper-create-class-features-plugin@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/helper-create-class-features-plugin@npm:7.23.9" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.22.5" "@babel/helper-environment-visitor": "npm:^7.22.20" @@ -143,7 +143,7 @@ __metadata: semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 5e0cff67a6809d2285215057be45de9dd8900b91e3526fad5eac79023c1d6bee32aed1a04fcdf0e4d99ee4bd49ea5459cb98260c13222edf3bb983621bb452f4 + checksum: 91c8aa8888780bd90aa50f511917cb0953ccd61b2ea4abf61915c1d68d99bb14b472969a8ae5b391d7890759dfc22be79104297be07919c38351714a4ce2fe74 languageName: node linkType: hard @@ -160,9 +160,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:0.4.4, @babel/helper-define-polyfill-provider@npm:^0.4.4": - version: 0.4.4 - resolution: "@babel/helper-define-polyfill-provider@npm:0.4.4" +"@babel/helper-define-polyfill-provider@npm:0.5.0, @babel/helper-define-polyfill-provider@npm:^0.5.0": + version: 0.5.0 + resolution: "@babel/helper-define-polyfill-provider@npm:0.5.0" dependencies: "@babel/helper-compilation-targets": "npm:^7.22.6" "@babel/helper-plugin-utils": "npm:^7.22.5" @@ -171,7 +171,7 @@ __metadata: resolve: "npm:^1.14.2" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 16c312e40ecf2ead81f3ab7275387079071012d2363022c04cf16d56fe0d781185f3a517b928f4556c716ae45e0567b817b636d5cd2fee8fb2ce2b18a04c5bcd + checksum: f849e816ec4b182a3e8fa8e09ff016f88bb95259cd6b2190b815c48f83c3d3b68e973a8ec72acc5086bfe93705cbd46ec089c06476421d858597780e42235a03 languageName: node linkType: hard @@ -335,14 +335,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/helpers@npm:7.23.6" +"@babel/helpers@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/helpers@npm:7.23.9" dependencies: - "@babel/template": "npm:^7.22.15" - "@babel/traverse": "npm:^7.23.6" - "@babel/types": "npm:^7.23.6" - checksum: 2a85fd2bcbc15a6c94dbe7b9e94d8920f9de76d164179d6895fee89c4339079d9e3e56f572bf19b5e7d1e6f1997d7fbaeaa686b47d35136852631dfd09e85c2f + "@babel/template": "npm:^7.23.9" + "@babel/traverse": "npm:^7.23.9" + "@babel/types": "npm:^7.23.9" + checksum: dd56daac8bbd7ed174bb00fd185926fd449e591d9a00edaceb7ac6edbdd7a8db57e2cb365b4fafda382201752789ced2f7ae010f667eab0f198a4571cda4d2c5 languageName: node linkType: hard @@ -357,12 +357,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.5, @babel/parser@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/parser@npm:7.23.6" +"@babel/parser@npm:^7.23.5, @babel/parser@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/parser@npm:7.23.9" bin: parser: ./bin/babel-parser.js - checksum: 6be3a63d3c9d07b035b5a79c022327cb7e16cbd530140ecb731f19a650c794c315a72c699a22413ebeafaff14aa8f53435111898d59e01a393d741b85629fa7d + checksum: 727a7a807100f6a26df859e2f009c4ddbd0d3363287b45daa50bd082ccd0d431d0c4d0e610a91f806e04a1918726cd0f5a0592c9b902a815337feed12e1cafd9 languageName: node linkType: hard @@ -390,31 +390,28 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.23.3": - version: 7.23.3 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.23.3" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.23.7": + version: 7.23.7 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.23.7" dependencies: "@babel/helper-environment-visitor": "npm:^7.22.20" "@babel/helper-plugin-utils": "npm:^7.22.5" peerDependencies: "@babel/core": ^7.0.0 - checksum: 6e13f14949eb943d33cf4d3775a7195fa93c92851dfb648931038e9eb92a9b1709fdaa5a0ff6cf063cfcd68b3e52d280f3ebc0f3085b3e006e64dd6196ecb72a + checksum: 3b0c9554cd0048e6e7341d7b92f29d400dbc6a5a4fc2f86dbed881d32e02ece9b55bc520387bae2eac22a5ab38a0b205c29b52b181294d99b4dd75e27309b548 languageName: node linkType: hard -"@babel/plugin-proposal-decorators@npm:7.23.6": - version: 7.23.6 - resolution: "@babel/plugin-proposal-decorators@npm:7.23.6" +"@babel/plugin-proposal-decorators@npm:7.23.9": + version: 7.23.9 + resolution: "@babel/plugin-proposal-decorators@npm:7.23.9" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.23.6" + "@babel/helper-create-class-features-plugin": "npm:^7.23.9" "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-replace-supers": "npm:^7.22.20" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" "@babel/plugin-syntax-decorators": "npm:^7.23.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 091796967ad728c7a00ae60c978d20f17cea08fa37bf977568880324b2619f8ce11b4a3797ef1b3137085fcb890639c49462f6be19f7cc3707e17e057df11c75 + checksum: 256a060d2346da10afdcc32d3c4939183bd8865367a64893c739174c4d247b586204dd0b7927d20e90f73714ac7d7a1a5f4740f827ef8e0277697bd626df59dc languageName: node linkType: hard @@ -670,9 +667,9 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.23.4" +"@babel/plugin-transform-async-generator-functions@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.23.9" dependencies: "@babel/helper-environment-visitor": "npm:^7.22.20" "@babel/helper-plugin-utils": "npm:^7.22.5" @@ -680,7 +677,7 @@ __metadata: "@babel/plugin-syntax-async-generators": "npm:^7.8.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e2fc132c9033711d55209f4781e1fc73f0f4da5e0ca80a2da73dec805166b73c92a6e83571a8994cd2c893a28302e24107e90856202b24781bab734f800102bb + checksum: d402494087a6b803803eb5ab46b837aab100a04c4c5148e38bfa943ea1bbfc1ecfb340f1ced68972564312d3580f550c125f452372e77607a558fbbaf98c31c0 languageName: node linkType: hard @@ -744,22 +741,21 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.23.5": - version: 7.23.5 - resolution: "@babel/plugin-transform-classes@npm:7.23.5" +"@babel/plugin-transform-classes@npm:^7.23.8": + version: 7.23.8 + resolution: "@babel/plugin-transform-classes@npm:7.23.8" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-compilation-targets": "npm:^7.22.15" + "@babel/helper-compilation-targets": "npm:^7.23.6" "@babel/helper-environment-visitor": "npm:^7.22.20" "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-optimise-call-expression": "npm:^7.22.5" "@babel/helper-plugin-utils": "npm:^7.22.5" "@babel/helper-replace-supers": "npm:^7.22.20" "@babel/helper-split-export-declaration": "npm:^7.22.6" globals: "npm:^11.1.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f6c4fed2f48bdd46a4726b829ea2ddb5c9c97edd0e55dc53791d82927daad5725052b7e785a8b7e90a53b0606166b9c554469dc94f10fba59ca9642e997d97ee + checksum: 4bb4b19e7a39871c4414fb44fc5f2cc47c78f993b74c43238dfb99c9dac2d15cb99b43f8a3d42747580e1807d2b8f5e13ce7e95e593fd839bd176aa090bf9a23 languageName: node linkType: hard @@ -941,9 +937,9 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.23.3": - version: 7.23.3 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.3" +"@babel/plugin-transform-modules-systemjs@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.9" dependencies: "@babel/helper-hoist-variables": "npm:^7.22.5" "@babel/helper-module-transforms": "npm:^7.23.3" @@ -951,7 +947,7 @@ __metadata: "@babel/helper-validator-identifier": "npm:^7.22.20" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 051112de7585fff4ffd67865066401f01f90745d41f26b0edbeec0981342c10517ce1a6b4d7051b583a3e513088eece6a3f57b1663f1dd9418071cd05f14fef9 + checksum: 4bb800e5a9d0d668d7421ae3672fccff7d5f2a36621fd87414d7ece6d6f4d93627f9644cfecacae934bc65ffc131c8374242aaa400cca874dcab9b281a21aff0 languageName: node linkType: hard @@ -1137,19 +1133,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-runtime@npm:7.23.6": - version: 7.23.6 - resolution: "@babel/plugin-transform-runtime@npm:7.23.6" +"@babel/plugin-transform-runtime@npm:7.23.9": + version: 7.23.9 + resolution: "@babel/plugin-transform-runtime@npm:7.23.9" dependencies: "@babel/helper-module-imports": "npm:^7.22.15" "@babel/helper-plugin-utils": "npm:^7.22.5" - babel-plugin-polyfill-corejs2: "npm:^0.4.6" - babel-plugin-polyfill-corejs3: "npm:^0.8.5" - babel-plugin-polyfill-regenerator: "npm:^0.5.3" + babel-plugin-polyfill-corejs2: "npm:^0.4.8" + babel-plugin-polyfill-corejs3: "npm:^0.9.0" + babel-plugin-polyfill-regenerator: "npm:^0.5.5" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 54e540ec04f05f664c69db36a78b5db56b0d3e25bbe34d7f11cfe21cc5aad5240661f5ada630c7b2abcae99d229851aafbb6b2218cf285771379e0844a3f24a9 + checksum: d942e5852f100d0de5021c4d1fda9e30c28b94aa846e09588476dd82c058fb6869a30be0cf915362bf23b5f3504aa150ca3c3b0299dbd0a86b3b1f5f744c2333 languageName: node linkType: hard @@ -1270,9 +1266,9 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.23.6, @babel/preset-env@npm:^7.11.0": - version: 7.23.6 - resolution: "@babel/preset-env@npm:7.23.6" +"@babel/preset-env@npm:7.23.9, @babel/preset-env@npm:^7.0.0, @babel/preset-env@npm:^7.11.0": + version: 7.23.9 + resolution: "@babel/preset-env@npm:7.23.9" dependencies: "@babel/compat-data": "npm:^7.23.5" "@babel/helper-compilation-targets": "npm:^7.23.6" @@ -1280,7 +1276,7 @@ __metadata: "@babel/helper-validator-option": "npm:^7.23.5" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.23.3" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.23.3" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.23.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.23.7" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/plugin-syntax-class-properties": "npm:^7.12.13" @@ -1301,13 +1297,13 @@ __metadata: "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.23.3" - "@babel/plugin-transform-async-generator-functions": "npm:^7.23.4" + "@babel/plugin-transform-async-generator-functions": "npm:^7.23.9" "@babel/plugin-transform-async-to-generator": "npm:^7.23.3" "@babel/plugin-transform-block-scoped-functions": "npm:^7.23.3" "@babel/plugin-transform-block-scoping": "npm:^7.23.4" "@babel/plugin-transform-class-properties": "npm:^7.23.3" "@babel/plugin-transform-class-static-block": "npm:^7.23.4" - "@babel/plugin-transform-classes": "npm:^7.23.5" + "@babel/plugin-transform-classes": "npm:^7.23.8" "@babel/plugin-transform-computed-properties": "npm:^7.23.3" "@babel/plugin-transform-destructuring": "npm:^7.23.3" "@babel/plugin-transform-dotall-regex": "npm:^7.23.3" @@ -1323,7 +1319,7 @@ __metadata: "@babel/plugin-transform-member-expression-literals": "npm:^7.23.3" "@babel/plugin-transform-modules-amd": "npm:^7.23.3" "@babel/plugin-transform-modules-commonjs": "npm:^7.23.3" - "@babel/plugin-transform-modules-systemjs": "npm:^7.23.3" + "@babel/plugin-transform-modules-systemjs": "npm:^7.23.9" "@babel/plugin-transform-modules-umd": "npm:^7.23.3" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.22.5" "@babel/plugin-transform-new-target": "npm:^7.23.3" @@ -1349,14 +1345,14 @@ __metadata: "@babel/plugin-transform-unicode-regex": "npm:^7.23.3" "@babel/plugin-transform-unicode-sets-regex": "npm:^7.23.3" "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2: "npm:^0.4.6" - babel-plugin-polyfill-corejs3: "npm:^0.8.5" - babel-plugin-polyfill-regenerator: "npm:^0.5.3" + babel-plugin-polyfill-corejs2: "npm:^0.4.8" + babel-plugin-polyfill-corejs3: "npm:^0.9.0" + babel-plugin-polyfill-regenerator: "npm:^0.5.5" core-js-compat: "npm:^3.31.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b47e9e7cdb0d31b2a6919ffb1b767f8159a69b000e257c1dad1121dea8c42d7ec12a892a691d1a8e90cde86edd41b017254574ec6b82a984013bb3c9e3df2b36 + checksum: 0214ac9434a2496eac7f56c0c91164421232ff2083a66e1ccab633ca91e262828e54a5cbdb9036e8fe53d53530b6597aa98c99de8ff07b5193ffd95f21dc9d2c languageName: node linkType: hard @@ -1395,29 +1391,29 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.23.6, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4": - version: 7.23.6 - resolution: "@babel/runtime@npm:7.23.6" +"@babel/runtime@npm:7.23.9, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4": + version: 7.23.9 + resolution: "@babel/runtime@npm:7.23.9" dependencies: regenerator-runtime: "npm:^0.14.0" - checksum: 4c4ab16f0361c59fb23956e4d0a29935f1f8a64aa8dd37876ce38355b6f4d8f0e54237aacb89c73b1532def60539ddde2d651523c8fa887b30b19a8cf0c465b0 + checksum: 9a520fe1bf72249f7dd60ff726434251858de15cccfca7aa831bd19d0d3fb17702e116ead82724659b8da3844977e5e13de2bae01eb8a798f2823a669f122be6 languageName: node linkType: hard -"@babel/template@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/template@npm:7.22.15" +"@babel/template@npm:^7.22.15, @babel/template@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/template@npm:7.23.9" dependencies: - "@babel/code-frame": "npm:^7.22.13" - "@babel/parser": "npm:^7.22.15" - "@babel/types": "npm:^7.22.15" - checksum: 21e768e4eed4d1da2ce5d30aa51db0f4d6d8700bc1821fec6292587df7bba2fe1a96451230de8c64b989740731888ebf1141138bfffb14cacccf4d05c66ad93f + "@babel/code-frame": "npm:^7.23.5" + "@babel/parser": "npm:^7.23.9" + "@babel/types": "npm:^7.23.9" + checksum: 1b011ba9354dc2e646561d54b6862e0df51760e6179faadd79be05825b0b6da04911e4e192df943f1766748da3037fd8493615b38707f7cadb0cf0c96601c170 languageName: node linkType: hard -"@babel/traverse@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/traverse@npm:7.23.6" +"@babel/traverse@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/traverse@npm:7.23.9" dependencies: "@babel/code-frame": "npm:^7.23.5" "@babel/generator": "npm:^7.23.6" @@ -1425,22 +1421,22 @@ __metadata: "@babel/helper-function-name": "npm:^7.23.0" "@babel/helper-hoist-variables": "npm:^7.22.5" "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.23.6" - "@babel/types": "npm:^7.23.6" + "@babel/parser": "npm:^7.23.9" + "@babel/types": "npm:^7.23.9" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: ee4434a3ce792ee8956b64d76843caa1dda4779bb621ed9f951dd3551965bf1f292f097011c9730ecbc0b57f02434b1fa5a771610a2ef570726b0df0fc3332d9 + checksum: e2bb845f7f229feb7c338f7e150f5f1abc5395dcd3a6a47f63a25242ec3ec6b165f04a6df7d4849468547faee34eb3cf52487eb0bd867a7d3c42fec2a648266f languageName: node linkType: hard -"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.23.6 - resolution: "@babel/types@npm:7.23.6" +"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6, @babel/types@npm:^7.23.9, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.23.9 + resolution: "@babel/types@npm:7.23.9" dependencies: "@babel/helper-string-parser": "npm:^7.23.4" "@babel/helper-validator-identifier": "npm:^7.22.20" to-fast-properties: "npm:^2.0.0" - checksum: 07e70bb94d30b0231396b5e9a7726e6d9227a0a62e0a6830c0bd3232f33b024092e3d5a7d1b096a65bbf2bb43a9ab4c721bf618e115bfbb87b454fa060f88cbf + checksum: bed9634e5fd0f9dc63c84cfa83316c4cb617192db9fedfea464fca743affe93736d7bf2ebf418ee8358751a9d388e303af87a0c050cb5d87d5870c1b0154f6cb languageName: node linkType: hard @@ -1451,18 +1447,18 @@ __metadata: languageName: node linkType: hard -"@bundle-stats/plugin-webpack-filter@npm:4.8.3": - version: 4.8.3 - resolution: "@bundle-stats/plugin-webpack-filter@npm:4.8.3" +"@bundle-stats/plugin-webpack-filter@npm:4.9.2": + version: 4.9.2 + resolution: "@bundle-stats/plugin-webpack-filter@npm:4.9.2" peerDependencies: core-js: ^3.0.0 - checksum: a832be786197b0cd534e4ccb35cd7230f322b2b77697f5f4ac0b098e51f7867bff0364020f6015d31f2963619e1408933454841baac035452a9fec37d921f841 + checksum: d57aeff530a1af099929f67d12402f5a563e018333d86de159af2c1714274b040de9e1a9f6dad1b3bbee6b7af0cde6f9668a368138f27ac13d34642beba8e2f0 languageName: node linkType: hard -"@codemirror/autocomplete@npm:6.11.1": - version: 6.11.1 - resolution: "@codemirror/autocomplete@npm:6.11.1" +"@codemirror/autocomplete@npm:6.12.0": + version: 6.12.0 + resolution: "@codemirror/autocomplete@npm:6.12.0" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1473,33 +1469,33 @@ __metadata: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 "@lezer/common": ^1.0.0 - checksum: 5281bec2b77d2cf916f8774e324afb862646050eab929f33780a2a67b9957d02d9abceec5f47b1b5163fedc4d07a62f1ec13fa0e2ad4b022fb75f010362e8f0e + checksum: d01fa7e5b965285e7c26116c716c2f85d8fc4810f69abb9432dd97d60bf3a05cac547fe9dc6eaf8c7723a8621c017ff41a0b5df0e635c5a7cc37b041c7676ce8 languageName: node linkType: hard -"@codemirror/commands@npm:6.3.2": - version: 6.3.2 - resolution: "@codemirror/commands@npm:6.3.2" +"@codemirror/commands@npm:6.3.3": + version: 6.3.3 + resolution: "@codemirror/commands@npm:6.3.3" dependencies: "@codemirror/language": "npm:^6.0.0" - "@codemirror/state": "npm:^6.2.0" + "@codemirror/state": "npm:^6.4.0" "@codemirror/view": "npm:^6.0.0" "@lezer/common": "npm:^1.1.0" - checksum: 85bf8242645e3f8c52b143883e837277795f9c50ad76f28f72d0d55ec921750f15ef5b4fa156b37e123b51e540d96c8db89b48bf0460cbe4a6f7b57271a65ba6 + checksum: 4b398b102d6afcbf0e0018b426287a7458867497811c9155790a3cc679b880765cd756bdb96bf35abc28fecb85c0938e618d39469ce8bc0724d4dea5d88f6ac2 languageName: node linkType: hard -"@codemirror/language@npm:6.9.3, @codemirror/language@npm:^6.0.0": - version: 6.9.3 - resolution: "@codemirror/language@npm:6.9.3" +"@codemirror/language@npm:6.10.0, @codemirror/language@npm:^6.0.0": + version: 6.10.0 + resolution: "@codemirror/language@npm:6.10.0" dependencies: "@codemirror/state": "npm:^6.0.0" - "@codemirror/view": "npm:^6.0.0" + "@codemirror/view": "npm:^6.23.0" "@lezer/common": "npm:^1.1.0" "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" style-mod: "npm:^4.0.0" - checksum: 331230a3876ed469cbf18dc2733040aa42a175b1a038080e4c317cf7f1fd8e929dc6838ba4749ea52992805f20be85878214cab6e0e7c7ab5fda872844611b7a + checksum: 674f87a5f7cae19a5e0cbdd0e06beade7f3694809cc30b31939aa04790dd43008942e3cd3b537ba5729fd14fed81c8ff62b7dcf51800db815cbf8d19d1d6a763 languageName: node linkType: hard @@ -1523,21 +1519,21 @@ __metadata: languageName: node linkType: hard -"@codemirror/state@npm:6.3.3, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.4, @codemirror/state@npm:^6.2.0": - version: 6.3.3 - resolution: "@codemirror/state@npm:6.3.3" - checksum: c0db0fc943ed36925238242522a8f4b2a4e3ecc4489cc013ea6d32be2bc615ecd813c98ff6a4c8f8ec1179376e555ec93a07dc1f0d1751aae721b5b38f812c08 +"@codemirror/state@npm:6.4.0, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0": + version: 6.4.0 + resolution: "@codemirror/state@npm:6.4.0" + checksum: d9129c456d1589ca376594620bad10c51d3dcdb57950f34637cea0e2ea073a695d426dc1cfc9b909b07365c236a6312da1eaf740c384c853009742493b8c9935 languageName: node linkType: hard -"@codemirror/view@npm:6.22.3, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0": - version: 6.22.3 - resolution: "@codemirror/view@npm:6.22.3" +"@codemirror/view@npm:6.23.1, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0": + version: 6.23.1 + resolution: "@codemirror/view@npm:6.23.1" dependencies: - "@codemirror/state": "npm:^6.1.4" + "@codemirror/state": "npm:^6.4.0" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: c0ad3e9ab49ca9aba214cfe317e506f25d094bb3243cafd0309093279eb99084810f289a5cc019db6c35463813707bdc31557b28afb30d390f4736dd0dadc541 + checksum: 42e6b73bcad6bf5d2e9578c54d166c63c4b1c7c7c7806b6f6b4bead8683dc7fcca52201a02a1f9b8ccf120a4ad87e7dcd68f09d9d3e416304dad41a75e20da82 languageName: node linkType: hard @@ -1608,13 +1604,13 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:1.18.0": - version: 1.18.0 - resolution: "@formatjs/ecma402-abstract@npm:1.18.0" +"@formatjs/ecma402-abstract@npm:1.18.2": + version: 1.18.2 + resolution: "@formatjs/ecma402-abstract@npm:1.18.2" dependencies: - "@formatjs/intl-localematcher": "npm:0.5.2" + "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 97968f3b6e3c7f6791c8431889b9e7c4e8ac3ea9a7b1dec1be9ec2aba80d27ef5ff11573dd5440022c16e5944f034027921b3cb10e8713f26da61d10a916df1e + checksum: e761653887e4446188daa023f4cb7245790ed65eb56cef4821225467e63f271f1addff386cfcbb4eb73eb67704b1f3a2b35ea4082fcadd4d05cfa0b3be3d5577 languageName: node linkType: hard @@ -1627,55 +1623,55 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.7.3": - version: 2.7.3 - resolution: "@formatjs/icu-messageformat-parser@npm:2.7.3" +"@formatjs/icu-messageformat-parser@npm:2.7.6": + version: 2.7.6 + resolution: "@formatjs/icu-messageformat-parser@npm:2.7.6" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/icu-skeleton-parser": "npm:1.7.0" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/icu-skeleton-parser": "npm:1.8.0" tslib: "npm:^2.4.0" - checksum: dda2002a09855e06db2b1238c29ec01730af650af4899e9cd1e00dd6356f5bb797b7e11604adf077ba5648b25eb776a842a324d532d4e6ed354a4c88aa28cd49 + checksum: 5baf9c1cf4b3f70d95bbac602b0695fcf67c6e2ff098e39dd53bdad0a16d192b9b5fe74dbdbeb76404bbdcdc95628d2623d24f786736074751fef13490cb6237 languageName: node linkType: hard -"@formatjs/icu-skeleton-parser@npm:1.7.0": - version: 1.7.0 - resolution: "@formatjs/icu-skeleton-parser@npm:1.7.0" +"@formatjs/icu-skeleton-parser@npm:1.8.0": + version: 1.8.0 + resolution: "@formatjs/icu-skeleton-parser@npm:1.8.0" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" + "@formatjs/ecma402-abstract": "npm:1.18.2" tslib: "npm:^2.4.0" - checksum: 5477f408034516aed862c0b5b72042be13850da31e09f8d56d88b0a88e1abbebbb7d23f352ad2ac7e697496b4a33ae076a0abb76e5908cb6bc35feb3b6bf0c78 + checksum: 8cd96d9075d1d369e4746dfaea6e3f478d21ed0672f4b777c4ee53b2660ef8c9a081976e6a8c73bba889eddc7edc52dba6eeea5fd62a8c03aa73e266b3cd89e9 languageName: node linkType: hard -"@formatjs/intl-datetimeformat@npm:6.12.0": - version: 6.12.0 - resolution: "@formatjs/intl-datetimeformat@npm:6.12.0" +"@formatjs/intl-datetimeformat@npm:6.12.2": + version: 6.12.2 + resolution: "@formatjs/intl-datetimeformat@npm:6.12.2" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/intl-localematcher": "npm:0.5.2" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 083e6fef76115ac45c96d52fb0fac973fa80514e77fb819a08685fabd671c168c3361b82792fd5684ad8c696d9fb8e42bf7a78e7f8764c6ddc53a960c72bc614 + checksum: 26acd2c7e98f65c66ffa5755d6da4f8d0f71f62d855d2ace793263832ed0a0eceb61660ecbc875caf5075f7ba968f81d5474aca8e1b8ab2985cd3741fc615b48 languageName: node linkType: hard -"@formatjs/intl-displaynames@npm:6.6.4": - version: 6.6.4 - resolution: "@formatjs/intl-displaynames@npm:6.6.4" +"@formatjs/intl-displaynames@npm:6.6.6": + version: 6.6.6 + resolution: "@formatjs/intl-displaynames@npm:6.6.6" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/intl-localematcher": "npm:0.5.2" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: e563a97e4789b3bd05e8507701e2300fdde85e0625ce316e2c7bbcfaf0eee9a49f0afec0b786e4488a671d7a304b894bd0c3a837d79a1d6695592da0544ec3f0 + checksum: b3c4ed7e7e432d20c76740f55b20f838a25cd8015a2d29051e30baeecac068491c4b0ae4e7cf18ec8e3d4f7e71df8dc56c8b0a33848fff8bcd6826a2a26093ea languageName: node linkType: hard -"@formatjs/intl-enumerator@npm:1.4.3": - version: 1.4.3 - resolution: "@formatjs/intl-enumerator@npm:1.4.3" +"@formatjs/intl-enumerator@npm:1.4.5": + version: 1.4.5 + resolution: "@formatjs/intl-enumerator@npm:1.4.5" dependencies: tslib: "npm:^2.4.0" - checksum: b9913cc51594701d1677ff4146f96fcc9b954d5400a09d6dbd252352fd3f08891a37bbaf93e6067983745f06a4e33a444ae4e955aacceaab7545bd7f09dab47b + checksum: 011d20cda70d30163934ee3e39704043a5716cad9b01a598ed14cb53e45c620a931c75c083aadf290dc345e9eafd06a77601d4462d7ab040f20032667b5e8abd languageName: node linkType: hard @@ -1688,68 +1684,68 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-listformat@npm:7.5.3": - version: 7.5.3 - resolution: "@formatjs/intl-listformat@npm:7.5.3" +"@formatjs/intl-listformat@npm:7.5.5": + version: 7.5.5 + resolution: "@formatjs/intl-listformat@npm:7.5.5" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/intl-localematcher": "npm:0.5.2" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: e62b9994fafba44470a9c0e71a4528ab3ac45133271d0b5e96d337b928288587726f671ef1d50a485d431d9abb264628dddb1ee964714ede0093a3732f8654e8 + checksum: 78907a10213a887c1943112b84b7c84a18401f036ebc5eb9baef30401cee1361beeba194237c4a4a7aa2bff4eba50c0e4bf3d7577d2ae99edc8c6afe6a5f363d languageName: node linkType: hard -"@formatjs/intl-locale@npm:3.4.3": - version: 3.4.3 - resolution: "@formatjs/intl-locale@npm:3.4.3" +"@formatjs/intl-locale@npm:3.4.5": + version: 3.4.5 + resolution: "@formatjs/intl-locale@npm:3.4.5" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/intl-enumerator": "npm:1.4.3" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/intl-enumerator": "npm:1.4.5" "@formatjs/intl-getcanonicallocales": "npm:2.3.0" tslib: "npm:^2.4.0" - checksum: ec739918a0dffe2ca38846148d9c2fe94a529560aedc71724f526dc367bc63cec47295deebf1dd8955012dfc4d40458948a0c87f345180c9e1f270005d0992bc + checksum: 3dc24bdeb01495d4f79e93b1fcf8fc22a6b8ebf40f7c3a6d586ab68783349d70ad06e0e0270ea29b88d797a52945d17e2104a8f67515fcc7388049d0239ee583 languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.5.2": - version: 0.5.2 - resolution: "@formatjs/intl-localematcher@npm:0.5.2" +"@formatjs/intl-localematcher@npm:0.5.4": + version: 0.5.4 + resolution: "@formatjs/intl-localematcher@npm:0.5.4" dependencies: tslib: "npm:^2.4.0" - checksum: 89db79ae89d6050b62d0a0f9e9c8ad4f8973eac37656f1d075ac76b6c372007f7e32791ae666fc061f7f5d89ea84ea42cfaa74dad39df4b1cf9878053e18b5d4 + checksum: 780cb29b42e1ea87f2eb5db268577fcdc53da52d9f096871f3a1bb78603b4ba81d208ea0b0b9bc21548797c941ce435321f62d2522795b83b740f90b0ceb5778 languageName: node linkType: hard -"@formatjs/intl-numberformat@npm:8.9.0": - version: 8.9.0 - resolution: "@formatjs/intl-numberformat@npm:8.9.0" +"@formatjs/intl-numberformat@npm:8.10.0": + version: 8.10.0 + resolution: "@formatjs/intl-numberformat@npm:8.10.0" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/intl-localematcher": "npm:0.5.2" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: e06179b08ab10b902db6e531e7fc5c1de7d5d7df00de0677310b3fa68cb20bea750021224aa1f0db8fbd60460c58473f04958fe52b166aad4d7a5e3a4c8071e4 + checksum: 1866bca7a24474af619265ecefb755c32705efade420e89be768cba4c1cab670d6f21456834bf2056d4560b0bad31b341798933c4bc222228c7844ed6d03d143 languageName: node linkType: hard -"@formatjs/intl-pluralrules@npm:5.2.10": - version: 5.2.10 - resolution: "@formatjs/intl-pluralrules@npm:5.2.10" +"@formatjs/intl-pluralrules@npm:5.2.12": + version: 5.2.12 + resolution: "@formatjs/intl-pluralrules@npm:5.2.12" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/intl-localematcher": "npm:0.5.2" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 69315ea80d237c701f56e9541b7d08931046e5e8965c6b57e47b8f1c7632011175c6081700c1bc09dff7d3baae8ef45cf164009f56076d4115788ec6b3589193 + checksum: 09f29358257bbb7c2c758517723c182b6ba2bd09190e04e6316a786ba9b5347e5204bc96daedaa2acc916ebb8ead4aa367788780ab10f54207a7636fefe67894 languageName: node linkType: hard -"@formatjs/intl-relativetimeformat@npm:11.2.10": - version: 11.2.10 - resolution: "@formatjs/intl-relativetimeformat@npm:11.2.10" +"@formatjs/intl-relativetimeformat@npm:11.2.12": + version: 11.2.12 + resolution: "@formatjs/intl-relativetimeformat@npm:11.2.12" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" - "@formatjs/intl-localematcher": "npm:0.5.2" + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: f79fc60f9f6aa7a468af2a3d6bd62d360b9956a124f28359ef9f716890470cf7c5888463bda9aba22dfca8c3d079cca2df1569af1a50093c477f6f29f94e63ed + checksum: b45a41570c0cf1d2c30e0fd5346b967fd618c9557e506a9d281b400bd3b1ba2c58a814b244f2d3e9796b24ca35f0621b3bd1b83e44422655f3fb27e188b4abdd languageName: node linkType: hard @@ -1820,13 +1816,13 @@ __metadata: linkType: hard "@humanwhocodes/config-array@npm:^0.11.13": - version: 0.11.13 - resolution: "@humanwhocodes/config-array@npm:0.11.13" + version: 0.11.14 + resolution: "@humanwhocodes/config-array@npm:0.11.14" dependencies: - "@humanwhocodes/object-schema": "npm:^2.0.1" - debug: "npm:^4.1.1" + "@humanwhocodes/object-schema": "npm:^2.0.2" + debug: "npm:^4.3.1" minimatch: "npm:^3.0.5" - checksum: 9f655e1df7efa5a86822cd149ca5cef57240bb8ffd728f0c07cc682cc0a15c6bdce68425fbfd58f9b3e8b16f79b3fd8cb1e96b10c434c9a76f20b2a89f213272 + checksum: 3ffb24ecdfab64014a230e127118d50a1a04d11080cbb748bc21629393d100850496456bbcb4e8c438957fe0934430d731042f1264d6a167b62d32fc2863580a languageName: node linkType: hard @@ -1837,10 +1833,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.1": - version: 2.0.1 - resolution: "@humanwhocodes/object-schema@npm:2.0.1" - checksum: dbddfd0465aecf92ed845ec30d06dba3f7bb2496d544b33b53dac7abc40370c0e46b8787b268d24a366730d5eeb5336ac88967232072a183905ee4abf7df4dab +"@humanwhocodes/object-schema@npm:^2.0.2": + version: 2.0.2 + resolution: "@humanwhocodes/object-schema@npm:2.0.2" + checksum: ef915e3e2f34652f3d383b28a9a99cfea476fa991482370889ab14aac8ecd2b38d47cc21932526c6d949da0daf4a4a6bf629d30f41b0caca25e146819cbfa70e languageName: node linkType: hard @@ -2044,12 +2040,12 @@ __metadata: linkType: hard "@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.20 - resolution: "@jridgewell/trace-mapping@npm:0.3.20" + version: 0.3.21 + resolution: "@jridgewell/trace-mapping@npm:0.3.21" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 683117e4e6707ef50c725d6d0ec4234687ff751f36fa46c2b3068931eb6a86b49af374d3030200777666579a992b7470d1bd1c591e9bf64d764dda5295f33093 + checksum: 925dda0620887e5a24f11b5a3a106f4e8b1a66155b49be6ceee61432174df33a17c243d8a89b2cd79ccebd281d817878759236a2fc42c47325ae9f73dfbfb90d languageName: node linkType: hard @@ -2077,9 +2073,9 @@ __metadata: linkType: hard "@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.1.0": - version: 1.1.2 - resolution: "@lezer/common@npm:1.1.2" - checksum: 2fb13b87c6cd1a33924908e3eb3bf08d9be9a624b32ca28d8dd369bacc7347f1765628353a8cb0d713d81a3fdc4d7939d5b0323764ecd2b926f0ca5255fe89ec + version: 1.2.1 + resolution: "@lezer/common@npm:1.2.1" + checksum: b362ed2e97664e4b36b3dbff49b52d1bfc5accc0152b577fefd46e585d012ff685d1fd336d75d80066e01c0505b1135d4cf69be5e330b5bfec2e2650c437bcae languageName: node linkType: hard @@ -2136,13 +2132,13 @@ __metadata: languageName: node linkType: hard -"@lit-labs/virtualizer@npm:2.0.11": - version: 2.0.11 - resolution: "@lit-labs/virtualizer@npm:2.0.11" +"@lit-labs/virtualizer@npm:2.0.12": + version: 2.0.12 + resolution: "@lit-labs/virtualizer@npm:2.0.12" dependencies: lit: "npm:^3.1.0" tslib: "npm:^2.0.3" - checksum: 9b44ef579c35f2063a67ec80db60813ffc9f84a3e66b4c7def46bdc6dad0507c4a8a867637233899d3671a34643bcf31f12801136946f62203e34de41810a53d + checksum: e3568ea6ef5d9362959ea2da873aaa2af27ef28c511189f38555aadbbf79919d5840f97926c62c56dd62aa8f1fd220eab88106802411cffce1121233c602cf73 languageName: node linkType: hard @@ -2162,12 +2158,12 @@ __metadata: languageName: node linkType: hard -"@lrnwebcomponents/simple-tooltip@npm:7.0.18": - version: 7.0.18 - resolution: "@lrnwebcomponents/simple-tooltip@npm:7.0.18" +"@lrnwebcomponents/simple-tooltip@npm:8.0.0": + version: 8.0.0 + resolution: "@lrnwebcomponents/simple-tooltip@npm:8.0.0" dependencies: - lit: "npm:^2.8.0" - checksum: f875ddf21751ab47a22cc872e97350a7e61b52694e99f77a00464d6d458cc2ff0638fb95696964a5e5259e777a6fc8b8765415c45a2da4f16de6c7b0cc8b5d85 + lit: "npm:^3.1.0" + checksum: d70ab59f32f9b3461b3dc24a41f2d18f8c3b48def9a53b040ff5f2161875a6ee40544982488fef826865758d2055d5286dff5d99ff6a9e6b9952ffb7eb0cba3a languageName: node linkType: hard @@ -3136,13 +3132,13 @@ __metadata: languageName: node linkType: hard -"@material/web@npm:=1.1.1": - version: 1.1.1 - resolution: "@material/web@npm:1.1.1" +"@material/web@npm:=1.2.0": + version: 1.2.0 + resolution: "@material/web@npm:1.2.0" dependencies: lit: "npm:^2.7.4 || ^3.0.0" tslib: "npm:^2.4.0" - checksum: e1066e50b06af54e64029a281bd49044c6b54817fa920453d622680f8dcbbbc920f83dfd6977c15b669fe3bfdf1e01cb851a4cd38d27cc96a497590a4b3cb2cd + checksum: abf79ee6e247b21f046bf099accdb3439d43586f3be948b5f3fc09ab2b47a3a2b37fcaff01dde4205a9e40a3d22312cf314887f87fe930af755c4dc440a32529 languageName: node linkType: hard @@ -3433,18 +3429,6 @@ __metadata: languageName: node linkType: hard -"@polymer/iron-autogrow-textarea@npm:^3.0.0-pre.26": - version: 3.0.3 - resolution: "@polymer/iron-autogrow-textarea@npm:3.0.3" - dependencies: - "@polymer/iron-behaviors": "npm:^3.0.0-pre.26" - "@polymer/iron-flex-layout": "npm:^3.0.0-pre.26" - "@polymer/iron-validatable-behavior": "npm:^3.0.0-pre.26" - "@polymer/polymer": "npm:^3.0.0" - checksum: bf11bd05a40623f6609bb9156e299414b15c7bb8c170c4f542f6586cf9273940a19f0f329047c7adf941a90ddc0deefc06be42c8770c6ca3637debad67c9456b - languageName: node - linkType: hard - "@polymer/iron-behaviors@npm:^3.0.0-pre.26": version: 3.0.1 resolution: "@polymer/iron-behaviors@npm:3.0.1" @@ -3514,17 +3498,6 @@ __metadata: languageName: node linkType: hard -"@polymer/iron-input@npm:^3.0.0-pre.26": - version: 3.0.1 - resolution: "@polymer/iron-input@npm:3.0.1" - dependencies: - "@polymer/iron-a11y-announcer": "npm:^3.0.0-pre.26" - "@polymer/iron-validatable-behavior": "npm:^3.0.0-pre.26" - "@polymer/polymer": "npm:^3.0.0" - checksum: b607da1b2962038cb12d6c220b98e49a9e9552ae89c798167d39d7caffea8671ce8d6b62721f386e623ddd535694248459bf5505b6ef335c2fbb36cf6ca373a9 - languageName: node - linkType: hard - "@polymer/iron-menu-behavior@npm:^3.0.0-pre.26": version: 3.0.2 resolution: "@polymer/iron-menu-behavior@npm:3.0.2" @@ -3610,21 +3583,6 @@ __metadata: languageName: node linkType: hard -"@polymer/paper-input@npm:3.2.1": - version: 3.2.1 - resolution: "@polymer/paper-input@npm:3.2.1" - dependencies: - "@polymer/iron-a11y-keys-behavior": "npm:^3.0.0-pre.26" - "@polymer/iron-autogrow-textarea": "npm:^3.0.0-pre.26" - "@polymer/iron-behaviors": "npm:^3.0.0-pre.26" - "@polymer/iron-form-element-behavior": "npm:^3.0.0-pre.26" - "@polymer/iron-input": "npm:^3.0.0-pre.26" - "@polymer/paper-styles": "npm:^3.0.0-pre.26" - "@polymer/polymer": "npm:^3.0.0" - checksum: d337b8c74351080e224aaa53233cd372b5f5121ecee13a4d218e42e6e5be23b21dcdd03e5019978c422436a14762c7b373b5073bb1988b11021d2103c32e3efe - languageName: node - linkType: hard - "@polymer/paper-item@npm:3.0.1": version: 3.0.1 resolution: "@polymer/paper-item@npm:3.0.1" @@ -3919,15 +3877,6 @@ __metadata: languageName: node linkType: hard -"@sinonjs/fake-timers@npm:^10.0.2": - version: 10.3.0 - resolution: "@sinonjs/fake-timers@npm:10.3.0" - dependencies: - "@sinonjs/commons": "npm:^3.0.0" - checksum: 78155c7bd866a85df85e22028e046b8d46cf3e840f72260954f5e3ed5bd97d66c595524305a6841ffb3f681a08f6e5cef572a2cce5442a8a232dc29fb409b83e - languageName: node - linkType: hard - "@sinonjs/fake-timers@npm:^11.2.2": version: 11.2.2 resolution: "@sinonjs/fake-timers@npm:11.2.2" @@ -3948,7 +3897,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/text-encoding@npm:^0.7.1": +"@sinonjs/text-encoding@npm:^0.7.2": version: 0.7.2 resolution: "@sinonjs/text-encoding@npm:0.7.2" checksum: ec713fb44888c852d84ca54f6abf9c14d036c11a5d5bfab7825b8b9d2b22127dbe53412c68f4dbb0c05ea5ed61c64679bd2845c177d81462db41e0d3d7eca499 @@ -4020,19 +3969,19 @@ __metadata: linkType: hard "@types/chrome@npm:*": - version: 0.0.254 - resolution: "@types/chrome@npm:0.0.254" + version: 0.0.258 + resolution: "@types/chrome@npm:0.0.258" dependencies: "@types/filesystem": "npm:*" "@types/har-format": "npm:*" - checksum: 61f29e116cb915ccfee266f43f9235743dee7bd9ce9c736acd35dc209d5be6ca4afc4cf064b45407f5e03ac46d8180e94f2b2c32fb3cd28b21fcd1071a25e2d7 + checksum: 301f60ba009832f8eff232159234a4b73db2d7925eae1b67a1a4687da80d56022430bc722e8c4effc0a12c1868c0bb380e981ef8b6ba744bbe52bf47591a8198 languageName: node linkType: hard -"@types/chromecast-caf-receiver@npm:6.0.12": - version: 6.0.12 - resolution: "@types/chromecast-caf-receiver@npm:6.0.12" - checksum: f4bef9d3106b19aa8a85a3d41d4eb7b4d86c334275d829bce7194f6c4afc0b23a5b056899d97c5bc639211a00bf4506259e85b9935545b8502218cde77296442 +"@types/chromecast-caf-receiver@npm:6.0.13": + version: 6.0.13 + resolution: "@types/chromecast-caf-receiver@npm:6.0.13" + checksum: 4e2fb8628985cd61f206de5dfe6f3bcbcb2527024f03e36c6cad844fe7672ecd1c2b14cd2b91987f3ab0f67a8b6430008e2a567cfa6b5df8cb09b204e7b72094 languageName: node linkType: hard @@ -4101,16 +4050,16 @@ __metadata: linkType: hard "@types/eslint@npm:*": - version: 8.56.0 - resolution: "@types/eslint@npm:8.56.0" + version: 8.56.2 + resolution: "@types/eslint@npm:8.56.2" dependencies: "@types/estree": "npm:*" "@types/json-schema": "npm:*" - checksum: 0405403788b9b8b3dbce59b668cdf5f7dc3b3fe5f82a4bf335cd7c936b95df83d892bd70af69f0d4f463125b45d15084dc5c0eeda9982d80ddbd988aa6758b63 + checksum: 9e4805e770ea90a561e1f69e5edce28b8f66e92e290705100e853c7c252cf87bef654168d0d47fc60c0effbe4517dd7a8d2fa6d3f04c7f831367d568009fd368 languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0": +"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" checksum: 7de6d928dd4010b0e20c6919e1a6c27b61f8d4567befa89252055fad503d587ecb9a1e3eab1b1901f923964d7019796db810b7fd6430acb26c32866d126fd408 @@ -4263,8 +4212,8 @@ __metadata: linkType: hard "@types/koa@npm:*, @types/koa@npm:^2.11.6": - version: 2.13.12 - resolution: "@types/koa@npm:2.13.12" + version: 2.14.0 + resolution: "@types/koa@npm:2.14.0" dependencies: "@types/accepts": "npm:*" "@types/content-disposition": "npm:*" @@ -4274,7 +4223,7 @@ __metadata: "@types/keygrip": "npm:*" "@types/koa-compose": "npm:*" "@types/node": "npm:*" - checksum: d148fb02aa25cb239d5179211cd66f19275e7fc2563532dd2bc347163332f771dea224b7555209530abf6777afa5b5c7a2d650e752fb1126ce362fbdde4ec214 + checksum: 00bd0dd9f2366eabbd05a9af1c83e10679b4241e69b75927c7653826508c03e9b19b2c03499c5ff6496acbb192f5c0a860b4e95308462e945e2fb79636cbf3cf languageName: node linkType: hard @@ -4312,10 +4261,10 @@ __metadata: languageName: node linkType: hard -"@types/luxon@npm:3.3.7": - version: 3.3.7 - resolution: "@types/luxon@npm:3.3.7" - checksum: 282ac72fd55da0c9d57f376ba9061ce83506cf6239c32259dacde0800964089f6183d2e449ef2ddd89b079fb8bfdffd7e5dbf187eb5c9f106aeaeca2aa60ed09 +"@types/luxon@npm:3.4.2": + version: 3.4.2 + resolution: "@types/luxon@npm:3.4.2" + checksum: fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d languageName: node linkType: hard @@ -4348,20 +4297,20 @@ __metadata: linkType: hard "@types/node-forge@npm:^1.3.0": - version: 1.3.10 - resolution: "@types/node-forge@npm:1.3.10" + version: 1.3.11 + resolution: "@types/node-forge@npm:1.3.11" dependencies: "@types/node": "npm:*" - checksum: 111520ac4db33bba4e46fcb75e9c29234ca78e2ece32fc929e7382798cdb7985e01da7e8f70c32769f42996e8d06f347d34d90308951cf2d004f418135ac7735 + checksum: 670c9b377c48189186ec415e3c8ed371f141ecc1a79ab71b213b20816adeffecba44dae4f8406cc0d09e6349a4db14eb8c5893f643d8e00fa19fc035cf49dee0 languageName: node linkType: hard "@types/node@npm:*": - version: 20.10.5 - resolution: "@types/node@npm:20.10.5" + version: 20.11.5 + resolution: "@types/node@npm:20.11.5" dependencies: undici-types: "npm:~5.26.4" - checksum: 4a378428d2c9f692b19801a5a3d20dc4c0ad5d4a3d103350f8b401af439941a9aa5efeadc8eb9db13c66c620318bc7f336abfc8934f82fd32c4a689d85068c6f + checksum: 9f31c471047d7b3e240ce7b77ff29b0d15e83be7e3feafb3d0b0d0931122b438b1eefa302a5a2e1e9849914ff3fd76aafbd8ccb372efb1331ba048da63bce6f8 languageName: node linkType: hard @@ -4517,13 +4466,13 @@ __metadata: languageName: node linkType: hard -"@types/tar@npm:6.1.10": - version: 6.1.10 - resolution: "@types/tar@npm:6.1.10" +"@types/tar@npm:6.1.11": + version: 6.1.11 + resolution: "@types/tar@npm:6.1.11" dependencies: "@types/node": "npm:*" minipass: "npm:^4.0.0" - checksum: da525415a9bac9e81a1498d0b684dd7fa34f69a8568b54ab19660e99d5e7dcdeb2527a40059e1cfc697fe4bbcc18cd03a50c96356d84ab865345c2c48a9d88f6 + checksum: 0d54b8acbd7d2fc43bd1097eef5058604a6b0e3a394cf485038303ca3ef39ecb42451c7dc5a2b9b18420e137ef5b2c76ec504e94c2f45010b2c8e8c3a49d9de7 languageName: node linkType: hard @@ -4566,15 +4515,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/eslint-plugin@npm:6.16.0" +"@typescript-eslint/eslint-plugin@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/eslint-plugin@npm:6.19.1" dependencies: "@eslint-community/regexpp": "npm:^4.5.1" - "@typescript-eslint/scope-manager": "npm:6.16.0" - "@typescript-eslint/type-utils": "npm:6.16.0" - "@typescript-eslint/utils": "npm:6.16.0" - "@typescript-eslint/visitor-keys": "npm:6.16.0" + "@typescript-eslint/scope-manager": "npm:6.19.1" + "@typescript-eslint/type-utils": "npm:6.19.1" + "@typescript-eslint/utils": "npm:6.19.1" + "@typescript-eslint/visitor-keys": "npm:6.19.1" debug: "npm:^4.3.4" graphemer: "npm:^1.4.0" ignore: "npm:^5.2.4" @@ -4587,44 +4536,44 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 4bedce948ac3c20492a59813ee5d4f1f2306310857864dfaac2736f6c38e18785002c36844fd64c9fbdf3059fc390b29412be105fd7a118177f1eeeb1eb533f7 + checksum: e88a35527b066a42d0253d153183a360faedc1cd39867c541ce7cb1f7b22f8446bb913b998fcdeba269d5d4217888af42e6d64da5c0592b1f49ed5648d2e3e84 languageName: node linkType: hard -"@typescript-eslint/parser@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/parser@npm:6.16.0" +"@typescript-eslint/parser@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/parser@npm:6.19.1" dependencies: - "@typescript-eslint/scope-manager": "npm:6.16.0" - "@typescript-eslint/types": "npm:6.16.0" - "@typescript-eslint/typescript-estree": "npm:6.16.0" - "@typescript-eslint/visitor-keys": "npm:6.16.0" + "@typescript-eslint/scope-manager": "npm:6.19.1" + "@typescript-eslint/types": "npm:6.19.1" + "@typescript-eslint/typescript-estree": "npm:6.19.1" + "@typescript-eslint/visitor-keys": "npm:6.19.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 3d941ce345dc2ce29957e2110957662873d514b094b8939923c3281d858c11cd1f9058db862644afe14f68d087770f39a0a1f9e523a2013ed5d2fdf3421b34d0 + checksum: 63ff00a56586879a62e40b27b55c94501173fcf2fb5a620d01e7505851b4bb20feb1e7fbad36010af97aefc0a722267d9ce3aa004abab22cb7eb23eebb0102ce languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/scope-manager@npm:6.16.0" +"@typescript-eslint/scope-manager@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/scope-manager@npm:6.19.1" dependencies: - "@typescript-eslint/types": "npm:6.16.0" - "@typescript-eslint/visitor-keys": "npm:6.16.0" - checksum: 3360aae4b85f5c31d20ad48d771cc09a6f8f6b1811b00d94f06e55b5a09c610ac75631b1c4edecb3bec682d41351b87e7d14d42bee84aa032064d0e13463035b + "@typescript-eslint/types": "npm:6.19.1" + "@typescript-eslint/visitor-keys": "npm:6.19.1" + checksum: 2a17f68d3c41582bfac7ecd192e2c2539cf4d2c9728a7018d842da7a8a23986b8a1f8cfcb59862c909b483140a2d164a4ba44451905e0a141378e5dd0df056cc languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/type-utils@npm:6.16.0" +"@typescript-eslint/type-utils@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/type-utils@npm:6.19.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:6.16.0" - "@typescript-eslint/utils": "npm:6.16.0" + "@typescript-eslint/typescript-estree": "npm:6.19.1" + "@typescript-eslint/utils": "npm:6.19.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.0.1" peerDependencies: @@ -4632,23 +4581,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 5964b87a87252bed278a248eb568902babd7c34defd3af8c3df371926d96aec716f33f1dc14bde170e93f73ed1b0af6e591e647853d0f33f378e2c7b3b73fc5b + checksum: 5150b897d8b3778c549c6b964b031981da1039dfa0fb89a0eb92702735ca55793d2f840af14b340eccbca81669ba3dd02d7f09fb420fb66b18ec9f1f211b3243 languageName: node linkType: hard -"@typescript-eslint/types@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/types@npm:6.16.0" - checksum: 236ca318c2440c95068e5d4d147e2bfed62447775e18695e21c8ca04a341a74d01c37ed2b417629b7bf2fb91ad4fd5e2a6570215d16fc24dd1507ce6973b4e22 +"@typescript-eslint/types@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/types@npm:6.19.1" + checksum: 93f3ded80b81a1b8686866b93e36ddf9bac04604d09e88d7ed1ec25b6b2f49ff64747d8d194ba1f3215e231fd0790b88fb5ecadcc6ed53ff584f8c0b87423216 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.16.0" +"@typescript-eslint/typescript-estree@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/typescript-estree@npm:6.19.1" dependencies: - "@typescript-eslint/types": "npm:6.16.0" - "@typescript-eslint/visitor-keys": "npm:6.16.0" + "@typescript-eslint/types": "npm:6.19.1" + "@typescript-eslint/visitor-keys": "npm:6.19.1" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" @@ -4658,34 +4607,34 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 8e1ef03ecabaf3791b11240a51217836dbb74850e458258db77ac5eab5508cd9c63fb671924993d1e7654718c0c857c3550d51ecba0845fe489d143bb858e1b1 + checksum: 3ce91dd477ccb2cc3cf5d07ac8d23792988f4fad78bfd39783292846f32daea5081d3790ba9cc795d9de89ea2e1d55dc9c3d2aeaa8597093b0f6ac3a206195e9 languageName: node linkType: hard -"@typescript-eslint/utils@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/utils@npm:6.16.0" +"@typescript-eslint/utils@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/utils@npm:6.19.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" "@types/json-schema": "npm:^7.0.12" "@types/semver": "npm:^7.5.0" - "@typescript-eslint/scope-manager": "npm:6.16.0" - "@typescript-eslint/types": "npm:6.16.0" - "@typescript-eslint/typescript-estree": "npm:6.16.0" + "@typescript-eslint/scope-manager": "npm:6.19.1" + "@typescript-eslint/types": "npm:6.19.1" + "@typescript-eslint/typescript-estree": "npm:6.19.1" semver: "npm:^7.5.4" peerDependencies: eslint: ^7.0.0 || ^8.0.0 - checksum: 84dd02f7c8e47fae699cc222da5cbea08b28c6e1cc7827860430bc86c2a17ee3f86e198a4356902b95930f85785aa662266ea9c476f69bf80c6a5f648e55f9f4 + checksum: f8931df675defa84af373c81bbb13cc34c2fcf0803c687a38b982e85335dbf2fb8415667fbabaa043df0326ba3e98ed974104bbd21f09ec538304fc3adeed0c3 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:6.16.0": - version: 6.16.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.16.0" +"@typescript-eslint/visitor-keys@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/visitor-keys@npm:6.19.1" dependencies: - "@typescript-eslint/types": "npm:6.16.0" + "@typescript-eslint/types": "npm:6.19.1" eslint-visitor-keys: "npm:^3.4.1" - checksum: 19e559f14ea0092585a374b8c5f1aca9b6b271fc23909d9857de9cf71a1e1d3abc0afd237e9c02d7a5fbdfe8e3be7853cf9fedf40a6f16bac3495cb7f4e67982 + checksum: b41f3247520e1e4d3e43876843b03f0d887e544d4ac8a9e1f4b25d08568da36fedde883fa226488a595f688198859cd0290d0f1351c2ca6cbc30cca2c90adf21 languageName: node linkType: hard @@ -4696,127 +4645,128 @@ __metadata: languageName: node linkType: hard -"@vaadin/a11y-base@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/a11y-base@npm:24.3.2" +"@vaadin/a11y-base@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/a11y-base@npm:24.3.4" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.2" + "@vaadin/component-base": "npm:~24.3.4" lit: "npm:^3.0.0" - checksum: 8ddff8610cdf74816bcd9f24e6d7985c869cc5691de0a5928bf794d36e7e655a9d8e51c88c4918f5784dc35331a09bccf1de3233ed263a0854fa4bafc171a21e + checksum: ef74bdd1e20f6cb131f613ffabf3099170c9713fcc3d7bd54c5471dfa3c355adba84befff585981420bb4525943b30830bff8b3c32db864edd80f720dd8ca723 languageName: node linkType: hard -"@vaadin/combo-box@npm:24.3.2": - version: 24.3.2 - resolution: "@vaadin/combo-box@npm:24.3.2" +"@vaadin/combo-box@npm:24.3.4": + version: 24.3.4 + resolution: "@vaadin/combo-box@npm:24.3.4" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.2" - "@vaadin/component-base": "npm:~24.3.2" - "@vaadin/field-base": "npm:~24.3.2" - "@vaadin/input-container": "npm:~24.3.2" - "@vaadin/item": "npm:~24.3.2" - "@vaadin/lit-renderer": "npm:~24.3.2" - "@vaadin/overlay": "npm:~24.3.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.2" - "@vaadin/vaadin-material-styles": "npm:~24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.2" - checksum: 1b1a5b80977a782e74717030dc5007bb29668534b6a25dca592f2cc8d666479682c622501847bdf87e764fb688f5e23b0070e6089114e10aebdc7c1915c62f7b + "@vaadin/a11y-base": "npm:~24.3.4" + "@vaadin/component-base": "npm:~24.3.4" + "@vaadin/field-base": "npm:~24.3.4" + "@vaadin/input-container": "npm:~24.3.4" + "@vaadin/item": "npm:~24.3.4" + "@vaadin/lit-renderer": "npm:~24.3.4" + "@vaadin/overlay": "npm:~24.3.4" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.4" + "@vaadin/vaadin-material-styles": "npm:~24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.4" + checksum: a2d08d8d64a8b88a00f4436e5df1ddba402fd8b909bcfe3647eed1b1caba9b6edc93743fbdd56b73f0b7a5e71a7ab32e38a6912e266637453b11ecb34d3cf5f8 languageName: node linkType: hard -"@vaadin/component-base@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/component-base@npm:24.3.2" +"@vaadin/component-base@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/component-base@npm:24.3.4" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" "@vaadin/vaadin-development-mode-detector": "npm:^2.0.0" "@vaadin/vaadin-usage-statistics": "npm:^2.1.0" lit: "npm:^3.0.0" - checksum: b5fc7a7f8cb9ad26a1a4f4b554ac78f29c34ecaab74fd48516656d70347c7a3c9ae00a958b9a9a31f9861cf29bc640166567d0a8102b9dcf75e6ea08e184a102 + checksum: 4411977be1e394fc03d84f026fc093393c8f511df588560b43fae4f8b360222473723a6d56b6a65cde90b1d9fba29f3e9640fa0e2a4816f1701c4a5a43a86fe3 languageName: node linkType: hard -"@vaadin/field-base@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/field-base@npm:24.3.2" +"@vaadin/field-base@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/field-base@npm:24.3.4" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.2" - "@vaadin/component-base": "npm:~24.3.2" + "@vaadin/a11y-base": "npm:~24.3.4" + "@vaadin/component-base": "npm:~24.3.4" lit: "npm:^3.0.0" - checksum: 1be21ff4e6eabaa203ae1ab8f2f55033d1830359782bac7e001721c22d5e7c404436644c3d7e2bc9e5af1f743949f37f874c99495f3d3e2df47a51aed2b5f046 + checksum: fda6584bb7f343665070f6db1c4c9a705e82e97d0c9e482b5dc615a07677f3bdcc7d10a182e6f8a97b16d3c698a165538feb69b3a9d317e1d6a86912a8db3179 languageName: node linkType: hard -"@vaadin/icon@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/icon@npm:24.3.2" - dependencies: - "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.2" - lit: "npm:^3.0.0" - checksum: 2a5f9eeed5aac093f38878f749c774ad20ecf2ca4105c606f6b1938a0f3e4181b79a8ac8c51f7b3d1990552cd6bcdc04984c1846b41f7ef4143fc96b2b276445 - languageName: node - linkType: hard - -"@vaadin/input-container@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/input-container@npm:24.3.2" - dependencies: - "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.2" - "@vaadin/vaadin-material-styles": "npm:~24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.2" - lit: "npm:^3.0.0" - checksum: b43926c876259e189d1910696f539fbb90b17478fca101bd3216a471afaf16d765c8d5952bddafe8282fcfe9babe7dfc819f84ca2adb108eba4616a32858ffde - languageName: node - linkType: hard - -"@vaadin/item@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/item@npm:24.3.2" +"@vaadin/icon@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/icon@npm:24.3.4" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.2" - "@vaadin/component-base": "npm:~24.3.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.2" - "@vaadin/vaadin-material-styles": "npm:~24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.2" - checksum: b56cbe033a6fbf591edd26780658d5cc89338d79ced469812be0f6f0f462a571bc1550f835681d6a6981119dd958810312abe31c6641317e8be5dbcc3cfd1f01 - languageName: node - linkType: hard - -"@vaadin/lit-renderer@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/lit-renderer@npm:24.3.2" - dependencies: + "@vaadin/component-base": "npm:~24.3.4" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.4" lit: "npm:^3.0.0" - checksum: 3736065a55f69c13f76d202c7a9a4f690f2c9e84a127e9558ff522b113698c5a6ea8bf8f4aefbf3d532bd5eb2798f427ab0e35459f270d3c458861fb1861c194 + checksum: cb09713d42381a8a437c7485d2c71a84b91f0489e062c41abe5ae4aab9a40d0bedbb78020a8e1e847d71467a0011b2fd91944cd2eb8993d18efc98efb7dd0de1 languageName: node linkType: hard -"@vaadin/overlay@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/overlay@npm:24.3.2" +"@vaadin/input-container@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/input-container@npm:24.3.4" + dependencies: + "@polymer/polymer": "npm:^3.0.0" + "@vaadin/component-base": "npm:~24.3.4" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.4" + "@vaadin/vaadin-material-styles": "npm:~24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.4" + lit: "npm:^3.0.0" + checksum: a38155c598328f8d6ad9d41d8b36e3eef65ddb2f73cea09cd114284570b10f1564c7414f6dea4c6cc811f01f114dae8853e3188b883ea909bc23024bb7ada83c + languageName: node + linkType: hard + +"@vaadin/item@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/item@npm:24.3.4" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.2" - "@vaadin/component-base": "npm:~24.3.2" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.2" - "@vaadin/vaadin-material-styles": "npm:~24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.2" - checksum: e04aff31f8cf2475dfed3d7f29ab1c7e08d914c6c74fbd7abd92bdd89a2c9ace810436cb2b31840fa6e6a152f1b39596f4c6625611c9174d1fdff7a8c20acafa + "@vaadin/a11y-base": "npm:~24.3.4" + "@vaadin/component-base": "npm:~24.3.4" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.4" + "@vaadin/vaadin-material-styles": "npm:~24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.4" + checksum: 9d4f1e57305febd387e476955a56c27bd55c524da60105532b39263644d2b246d578e9bf1d41d90a114919684121520007c9a881cc876ffcb79f80fcaf107374 + languageName: node + linkType: hard + +"@vaadin/lit-renderer@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/lit-renderer@npm:24.3.4" + dependencies: + lit: "npm:^3.0.0" + checksum: 31283f1e8a12661fb75cae7d73cabd4a29eeaa310ae998b206b3a8f5ef0203d7d8235c75f39d848b8c9122e0b0b03bb8c889dd2f39be89d7edc84af7769a3ae2 + languageName: node + linkType: hard + +"@vaadin/overlay@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/overlay@npm:24.3.4" + dependencies: + "@open-wc/dedupe-mixin": "npm:^1.3.0" + "@polymer/polymer": "npm:^3.0.0" + "@vaadin/a11y-base": "npm:~24.3.4" + "@vaadin/component-base": "npm:~24.3.4" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.4" + "@vaadin/vaadin-material-styles": "npm:~24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.4" + checksum: 357a446e162deb377cac3c5b4004651f7e2a882e209cc8180ebd32ba560e974a3bbfb7294dc36092ba8f8ad6946b7b8f3f2ba5afbe120d9e7630871d0cdd03f8 languageName: node linkType: hard @@ -4827,36 +4777,36 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-lumo-styles@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/vaadin-lumo-styles@npm:24.3.2" +"@vaadin/vaadin-lumo-styles@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/vaadin-lumo-styles@npm:24.3.4" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.2" - "@vaadin/icon": "npm:~24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.2" - checksum: e37abd1d54d5045815f91efe8ef845fd2e5417bf7a8f1aaff8d98adf34c0cf0ec25990d0cb62f869d04777b9e7f2b64437e8d83cec05121e482d60b4bba61ff1 + "@vaadin/component-base": "npm:~24.3.4" + "@vaadin/icon": "npm:~24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.4" + checksum: c04ae63de4d5ed01f8ee4a2786a47dc8cbf9b238fd2199115ac6cd63bbc110ed6c7bd3d50eb0a1d7fa2036343f63c5b475f0376b2c4c081be111a7c1b356290c languageName: node linkType: hard -"@vaadin/vaadin-material-styles@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/vaadin-material-styles@npm:24.3.2" +"@vaadin/vaadin-material-styles@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/vaadin-material-styles@npm:24.3.4" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.2" - checksum: 8de34251d755d0120d9b79be6c0fa481c928c3ad6044caae3b4f9217c404cadb032fa8c8ab4fcd0150f89e0f7a5ade4660964056e4470923353c2f234ca0673b + "@vaadin/component-base": "npm:~24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.4" + checksum: 60f3bd1598705e822428cde7698af93bd9b025f9229af2a489005e210dc7b1a3a9bcd9f960da9b44174c03dbdb2d16a5f43062b78ab3f34acc6a8d09d9c03919 languageName: node linkType: hard -"@vaadin/vaadin-themable-mixin@npm:24.3.2, @vaadin/vaadin-themable-mixin@npm:~24.3.2": - version: 24.3.2 - resolution: "@vaadin/vaadin-themable-mixin@npm:24.3.2" +"@vaadin/vaadin-themable-mixin@npm:24.3.4, @vaadin/vaadin-themable-mixin@npm:~24.3.4": + version: 24.3.4 + resolution: "@vaadin/vaadin-themable-mixin@npm:24.3.4" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" lit: "npm:^3.0.0" - checksum: cb85790319b4f76eabf6dd9ff3455781773a3bfcd534fd2ab040c5a792e485bbbb60aa36f8f6c837c1d42ece069ea54e2cd67397053d0f64eacea009ddc5394b + checksum: ad932481d1f80759e583c58926d0f8e1cf67b29f6cd2ddfe6f16af16848f6f15f91eb266c093746ddfc2b562e855972742df40ff065855a536f872738e1320e4 languageName: node linkType: hard @@ -5356,11 +5306,11 @@ __metadata: linkType: hard "acorn@npm:^8.5.0, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.11.2 - resolution: "acorn@npm:8.11.2" + version: 8.11.3 + resolution: "acorn@npm:8.11.3" bin: acorn: bin/acorn - checksum: ff559b891382ad4cd34cc3c493511d0a7075a51f5f9f02a03440e92be3705679367238338566c5fbd3521ecadd565d29301bc8e16cb48379206bffbff3d72500 + checksum: b688e7e3c64d9bfb17b596e1b35e4da9d50553713b3b3630cf5690f2b023a84eac90c56851e6912b483fe60e8b4ea28b254c07e92f17ef83d72d78745a8352dd languageName: node linkType: hard @@ -5746,13 +5696,6 @@ __metadata: languageName: node linkType: hard -"array-flatten@npm:^2.1.2": - version: 2.1.2 - resolution: "array-flatten@npm:2.1.2" - checksum: e8988aac1fbfcdaae343d08c9a06a6fddd2c6141721eeeea45c3cf523bf4431d29a46602929455ed548c7a3e0769928cdc630405427297e7081bd118fdec9262 - languageName: node - linkType: hard - "array-includes@npm:^3.1.7": version: 3.1.7 resolution: "array-includes@npm:3.1.7" @@ -6003,39 +5946,39 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.6": - version: 0.4.7 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.7" +"babel-plugin-polyfill-corejs2@npm:^0.4.8": + version: 0.4.8 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.8" dependencies: "@babel/compat-data": "npm:^7.22.6" - "@babel/helper-define-polyfill-provider": "npm:^0.4.4" + "@babel/helper-define-polyfill-provider": "npm:^0.5.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 3b61cdb275592f61b29d582ee8c738a13d9897c5dd201cddb0610b381f3ae139ebc988ac96f72978fc143c3d50c15d46618df865822e282c8e76c236e7378b63 + checksum: 6b5a79bdc1c43edf857fd3a82966b3c7ff4a90eee00ca8d663e0a98304d6e285a05759d64a4dbc16e04a2a5ea1f248673d8bf789711be5e694e368f19884887c languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.8.5": - version: 0.8.7 - resolution: "babel-plugin-polyfill-corejs3@npm:0.8.7" +"babel-plugin-polyfill-corejs3@npm:^0.9.0": + version: 0.9.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.9.0" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.4.4" - core-js-compat: "npm:^3.33.1" + "@babel/helper-define-polyfill-provider": "npm:^0.5.0" + core-js-compat: "npm:^3.34.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: defbc6de3d309c9639dd31223b5011707fcc0384037ac5959a1aefe16eb314562e1c1e5cfbce0af14a220d639ef92dfe5baf66664e9e6054656aca2841677622 + checksum: efdf9ba82e7848a2c66e0522adf10ac1646b16f271a9006b61a22f976b849de22a07c54c8826887114842ccd20cc9a4617b61e8e0789227a74378ab508e715cd languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.5.3": - version: 0.5.4 - resolution: "babel-plugin-polyfill-regenerator@npm:0.5.4" +"babel-plugin-polyfill-regenerator@npm:^0.5.5": + version: 0.5.5 + resolution: "babel-plugin-polyfill-regenerator@npm:0.5.5" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.4.4" + "@babel/helper-define-polyfill-provider": "npm:^0.5.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 461b735c6c0eca3c7b4434d14bfa98c2ab80f00e2bdc1c69eb46d1d300092a9786d76bbd3ee55e26d2d1a2380c14592d8d638e271dfd2a2b78a9eacffa3645d1 + checksum: 3a9b4828673b23cd648dcfb571eadcd9d3fadfca0361d0a7c6feeb5a30474e92faaa49f067a6e1c05e49b6a09812879992028ff3ef3446229ff132d6e1de7eb6 languageName: node linkType: hard @@ -6178,14 +6121,12 @@ __metadata: linkType: hard "bonjour-service@npm:^1.0.11": - version: 1.1.1 - resolution: "bonjour-service@npm:1.1.1" + version: 1.2.1 + resolution: "bonjour-service@npm:1.2.1" dependencies: - array-flatten: "npm:^2.1.2" - dns-equal: "npm:^1.0.0" fast-deep-equal: "npm:^3.1.3" multicast-dns: "npm:^7.2.5" - checksum: 60a14328dff846a66ae5cddbba4f2e2845a4b3cf62f64d93b57808e08e5e1a8e8c4454e37e0e289741706b359a343444ba132957bf53be9e8f5eaebdebb06306 + checksum: 8350d135ab8dd998a829136984d7f74bfc0667b162ab99ac98bae54d72ff7a6003c6fb7911739dfba7c56a113bd6ab06a4d4fe6719b18e66592c345663e7d923 languageName: node linkType: hard @@ -6249,17 +6190,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.14.5, browserslist@npm:^4.22.2": - version: 4.22.2 - resolution: "browserslist@npm:4.22.2" +"browserslist@npm:^4.21.10, browserslist@npm:^4.22.2": + version: 4.22.3 + resolution: "browserslist@npm:4.22.3" dependencies: - caniuse-lite: "npm:^1.0.30001565" - electron-to-chromium: "npm:^1.4.601" + caniuse-lite: "npm:^1.0.30001580" + electron-to-chromium: "npm:^1.4.648" node-releases: "npm:^2.0.14" update-browserslist-db: "npm:^1.0.13" bin: browserslist: cli.js - checksum: e3590793db7f66ad3a50817e7b7f195ce61e029bd7187200244db664bfbe0ac832f784e4f6b9c958aef8ea4abe001ae7880b7522682df521f4bc0a5b67660b5e + checksum: d46a906c79dfe95d9702c020afbe5b7b4dbe2019b85432e7a020326adff27e63e3c0a52dc8d4e73247060bbe2c13f000714741903cf96a16baae9c216dc74c75 languageName: node linkType: hard @@ -6332,8 +6273,8 @@ __metadata: linkType: hard "cacache@npm:^18.0.0": - version: 18.0.1 - resolution: "cacache@npm:18.0.1" + version: 18.0.2 + resolution: "cacache@npm:18.0.2" dependencies: "@npmcli/fs": "npm:^3.1.0" fs-minipass: "npm:^3.0.0" @@ -6347,7 +6288,7 @@ __metadata: ssri: "npm:^10.0.0" tar: "npm:^6.1.11" unique-filename: "npm:^3.0.0" - checksum: aecafd368fbfb2fc0cda1f2f831fe5a1d8161d2121317c92ac089bcd985085e8a588e810b4471e69946f91c6d2661849400e963231563c519aa1e3dac2cf6187 + checksum: 5ca58464f785d4d64ac2019fcad95451c8c89bea25949f63acd8987fcc3493eaef1beccc0fa39e673506d879d3fc1ab420760f8a14f8ddf46ea2d121805a5e96 languageName: node linkType: hard @@ -6427,23 +6368,23 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001565": - version: 1.0.30001571 - resolution: "caniuse-lite@npm:1.0.30001571" - checksum: 04f53b9a74776c9214476314613af95c62c43a9ddbc2ae555e176e896cc312110f9b74683f278fd07b8b83ab8ef6bee87b88f466df6ae560461a117fbd678b69 +"caniuse-lite@npm:^1.0.30001580": + version: 1.0.30001580 + resolution: "caniuse-lite@npm:1.0.30001580" + checksum: b626d25d792c766383a47c9efe1384f7d3c3b23e0ee70bf121e8b3a628806a96a21def16a44e58b75f4a5e23b5e008f51c2cc1e8be477b8c8d9493dcc170dd0e languageName: node linkType: hard -"chai@npm:5.0.0": - version: 5.0.0 - resolution: "chai@npm:5.0.0" +"chai@npm:5.0.3": + version: 5.0.3 + resolution: "chai@npm:5.0.3" dependencies: assertion-error: "npm:^2.0.1" check-error: "npm:^2.0.0" deep-eql: "npm:^5.0.1" - loupe: "npm:^3.0.0" + loupe: "npm:^3.1.0" pathval: "npm:^2.0.0" - checksum: c23d1bb3912cc36d12861d7e4010bb992aabf923a8d393bc71e776218c39f5f40778ab9f398ff962dd26857edee07ce732c9ad28a678feb1b76f99384d2e4a90 + checksum: d9b2bb0e4591b4a73ea98bb7a6423c4e50dd35f43ce4404be26126796af970d844269bb0b07b21866620c542f07957003b34c5cf60095e5a84878af6b120cc2d languageName: node linkType: hard @@ -6991,13 +6932,13 @@ __metadata: languageName: node linkType: hard -"cookies@npm:~0.8.0": - version: 0.8.0 - resolution: "cookies@npm:0.8.0" +"cookies@npm:~0.9.0": + version: 0.9.1 + resolution: "cookies@npm:0.9.1" dependencies: depd: "npm:~2.0.0" keygrip: "npm:~1.1.0" - checksum: 5da4d72ba81c2740511751ac8ea9506e10e2366b9ad3360333581e4667fd8d063d02c5be0bef16177de3e366b8128ed2b72921e2952c79cbca084d177e529bba + checksum: 4816461a38d907b20f3fb7a2bc4741fe580e7a195f3e248ef7025cb3be56a07638a0f4e72553a5f535554ca30172c8a3245c63ac72c9737cec034e9a47773392 languageName: node linkType: hard @@ -7018,19 +6959,19 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.33.1": - version: 3.34.0 - resolution: "core-js-compat@npm:3.34.0" +"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.34.0": + version: 3.35.1 + resolution: "core-js-compat@npm:3.35.1" dependencies: browserslist: "npm:^4.22.2" - checksum: e29571cc524b4966e331b5876567f13c2b82ed48ac9b02784f3156b29ee1cd82fe3e60052d78b017c429eb61969fd238c22684bb29180908d335266179a29155 + checksum: 9a153c66591e23703e182b258ec6bdaff0a7c578dc5f9ac152fdfef2d09e8ec277f192e28d4634a8b576c8e1a6d3b1ac76ff6b8776e72b71b334e609e177a05e languageName: node linkType: hard -"core-js@npm:3.34.0": - version: 3.34.0 - resolution: "core-js@npm:3.34.0" - checksum: 054474ab6a0a08a2277ca2c1c953e5789c562bbe144f6a43786b0f4167b4a76c671833bd0a112e275e1d99d84fa157e64814ff23aa01532e08e3b46403d7f7f4 +"core-js@npm:3.35.1": + version: 3.35.1 + resolution: "core-js@npm:3.35.1" + checksum: 5d31f22eb05cf66bd1a2088a04b7106faa5d0b91c1ffa5d72c5203e4974c31bd7e11969297f540a806c00c74c23991eaad5639592df8b5dbe4412fff3c075cd5 languageName: node linkType: hard @@ -7443,13 +7384,6 @@ __metadata: languageName: node linkType: hard -"dns-equal@npm:^1.0.0": - version: 1.0.0 - resolution: "dns-equal@npm:1.0.0" - checksum: c4f55af6f13536de39ebcfa15f504a5678d4fc2cf37b76fd41e73aa46dbd1fa596c9468c0c929aeb248ec443cb217fde949942c513312acf93c76cf783276617 - languageName: node - linkType: hard - "dns-packet@npm:^5.2.2": version: 5.6.1 resolution: "dns-packet@npm:5.6.1" @@ -7552,10 +7486,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.601": - version: 1.4.616 - resolution: "electron-to-chromium@npm:1.4.616" - checksum: 7793eda8ebfb66621300339fe830bc2b1658530b9e295a7aa37ef7fc1ca7defab4070cf407977f9112d784004a8e2efdcceb793d7e0a81096a7eb06c844db0ba +"electron-to-chromium@npm:^1.4.648": + version: 1.4.648 + resolution: "electron-to-chromium@npm:1.4.648" + checksum: a18f06bafce9017ac7b587f76dac77063a0beb7dfcdf9d5971f72b322f56af6315e4fc3c59154a260a9188c168ac7632538797d57a8c53ab57025ace0c9441f2 languageName: node linkType: hard @@ -8720,12 +8654,12 @@ __metadata: linkType: hard "follow-redirects@npm:^1.0.0": - version: 1.15.3 - resolution: "follow-redirects@npm:1.15.3" + version: 1.15.5 + resolution: "follow-redirects@npm:1.15.5" peerDependenciesMeta: debug: optional: true - checksum: 60d98693f4976892f8c654b16ef6d1803887a951898857ab0cdc009570b1c06314ad499505b7a040ac5b98144939f8597766e5e6a6859c0945d157b473aa6f5f + checksum: d467f13c1c6aa734599b8b369cd7a625b20081af358f6204ff515f6f4116eb440de9c4e0c49f10798eeb0df26c95dd05d5e0d9ddc5786ab1a8a8abefe92929b4 languageName: node linkType: hard @@ -9414,7 +9348,7 @@ __metadata: languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.0": +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.1": version: 1.0.1 resolution: "has-property-descriptors@npm:1.0.1" dependencies: @@ -9503,10 +9437,10 @@ __metadata: languageName: node linkType: hard -"hls.js@npm:1.4.14": - version: 1.4.14 - resolution: "hls.js@npm:1.4.14" - checksum: 9b4bbb379022da6a0c923d351c4592aac3be271d7458804ee7beaf6fa61f58a2538f16b5b23e180c7d636aa033ba373a32829b803dd654627fac5b647fac9e67 +"hls.js@npm:1.5.2": + version: 1.5.2 + resolution: "hls.js@npm:1.5.2" + checksum: bf92e8f005eb4e906bc19a0baf6428df4cbfd344b517de70496b953d77f1231324d26b4dc88c375d3cd481311601c1d1d5aea34ec4e0fc96e9df22b9b0e28a84 languageName: node linkType: hard @@ -9514,31 +9448,31 @@ __metadata: version: 0.0.0-use.local resolution: "home-assistant-frontend@workspace:." dependencies: - "@babel/core": "npm:7.23.6" - "@babel/helper-define-polyfill-provider": "npm:0.4.4" - "@babel/plugin-proposal-decorators": "npm:7.23.6" - "@babel/plugin-transform-runtime": "npm:7.23.6" - "@babel/preset-env": "npm:7.23.6" + "@babel/core": "npm:7.23.9" + "@babel/helper-define-polyfill-provider": "npm:0.5.0" + "@babel/plugin-proposal-decorators": "npm:7.23.9" + "@babel/plugin-transform-runtime": "npm:7.23.9" + "@babel/preset-env": "npm:7.23.9" "@babel/preset-typescript": "npm:7.23.3" - "@babel/runtime": "npm:7.23.6" + "@babel/runtime": "npm:7.23.9" "@braintree/sanitize-url": "npm:7.0.0" - "@bundle-stats/plugin-webpack-filter": "npm:4.8.3" - "@codemirror/autocomplete": "npm:6.11.1" - "@codemirror/commands": "npm:6.3.2" - "@codemirror/language": "npm:6.9.3" + "@bundle-stats/plugin-webpack-filter": "npm:4.9.2" + "@codemirror/autocomplete": "npm:6.12.0" + "@codemirror/commands": "npm:6.3.3" + "@codemirror/language": "npm:6.10.0" "@codemirror/legacy-modes": "npm:6.3.3" "@codemirror/search": "npm:6.5.5" - "@codemirror/state": "npm:6.3.3" - "@codemirror/view": "npm:6.22.3" + "@codemirror/state": "npm:6.4.0" + "@codemirror/view": "npm:6.23.1" "@egjs/hammerjs": "npm:2.0.17" - "@formatjs/intl-datetimeformat": "npm:6.12.0" - "@formatjs/intl-displaynames": "npm:6.6.4" + "@formatjs/intl-datetimeformat": "npm:6.12.2" + "@formatjs/intl-displaynames": "npm:6.6.6" "@formatjs/intl-getcanonicallocales": "npm:2.3.0" - "@formatjs/intl-listformat": "npm:7.5.3" - "@formatjs/intl-locale": "npm:3.4.3" - "@formatjs/intl-numberformat": "npm:8.9.0" - "@formatjs/intl-pluralrules": "npm:5.2.10" - "@formatjs/intl-relativetimeformat": "npm:11.2.10" + "@formatjs/intl-listformat": "npm:7.5.5" + "@formatjs/intl-locale": "npm:3.4.5" + "@formatjs/intl-numberformat": "npm:8.10.0" + "@formatjs/intl-pluralrules": "npm:5.2.12" + "@formatjs/intl-relativetimeformat": "npm:11.2.12" "@fullcalendar/core": "npm:6.1.10" "@fullcalendar/daygrid": "npm:6.1.10" "@fullcalendar/interaction": "npm:6.1.10" @@ -9550,9 +9484,9 @@ __metadata: "@lit-labs/context": "npm:0.4.1" "@lit-labs/motion": "npm:1.0.6" "@lit-labs/observers": "npm:2.0.2" - "@lit-labs/virtualizer": "npm:2.0.11" + "@lit-labs/virtualizer": "npm:2.0.12" "@lokalise/node-api": "npm:12.1.0" - "@lrnwebcomponents/simple-tooltip": "npm:7.0.18" + "@lrnwebcomponents/simple-tooltip": "npm:8.0.0" "@material/chips": "npm:=14.0.0-canary.53b3cad2f.0" "@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0" "@material/mwc-base": "npm:0.27.0" @@ -9578,14 +9512,13 @@ __metadata: "@material/mwc-top-app-bar": "npm:0.27.0" "@material/mwc-top-app-bar-fixed": "npm:0.27.0" "@material/top-app-bar": "npm:=14.0.0-canary.53b3cad2f.0" - "@material/web": "npm:=1.1.1" + "@material/web": "npm:=1.2.0" "@mdi/js": "npm:7.4.47" "@mdi/svg": "npm:7.4.47" "@octokit/auth-oauth-device": "npm:6.0.1" "@octokit/plugin-retry": "npm:6.0.1" "@octokit/rest": "npm:20.0.2" "@open-wc/dev-server-hmr": "npm:0.1.4" - "@polymer/paper-input": "npm:3.2.1" "@polymer/paper-item": "npm:3.0.1" "@polymer/paper-listbox": "npm:3.0.1" "@polymer/paper-tabs": "npm:3.1.0" @@ -9598,25 +9531,25 @@ __metadata: "@rollup/plugin-replace": "npm:5.0.5" "@thomasloven/round-slider": "npm:0.6.0" "@types/babel__plugin-transform-runtime": "npm:7.9.5" - "@types/chromecast-caf-receiver": "npm:6.0.12" + "@types/chromecast-caf-receiver": "npm:6.0.13" "@types/chromecast-caf-sender": "npm:1.0.8" "@types/glob": "npm:8.1.0" "@types/html-minifier-terser": "npm:7.0.2" "@types/js-yaml": "npm:4.0.9" "@types/leaflet": "npm:1.9.8" "@types/leaflet-draw": "npm:1.0.11" - "@types/luxon": "npm:3.3.7" + "@types/luxon": "npm:3.4.2" "@types/mocha": "npm:10.0.6" "@types/qrcode": "npm:1.5.5" "@types/serve-handler": "npm:6.1.4" "@types/sortablejs": "npm:1.15.7" - "@types/tar": "npm:6.1.10" + "@types/tar": "npm:6.1.11" "@types/ua-parser-js": "npm:0.7.39" "@types/webspeechapi": "npm:0.0.29" - "@typescript-eslint/eslint-plugin": "npm:6.16.0" - "@typescript-eslint/parser": "npm:6.16.0" - "@vaadin/combo-box": "npm:24.3.2" - "@vaadin/vaadin-themable-mixin": "npm:24.3.2" + "@typescript-eslint/eslint-plugin": "npm:6.19.1" + "@typescript-eslint/parser": "npm:6.19.1" + "@vaadin/combo-box": "npm:24.3.4" + "@vaadin/vaadin-themable-mixin": "npm:24.3.4" "@vibrant/color": "npm:3.2.1-alpha.1" "@vibrant/core": "npm:3.2.1-alpha.1" "@vibrant/quantizer-mmcq": "npm:3.2.1-alpha.1" @@ -9628,10 +9561,10 @@ __metadata: app-datepicker: "npm:5.1.1" babel-loader: "npm:9.1.3" babel-plugin-template-html-minifier: "npm:4.1.0" - chai: "npm:5.0.0" + chai: "npm:5.0.3" chart.js: "npm:4.4.1" comlink: "npm:4.4.1" - core-js: "npm:3.34.0" + core-js: "npm:3.35.1" cropperjs: "npm:1.6.1" date-fns: "npm:2.30.0" date-fns-tz: "npm:2.0.0" @@ -9661,32 +9594,32 @@ __metadata: gulp-merge-json: "npm:2.1.2" gulp-rename: "npm:2.0.0" gulp-zopfli-green: "npm:6.0.1" - hls.js: "npm:1.4.14" + hls.js: "npm:1.5.2" home-assistant-js-websocket: "npm:9.1.0" html-minifier-terser: "npm:7.2.0" - husky: "npm:8.0.3" + husky: "npm:9.0.6" idb-keyval: "npm:6.2.1" instant-mocha: "npm:1.5.2" - intl-messageformat: "npm:10.5.8" + intl-messageformat: "npm:10.5.11" js-yaml: "npm:4.1.0" jszip: "npm:3.10.1" leaflet: "npm:1.9.4" leaflet-draw: "npm:1.0.4" lint-staged: "npm:15.2.0" lit: "npm:2.8.0" - lit-analyzer: "npm:2.0.2" + lit-analyzer: "npm:2.0.3" lodash.template: "npm:4.5.0" luxon: "npm:3.4.4" magic-string: "npm:0.30.5" map-stream: "npm:0.0.7" - marked: "npm:11.1.1" + marked: "npm:11.2.0" memoize-one: "npm:6.0.0" mocha: "npm:10.2.0" node-vibrant: "npm:3.2.1-alpha.1" object-hash: "npm:3.0.0" - open: "npm:10.0.2" + open: "npm:10.0.3" pinst: "npm:3.0.0" - prettier: "npm:3.1.1" + prettier: "npm:3.2.4" proxy-polyfill: "npm:0.3.2" punycode: "npm:2.3.1" qr-scanner: "npm:1.4.2" @@ -9699,15 +9632,16 @@ __metadata: rrule: "npm:2.8.1" serve-handler: "npm:6.1.5" sinon: "npm:17.0.1" - sortablejs: "npm:1.15.1" + sortablejs: "npm:1.15.2" source-map-url: "npm:0.4.1" stacktrace-js: "npm:2.0.2" superstruct: "npm:1.0.3" - systemjs: "npm:6.14.2" + systemjs: "npm:6.14.3" tar: "npm:6.2.0" terser-webpack-plugin: "npm:5.3.10" tinykeys: "npm:2.1.0" - ts-lit-plugin: "npm:2.0.1" + transform-async-modules-webpack-plugin: "npm:1.0.2" + ts-lit-plugin: "npm:2.0.2" tsparticles-engine: "npm:2.12.0" tsparticles-preset-links: "npm:2.12.0" typescript: "npm:5.3.3" @@ -9719,7 +9653,7 @@ __metadata: vis-network: "npm:9.1.9" vue: "npm:2.7.16" vue2-daterange-picker: "npm:0.6.8" - webpack: "npm:5.89.0" + webpack: "npm:5.90.0" webpack-cli: "npm:5.1.4" webpack-dev-server: "npm:4.15.1" webpack-manifest-plugin: "npm:5.0.0" @@ -9945,12 +9879,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:8.0.3": - version: 8.0.3 - resolution: "husky@npm:8.0.3" +"husky@npm:9.0.6": + version: 9.0.6 + resolution: "husky@npm:9.0.6" bin: - husky: lib/bin.js - checksum: b754cf70fdc97c3b60fec5b80056b9c11436464953b1691bf2b5dcf0081fb6685d2c5f47abb8b2b1c49f504aabea5321fdd6496f8b755d9f6e7525a493406abb + husky: bin.js + checksum: e198c90a59d460cf860c33e0a4c3927ecfb645d4fd4c2de3fbcd5fb56b858a923af452508d549f6ed020bb48de08290912cd77c006dd2a83e551c24c17340d5b languageName: node linkType: hard @@ -10132,15 +10066,15 @@ __metadata: languageName: node linkType: hard -"intl-messageformat@npm:10.5.8": - version: 10.5.8 - resolution: "intl-messageformat@npm:10.5.8" +"intl-messageformat@npm:10.5.11": + version: 10.5.11 + resolution: "intl-messageformat@npm:10.5.11" dependencies: - "@formatjs/ecma402-abstract": "npm:1.18.0" + "@formatjs/ecma402-abstract": "npm:1.18.2" "@formatjs/fast-memoize": "npm:2.2.0" - "@formatjs/icu-messageformat-parser": "npm:2.7.3" + "@formatjs/icu-messageformat-parser": "npm:2.7.6" tslib: "npm:^2.4.0" - checksum: b2c420600d0b04929b6d401cab0669ec4815d63c3096d1a4a260d5851924dcd4c218771fc3adefc7fdfcc7418bb9353c344451fb98c72559be0a271eea602496 + checksum: 2146f4d3e2c4bcf2c4fa343e4ee070fe1124d3821caa2fa0e7112a68fdefbedbbda6a3778f3ba04e38bbce3db33511ca9eecbb0a7e06013e6699255c153813ce languageName: node linkType: hard @@ -10724,13 +10658,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:0.0.1": - version: 0.0.1 - resolution: "isarray@npm:0.0.1" - checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4 - languageName: node - linkType: hard - "isarray@npm:1.0.0, isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -10982,10 +10909,10 @@ __metadata: languageName: node linkType: hard -"just-extend@npm:^4.0.2": - version: 4.2.1 - resolution: "just-extend@npm:4.2.1" - checksum: 375389c0847d56300873fa622fbc5c5e208933e372bbedb39c82f583299cdad4fe9c4773bc35fcd9c42cd85744f07474ca4163aa0f9125dd5be37bc09075eb49 +"just-extend@npm:^6.2.0": + version: 6.2.0 + resolution: "just-extend@npm:6.2.0" + checksum: 1f487b074b9e5773befdd44dc5d1b446f01f24f7d4f1f255d51c0ef7f686e8eb5f95d983b792b9ca5c8b10cd7e60a924d64103725759eddbd7f18bcb22743f92 languageName: node linkType: hard @@ -11094,14 +11021,14 @@ __metadata: linkType: hard "koa@npm:^2.13.0": - version: 2.14.2 - resolution: "koa@npm:2.14.2" + version: 2.15.0 + resolution: "koa@npm:2.15.0" dependencies: accepts: "npm:^1.3.5" cache-content-type: "npm:^1.0.0" content-disposition: "npm:~0.5.2" content-type: "npm:^1.0.4" - cookies: "npm:~0.8.0" + cookies: "npm:~0.9.0" debug: "npm:^4.3.2" delegates: "npm:^1.0.0" depd: "npm:^2.0.0" @@ -11120,7 +11047,7 @@ __metadata: statuses: "npm:^1.5.0" type-is: "npm:^1.6.16" vary: "npm:^1.1.2" - checksum: be3592ad2ed61068aaf19f4957de07602c4141cb33e049727d2453010d90cb9f4756c87103c9b44e24cf31f061116f4479b0a4ce7638c96fd64652f559ad5b7b + checksum: 8063140a80f274f5075880cb4b19bdc7e7ce34a590fc4a7b6ae9c6876f93cfe099276769767419da5d74c1a74ead63437ecc90e9bc23edce9236656743d27350 languageName: node linkType: hard @@ -11291,9 +11218,9 @@ __metadata: languageName: node linkType: hard -"lit-analyzer@npm:2.0.2, lit-analyzer@npm:^2.0.1": - version: 2.0.2 - resolution: "lit-analyzer@npm:2.0.2" +"lit-analyzer@npm:2.0.3, lit-analyzer@npm:^2.0.1": + version: 2.0.3 + resolution: "lit-analyzer@npm:2.0.3" dependencies: "@vscode/web-custom-data": "npm:^0.4.2" chalk: "npm:^2.4.2" @@ -11306,7 +11233,7 @@ __metadata: web-component-analyzer: "npm:^2.0.0" bin: lit-analyzer: cli.js - checksum: 247de26c9de2a340d9bf0d82ce87c8ff1a32c399111c5e66485e6831e204d3e3393614d7a430b24869c392861a075dba581647185c66c4febec9cebf4c3433e8 + checksum: 596bb3463fa7d0da28d156ae3e2240f721273950a5acae09e6d4a4f339c87bce9d7d43b64cea6d7a0f55fcf75a03435f26b330576ccba23078ec461e50d31238 languageName: node linkType: hard @@ -11527,12 +11454,12 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^3.0.0": - version: 3.0.2 - resolution: "loupe@npm:3.0.2" +"loupe@npm:^3.1.0": + version: 3.1.0 + resolution: "loupe@npm:3.1.0" dependencies: get-func-name: "npm:^2.0.1" - checksum: 256467bf10afaca4a5dd79b32e36fcd042bc2a247232e940c62bcc07cb114c2d7a549218eb103598e7cdb8bd32c6601fe230d80e03b8d49782973d4da11c08ed + checksum: 48d1ad60a51f084430a7b9a3772434e0140e2d8a9ed422f13a632511452a29762695876ff97431c06eea4e248d42674a0f69b8e545d97f63fa6115adc7983b94 languageName: node linkType: hard @@ -11655,12 +11582,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:11.1.1": - version: 11.1.1 - resolution: "marked@npm:11.1.1" +"marked@npm:11.2.0": + version: 11.2.0 + resolution: "marked@npm:11.2.0" bin: marked: bin/marked.js - checksum: c2e15a330ac75cca2e12e25aae09985a78ad7e96a84418964dcdd3ee776764a38812dc0e94e9fcbacac43113d1650ca7946f9dc0bab800d72181e56a37e7631e + checksum: 0c8c0d263617a04f066db6f5adfed811a8eb78a685850d4d0b8b9ef351e416fb871813ea7ee7f94f4d5f67da98c3a2a4259b8684e4f93a6a673733fe9d9f2868 languageName: node linkType: hard @@ -12154,15 +12081,15 @@ __metadata: linkType: hard "nise@npm:^5.1.5": - version: 5.1.5 - resolution: "nise@npm:5.1.5" + version: 5.1.7 + resolution: "nise@npm:5.1.7" dependencies: - "@sinonjs/commons": "npm:^2.0.0" - "@sinonjs/fake-timers": "npm:^10.0.2" - "@sinonjs/text-encoding": "npm:^0.7.1" - just-extend: "npm:^4.0.2" - path-to-regexp: "npm:^1.7.0" - checksum: c6afe82b919a2c1985916d5bb3a738a7b2cfb017a6ab9479ec1ede62343051b40da88a1321517bb5d912c13e08b8d9ce9cdef9583edeb44d640af7273c35ebf2 + "@sinonjs/commons": "npm:^3.0.0" + "@sinonjs/fake-timers": "npm:^11.2.2" + "@sinonjs/text-encoding": "npm:^0.7.2" + just-extend: "npm:^6.2.0" + path-to-regexp: "npm:^6.2.1" + checksum: 4754e3ae52654f66e947d44d0dd40ae823e594c201474ad7c5115acb2188c839c9b8617504327051857aea8042befac946e82918e1e53b99350cb275140332d0 languageName: node linkType: hard @@ -12524,15 +12451,15 @@ __metadata: languageName: node linkType: hard -"open@npm:10.0.2": - version: 10.0.2 - resolution: "open@npm:10.0.2" +"open@npm:10.0.3": + version: 10.0.3 + resolution: "open@npm:10.0.3" dependencies: default-browser: "npm:^5.2.1" define-lazy-prop: "npm:^3.0.0" is-inside-container: "npm:^1.0.0" is-wsl: "npm:^3.1.0" - checksum: 6f7f9e08204af00930f2998690293df1a919a61c98b225ff9e3aa09a765254b8a98bec101644ffe991452c6aabea0c6f9e49670b559d48a44bfc5238f6b58351 + checksum: 4dc757ad1d3d63490822f991e9cbe3a7c05b7249fca2eaa571cb7d191e5cec88bc37e15d8ef4fd740d8989a288b661d8da253caa8d98e8c97430ddbbb0ae4ed1 languageName: node linkType: hard @@ -12927,12 +12854,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^1.7.0": - version: 1.8.0 - resolution: "path-to-regexp@npm:1.8.0" - dependencies: - isarray: "npm:0.0.1" - checksum: 45a01690f72919163cf89714e31a285937b14ad54c53734c826363fcf7beba9d9d0f2de802b4986b1264374562d6a3398a2e5289753a764e3a256494f1e52add +"path-to-regexp@npm:^6.2.1": + version: 6.2.1 + resolution: "path-to-regexp@npm:6.2.1" + checksum: 1e266be712d1a08086ee77beab12a1804842ec635dfed44f9ee1ba960a0e01cec8063fb8c92561115cdc0ce73158cdc7766e353ffa039340b4a85b370084c4d4 languageName: node linkType: hard @@ -13117,13 +13042,13 @@ __metadata: linkType: hard "postcss@npm:^8.4.14": - version: 8.4.32 - resolution: "postcss@npm:8.4.32" + version: 8.4.33 + resolution: "postcss@npm:8.4.33" dependencies: nanoid: "npm:^3.3.7" picocolors: "npm:^1.0.0" source-map-js: "npm:^1.0.2" - checksum: 28084864122f29148e1f632261c408444f5ead0e0b9ea9bd9729d0468818ebe73fe5dc0075acd50c1365dbe639b46a79cba27d355ec857723a24bc9af0f18525 + checksum: e22a4594c255f26117f38419fb494d7ecab0f596cd409f7aadc8a6173abf180ed7ea970cd13fd366ab12b5840be901d2a09b25197700c2ebcb5a8077326bf519 languageName: node linkType: hard @@ -13141,12 +13066,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:3.1.1": - version: 3.1.1 - resolution: "prettier@npm:3.1.1" +"prettier@npm:3.2.4": + version: 3.2.4 + resolution: "prettier@npm:3.2.4" bin: prettier: bin/prettier.cjs - checksum: 26a249f321b97d26c04483f1bf2eeb22e082a76f4222a2c922bebdc60111691aad4ec3979610e83942e0b956058ec361d9e9c81c185172264eb6db9aa678082b + checksum: e2b735d0552501b3a7ac8bd3ba3b6de2920bb35bd4cd02d08cb9057ebe3e96d83b9a7e4b903d987b7530a50223b12c74d107c154337236ae2c68156ba1e65cd2 languageName: node linkType: hard @@ -13908,14 +13833,14 @@ __metadata: linkType: hard "safe-array-concat@npm:^1.0.1": - version: 1.0.1 - resolution: "safe-array-concat@npm:1.0.1" + version: 1.1.0 + resolution: "safe-array-concat@npm:1.1.0" dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.1" + call-bind: "npm:^1.0.5" + get-intrinsic: "npm:^1.2.2" has-symbols: "npm:^1.0.3" isarray: "npm:^2.0.5" - checksum: 44f073d85ca12458138e6eff103ac63cec619c8261b6579bd2fa3ae7b6516cf153f02596d68e40c5bbe322a29c930017800efff652734ddcb8c0f33b2a71f89c + checksum: 41ac35ce46c44e2e8637b1805b0697d5269507779e3082b7afb92c01605fd73ab813bbc799510c56e300cfc941b1447fd98a338205db52db7fd1322ab32d7c9f languageName: node linkType: hard @@ -13934,13 +13859,13 @@ __metadata: linkType: hard "safe-regex-test@npm:^1.0.0": - version: 1.0.0 - resolution: "safe-regex-test@npm:1.0.0" + version: 1.0.2 + resolution: "safe-regex-test@npm:1.0.2" dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.1.3" + call-bind: "npm:^1.0.5" + get-intrinsic: "npm:^1.2.2" is-regex: "npm:^1.1.4" - checksum: c7248dfa07891aa634c8b9c55da696e246f8589ca50e7fd14b22b154a106e83209ddf061baf2fa45ebfbd485b094dc7297325acfc50724de6afe7138451b42a9 + checksum: 0e6a472caa8f44a502c7842ea19749de42c2eb1b41cb00456061dc3746cf3468e907522f56e97a15f3b41d88f660bd3d4f9bdec064a39895f7babae0f7aafc6a languageName: node linkType: hard @@ -14085,11 +14010,11 @@ __metadata: linkType: hard "serialize-javascript@npm:^6.0.1": - version: 6.0.1 - resolution: "serialize-javascript@npm:6.0.1" + version: 6.0.2 + resolution: "serialize-javascript@npm:6.0.2" dependencies: randombytes: "npm:^2.1.0" - checksum: f756b1ff34b655b2183c64dd6683d28d4d9b9a80284b264cac9fd421c73890491eafd6c5c2bbe93f1f21bf78b572037c5a18d24b044c317ee1c9dc44d22db94c + checksum: 445a420a6fa2eaee4b70cbd884d538e259ab278200a2ededd73253ada17d5d48e91fb1f4cd224a236ab62ea7ba0a70c6af29fc93b4f3d3078bf7da1c031fde58 languageName: node linkType: hard @@ -14144,14 +14069,15 @@ __metadata: linkType: hard "set-function-length@npm:^1.1.1": - version: 1.1.1 - resolution: "set-function-length@npm:1.1.1" + version: 1.2.0 + resolution: "set-function-length@npm:1.2.0" dependencies: define-data-property: "npm:^1.1.1" - get-intrinsic: "npm:^1.2.1" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.2" gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.0" - checksum: 745ed1d7dc69a6185e0820082fe73838ab3dfd01e75cce83a41e4c1d68bbf34bc5fb38f32ded542ae0b557536b5d2781594499b5dcd19e7db138e06292a76c7b + has-property-descriptors: "npm:^1.0.1" + checksum: 6d609cd060c488d7d2178a5d4c3689f8a6afa26fa4c48ff4a0516664ff9b84c1c0898915777f5628092dab55c4fcead205525e2edd15c659423bf86f790fdcae languageName: node linkType: hard @@ -14379,10 +14305,17 @@ __metadata: languageName: node linkType: hard -"sortablejs@npm:1.15.1": - version: 1.15.1 - resolution: "sortablejs@npm:1.15.1" - checksum: c4ccbc60e7936321eee0e07448e04b1f4481fed727ed1ace86866f49f9929e3acda2b7b825894e7ece5b8bc41f3c135eb4d00d09a287de1ee896d8256a0a84df +"sortablejs@npm:1.15.2": + version: 1.15.2 + resolution: "sortablejs@npm:1.15.2" + checksum: d149dd04bb05904ea20ca477d97cdfbbbd46edf7ede8c80958f2ad881dd6d7b1633cce31665992b341e0ea01fc7ef9d7571ccb7eb995baf01f9418b44e935eac + languageName: node + linkType: hard + +"sortablejs@patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch": + version: 1.15.2 + resolution: "sortablejs@patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch::version=1.15.2&hash=1591ab" + checksum: d44399e9ca660157c76b13705eaa26191f71c4bd025e2d47b9f7e50a8f9bdb7deaaa2783a8032e55f39627fa4007042bcfd62cb4bbeb2931f6a5d6ee06047e2e languageName: node linkType: hard @@ -14650,9 +14583,9 @@ __metadata: linkType: hard "stream-shift@npm:^1.0.0": - version: 1.0.1 - resolution: "stream-shift@npm:1.0.1" - checksum: 59b82b44b29ec3699b5519a49b3cedcc6db58c72fb40c04e005525dfdcab1c75c4e0c180b923c380f204bed78211b9bad8faecc7b93dece4d004c3f6ec75737b + version: 1.0.3 + resolution: "stream-shift@npm:1.0.3" + checksum: a24c0a3f66a8f9024bd1d579a533a53be283b4475d4e6b4b3211b964031447bdf6532dd1f3c2b0ad66752554391b7c62bd7ca4559193381f766534e723d50242 languageName: node linkType: hard @@ -14934,10 +14867,10 @@ __metadata: languageName: node linkType: hard -"systemjs@npm:6.14.2": - version: 6.14.2 - resolution: "systemjs@npm:6.14.2" - checksum: 5e126987ab10c3a19c3134d1ca82ab18cbf05018b396b0cc64c31c9cd561e5129f134cd0985d1987d7f82bd115a9b03182658ea76d612c58e8acfd446cda550f +"systemjs@npm:6.14.3": + version: 6.14.3 + resolution: "systemjs@npm:6.14.3" + checksum: 5d79c3b7dbd68b246fba6a9fe2372b88765c61cb1dbbd550beaaae0a4b38a1f19de302e203f2106845f959d00c1e7f9976bccf9b0527e907d5f3d8b5c6c5f61a languageName: node linkType: hard @@ -15005,7 +14938,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:5.3.10, terser-webpack-plugin@npm:^5.3.7": +"terser-webpack-plugin@npm:5.3.10, terser-webpack-plugin@npm:^5.3.10": version: 5.3.10 resolution: "terser-webpack-plugin@npm:5.3.10" dependencies: @@ -15041,8 +14974,8 @@ __metadata: linkType: hard "terser@npm:^5.0.0, terser@npm:^5.15.1, terser@npm:^5.26.0": - version: 5.26.0 - resolution: "terser@npm:5.26.0" + version: 5.27.0 + resolution: "terser@npm:5.27.0" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.8.2" @@ -15050,7 +14983,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 0282c5c065cbfa1e725d5609b99579252bc20b83cd1d75e8ab8b46d5da2c9d0fcfc453a12624f2d2d4c1240bfa0017a90fcf1e3b88258e5842fca1b0b82be8d8 + checksum: 9b2c5cb00747dea5994034ca064fb3cc7efc1be6b79a35247662d51ab43bdbe9cbf002bbf29170b5f3bd068c811d0212e22d94acd2cf0d8562687b96f1bffc9f languageName: node linkType: hard @@ -15243,6 +15176,18 @@ __metadata: languageName: node linkType: hard +"transform-async-modules-webpack-plugin@npm:1.0.2": + version: 1.0.2 + resolution: "transform-async-modules-webpack-plugin@npm:1.0.2" + dependencies: + "@babel/core": "npm:^7.0.0" + "@babel/preset-env": "npm:^7.0.0" + peerDependencies: + webpack: ^5.0.0 + checksum: 645df77c31e42f8ae57d86dfa023f67e4a3854f3b0848a9fd28392cb83d91fc05762150b909d01ad570e867b9e16e7e49c88a183d2dc7911d0e0154aaac21d52 + languageName: node + linkType: hard + "ts-api-utils@npm:^1.0.1": version: 1.0.3 resolution: "ts-api-utils@npm:1.0.3" @@ -15252,13 +15197,13 @@ __metadata: languageName: node linkType: hard -"ts-lit-plugin@npm:2.0.1": - version: 2.0.1 - resolution: "ts-lit-plugin@npm:2.0.1" +"ts-lit-plugin@npm:2.0.2": + version: 2.0.2 + resolution: "ts-lit-plugin@npm:2.0.2" dependencies: lit-analyzer: "npm:^2.0.1" web-component-analyzer: "npm:^2.0.0" - checksum: c447865b13afdcdef103c939706498051190ca9d377f7c64038f22a8c2fbf2f805bf7b9ccb1824b09289b5dc8c9072538446f18a4369bee35e94813396e5816f + checksum: f355a5dc1a0c2fc3bb0e4b104596ee49a94d456a96b00b2195c0983b93027a2b2bad967e633bbe3373362d94a5edfd3c7cb101e09e4756735d16c8705b88df78 languageName: node linkType: hard @@ -16227,18 +16172,18 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.89.0": - version: 5.89.0 - resolution: "webpack@npm:5.89.0" +"webpack@npm:5.90.0": + version: 5.90.0 + resolution: "webpack@npm:5.90.0" dependencies: "@types/eslint-scope": "npm:^3.7.3" - "@types/estree": "npm:^1.0.0" + "@types/estree": "npm:^1.0.5" "@webassemblyjs/ast": "npm:^1.11.5" "@webassemblyjs/wasm-edit": "npm:^1.11.5" "@webassemblyjs/wasm-parser": "npm:^1.11.5" acorn: "npm:^8.7.1" acorn-import-assertions: "npm:^1.9.0" - browserslist: "npm:^4.14.5" + browserslist: "npm:^4.21.10" chrome-trace-event: "npm:^1.0.2" enhanced-resolve: "npm:^5.15.0" es-module-lexer: "npm:^1.2.1" @@ -16252,7 +16197,7 @@ __metadata: neo-async: "npm:^2.6.2" schema-utils: "npm:^3.2.0" tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.7" + terser-webpack-plugin: "npm:^5.3.10" watchpack: "npm:^2.4.0" webpack-sources: "npm:^3.2.3" peerDependenciesMeta: @@ -16260,7 +16205,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: ee19b070279c9bc3bf21eeaac3ea08e6583c1b8da334e595b3c9badedbd7f9fad071b9f785076081af661ef247bb72441e86e8b903bf253ae9300007a048ea6e + checksum: 7ff6286be54e00b2580274d8009b014fd03c6d8ade898434376c739e460da1f3a63a51006966024710061f440d6723813365b8a54ae6bcb93b94867c42cf017e languageName: node linkType: hard @@ -16696,8 +16641,8 @@ __metadata: linkType: hard "ws@npm:^8.13.0": - version: 8.15.1 - resolution: "ws@npm:8.15.1" + version: 8.16.0 + resolution: "ws@npm:8.16.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -16706,7 +16651,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 746a3102d43e8df7b09f5814bec858f12d10185a7abd655537f3291b687d440bb80fc9d1e082f8dee42d4d74307f78a96810e18a2c8e13053b003c6608c1c648 + checksum: 7c511c59e979bd37b63c3aea4a8e4d4163204f00bd5633c053b05ed67835481995f61a523b0ad2b603566f9a89b34cb4965cb9fab9649fbfebd8f740cea57f17 languageName: node linkType: hard