mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-18 05:57:21 +00:00
Compare commits
65 Commits
lovelace_d
...
migrate-na
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfabbf626e | ||
|
|
0e149c35a5 | ||
|
|
c90fa341bb | ||
|
|
b125cd5f3e | ||
|
|
3425837de3 | ||
|
|
24a797e46a | ||
|
|
4a486ff28b | ||
|
|
6be390a07e | ||
|
|
c93942919b | ||
|
|
ca06269a91 | ||
|
|
b3d7c0b6dc | ||
|
|
e709703f79 | ||
|
|
fd42614a23 | ||
|
|
4050dd0384 | ||
|
|
9996b1bfea | ||
|
|
8092340d2a | ||
|
|
443e205395 | ||
|
|
8c5d0c4b80 | ||
|
|
3005f12ef5 | ||
|
|
3bcf530200 | ||
|
|
af2fa98666 | ||
|
|
8f335668db | ||
|
|
51acd271ea | ||
|
|
830e9b5089 | ||
|
|
b13cb84baf | ||
|
|
f8df7f37ea | ||
|
|
54e4f4e60a | ||
|
|
81cb483163 | ||
|
|
eb5b14ea00 | ||
|
|
b221fbb387 | ||
|
|
3033bfb1fb | ||
|
|
8db967eb2f | ||
|
|
10af86cc02 | ||
|
|
dfc7116819 | ||
|
|
685c642bfc | ||
|
|
2e547937b8 | ||
|
|
e3e01c327a | ||
|
|
abd706fed0 | ||
|
|
3bd45dd29b | ||
|
|
206f067d2b | ||
|
|
caefa7530a | ||
|
|
de142e33a1 | ||
|
|
5d4c3ebfcd | ||
|
|
eb910c5ac5 | ||
|
|
72726a2e0f | ||
|
|
253b49871e | ||
|
|
6306890922 | ||
|
|
fee16ed4e4 | ||
|
|
055f6c82fb | ||
|
|
0c1627c69a | ||
|
|
780c03ece8 | ||
|
|
62dc66abd8 | ||
|
|
08aaee754e | ||
|
|
bba400443e | ||
|
|
a7a937e197 | ||
|
|
2a97913520 | ||
|
|
9f16ce7341 | ||
|
|
3e20e9b388 | ||
|
|
6891eb9ff8 | ||
|
|
f649b3783d | ||
|
|
c46f67d572 | ||
|
|
86acfa67dd | ||
|
|
3c5c19270f | ||
|
|
6db7817032 | ||
|
|
05ca8253f0 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Build resources
|
- name: Build resources
|
||||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||||
- name: Setup lint cache
|
- name: Setup lint cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
node_modules/.cache/prettier
|
node_modules/.cache/prettier
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: frontend-bundle-stats
|
name: frontend-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: supervisor-bundle-stats
|
name: supervisor-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
|
|||||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
|
uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
|
uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -57,4 +57,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
|
uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||||
|
|||||||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
- uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
process-only: "issues, prs"
|
process-only: "issues, prs"
|
||||||
|
|||||||
4
.github/workflows/nightly.yaml
vendored
4
.github/workflows/nightly.yaml
vendored
@@ -57,14 +57,14 @@ jobs:
|
|||||||
run: tar -czvf translations.tar.gz translations
|
run: tar -czvf translations.tar.gz translations
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels
|
||||||
path: dist/home_assistant_frontend*.whl
|
path: dist/home_assistant_frontend*.whl
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -56,4 +56,5 @@ test/coverage/
|
|||||||
|
|
||||||
# AI tooling
|
# AI tooling
|
||||||
.claude
|
.claude
|
||||||
|
.cursor
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { DeviceRegistryEntry } from "../../../src/data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../src/data/device/device_registry";
|
||||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
export const mockDeviceRegistry = (
|
export const mockDeviceRegistry = (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { EntityRegistryEntry } from "../../../src/data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../src/data/entity/entity_registry";
|
||||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
export const mockEntityRegistry = (
|
export const mockEntityRegistry = (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { LabelRegistryEntry } from "../../../src/data/label_registry";
|
import type { LabelRegistryEntry } from "../../../src/data/label/label_registry";
|
||||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
export const mockLabelRegistry = (
|
export const mockLabelRegistry = (
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
<div class="action">
|
<div class="action">
|
||||||
<span>
|
<span>
|
||||||
${this._action
|
${this._action
|
||||||
? describeAction(this.hass, [], [], {}, this._action)
|
? describeAction(this.hass, [], this._action)
|
||||||
: "<invalid YAML>"}
|
: "<invalid YAML>"}
|
||||||
</span>
|
</span>
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
@@ -155,7 +155,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
${ACTIONS.map(
|
${ACTIONS.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<span>${describeAction(this.hass, [], [], {}, conf as any)}</span>
|
<span>${describeAction(this.hass, [], conf as any)}</span>
|
||||||
<pre>${dump(conf)}</pre>
|
<pre>${dump(conf)}</pre>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import { computeInitialHaFormData } from "../../../../src/components/ha-form/com
|
|||||||
import "../../../../src/components/ha-form/ha-form";
|
import "../../../../src/components/ha-form/ha-form";
|
||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||||
|
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
import "../../components/demo-black-white-row";
|
import "../../components/demo-black-white-row";
|
||||||
import type { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import "../../../../src/components/ha-selector/ha-selector";
|
|||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
|
||||||
import type { BlueprintInput } from "../../../../src/data/blueprint";
|
import type { BlueprintInput } from "../../../../src/data/blueprint";
|
||||||
import type { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
|
||||||
import type { FloorRegistryEntry } from "../../../../src/data/floor_registry";
|
import type { FloorRegistryEntry } from "../../../../src/data/floor_registry";
|
||||||
import type { LabelRegistryEntry } from "../../../../src/data/label_registry";
|
import type { LabelRegistryEntry } from "../../../../src/data/label/label_registry";
|
||||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import type { IntegrationManifest } from "../../../../src/data/integration";
|
import type { IntegrationManifest } from "../../../../src/data/integration";
|
||||||
|
|
||||||
import type { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
|
||||||
import type { EntityRegistryEntry } from "../../../../src/data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../../src/data/entity/entity_registry";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import "../../../../src/panels/config/integrations/ha-config-flow-card";
|
import "../../../../src/panels/config/integrations/ha-config-flow-card";
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
20
package.json
20
package.json
@@ -34,7 +34,7 @@
|
|||||||
"@codemirror/legacy-modes": "6.5.2",
|
"@codemirror/legacy-modes": "6.5.2",
|
||||||
"@codemirror/search": "6.5.11",
|
"@codemirror/search": "6.5.11",
|
||||||
"@codemirror/state": "6.5.2",
|
"@codemirror/state": "6.5.2",
|
||||||
"@codemirror/view": "6.38.8",
|
"@codemirror/view": "6.39.4",
|
||||||
"@date-fns/tz": "1.4.1",
|
"@date-fns/tz": "1.4.1",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.18.2",
|
"@formatjs/intl-datetimeformat": "6.18.2",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"@fullcalendar/list": "6.1.19",
|
"@fullcalendar/list": "6.1.19",
|
||||||
"@fullcalendar/luxon3": "6.1.19",
|
"@fullcalendar/luxon3": "6.1.19",
|
||||||
"@fullcalendar/timegrid": "6.1.19",
|
"@fullcalendar/timegrid": "6.1.19",
|
||||||
"@home-assistant/webawesome": "3.0.0-ha.1",
|
"@home-assistant/webawesome": "3.0.0-ha.2",
|
||||||
"@lezer/highlight": "1.2.3",
|
"@lezer/highlight": "1.2.3",
|
||||||
"@lit-labs/motion": "1.0.9",
|
"@lit-labs/motion": "1.0.9",
|
||||||
"@lit-labs/observers": "2.0.6",
|
"@lit-labs/observers": "2.0.6",
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"superstruct": "2.0.2",
|
"superstruct": "2.0.2",
|
||||||
"tinykeys": "3.0.0",
|
"tinykeys": "3.0.0",
|
||||||
"ua-parser-js": "2.0.6",
|
"ua-parser-js": "2.0.7",
|
||||||
"vue": "2.7.16",
|
"vue": "2.7.16",
|
||||||
"vue2-daterange-picker": "0.6.8",
|
"vue2-daterange-picker": "0.6.8",
|
||||||
"weekstart": "2.0.0",
|
"weekstart": "2.0.0",
|
||||||
@@ -157,8 +157,8 @@
|
|||||||
"@octokit/auth-oauth-device": "8.0.3",
|
"@octokit/auth-oauth-device": "8.0.3",
|
||||||
"@octokit/plugin-retry": "8.0.3",
|
"@octokit/plugin-retry": "8.0.3",
|
||||||
"@octokit/rest": "22.0.1",
|
"@octokit/rest": "22.0.1",
|
||||||
"@rsdoctor/rspack-plugin": "1.3.12",
|
"@rsdoctor/rspack-plugin": "1.3.15",
|
||||||
"@rspack/core": "1.6.6",
|
"@rspack/core": "1.6.7",
|
||||||
"@rspack/dev-server": "1.1.4",
|
"@rspack/dev-server": "1.1.4",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.22",
|
"@types/chromecast-caf-receiver": "6.0.22",
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"browserslist-useragent-regexp": "4.1.3",
|
"browserslist-useragent-regexp": "4.1.3",
|
||||||
"del": "8.0.1",
|
"del": "8.0.1",
|
||||||
"eslint": "9.39.1",
|
"eslint": "9.39.2",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-import-resolver-webpack": "0.13.10",
|
"eslint-import-resolver-webpack": "0.13.10",
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
"gulp-rename": "2.1.0",
|
"gulp-rename": "2.1.0",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"jsdom": "27.2.0",
|
"jsdom": "27.3.0",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "16.2.7",
|
"lint-staged": "16.2.7",
|
||||||
"lit-analyzer": "2.0.3",
|
"lit-analyzer": "2.0.3",
|
||||||
@@ -214,10 +214,10 @@
|
|||||||
"serve": "14.2.5",
|
"serve": "14.2.5",
|
||||||
"sinon": "21.0.0",
|
"sinon": "21.0.0",
|
||||||
"tar": "7.5.2",
|
"tar": "7.5.2",
|
||||||
"terser-webpack-plugin": "5.3.15",
|
"terser-webpack-plugin": "5.3.16",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.48.1",
|
"typescript-eslint": "8.49.0",
|
||||||
"vite-tsconfig-paths": "5.1.4",
|
"vite-tsconfig-paths": "5.1.4",
|
||||||
"vitest": "4.0.15",
|
"vitest": "4.0.15",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
@@ -238,6 +238,6 @@
|
|||||||
},
|
},
|
||||||
"packageManager": "yarn@4.12.0",
|
"packageManager": "yarn@4.12.0",
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.21.1"
|
"node": "24.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import {
|
|||||||
DOMAIN_ATTRIBUTES_FORMATERS,
|
DOMAIN_ATTRIBUTES_FORMATERS,
|
||||||
DOMAIN_ATTRIBUTES_UNITS,
|
DOMAIN_ATTRIBUTES_UNITS,
|
||||||
TEMPERATURE_ATTRIBUTES,
|
TEMPERATURE_ATTRIBUTES,
|
||||||
} from "../../data/entity_attributes";
|
} from "../../data/entity/entity_attributes";
|
||||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||||
import type { FrontendLocaleData } from "../../data/translation";
|
import type { FrontendLocaleData } from "../../data/translation";
|
||||||
import type { WeatherEntity } from "../../data/weather";
|
import type { WeatherEntity } from "../../data/weather";
|
||||||
import { getWeatherUnit } from "../../data/weather";
|
import { getWeatherUnit } from "../../data/weather";
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||||
import type {
|
import type {
|
||||||
EntityRegistryDisplayEntry,
|
EntityRegistryDisplayEntry,
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
} from "../../data/entity_registry";
|
} from "../../data/entity/entity_registry";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { computeStateName } from "./compute_state_name";
|
|
||||||
import { getDuplicates } from "../string/get_duplicates";
|
import { getDuplicates } from "../string/get_duplicates";
|
||||||
|
import { computeStateName } from "./compute_state_name";
|
||||||
|
|
||||||
export const computeDeviceName = (
|
export const computeDeviceName = (
|
||||||
device: DeviceRegistryEntry
|
device: DeviceRegistryEntry
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import type {
|
import type {
|
||||||
EntityRegistryDisplayEntry,
|
EntityRegistryDisplayEntry,
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
} from "../../data/entity_registry";
|
} from "../../data/entity/entity_registry";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { computeDeviceName } from "./compute_device_name";
|
import { computeDeviceName } from "./compute_device_name";
|
||||||
import { computeStateName } from "./compute_state_name";
|
import { computeStateName } from "./compute_state_name";
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
import type { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||||
import type { FrontendLocaleData } from "../../data/translation";
|
import type { FrontendLocaleData } from "../../data/translation";
|
||||||
import { TimeZone } from "../../data/translation";
|
import { TimeZone } from "../../data/translation";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { DURATION_UNITS, formatDuration } from "../datetime/format_duration";
|
|
||||||
import { formatDate } from "../datetime/format_date";
|
import { formatDate } from "../datetime/format_date";
|
||||||
import { formatDateTime } from "../datetime/format_date_time";
|
import { formatDateTime } from "../datetime/format_date_time";
|
||||||
|
import { DURATION_UNITS, formatDuration } from "../datetime/format_duration";
|
||||||
import { formatTime } from "../datetime/format_time";
|
import { formatTime } from "../datetime/format_time";
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||||
import type {
|
import type {
|
||||||
EntityRegistryDisplayEntry,
|
EntityRegistryDisplayEntry,
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
ExtEntityRegistryEntry,
|
ExtEntityRegistryEntry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity/entity_registry";
|
||||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import type { HomeAssistant } from "../../types";
|
|
||||||
import type { IntegrationManifest } from "../../data/integration";
|
|
||||||
import { computeDomain } from "./compute_domain";
|
|
||||||
import { HELPERS_CRUD } from "../../data/helpers_crud";
|
|
||||||
import type { Helper } from "../../panels/config/helpers/const";
|
|
||||||
import { isHelperDomain } from "../../panels/config/helpers/const";
|
|
||||||
import { isComponentLoaded } from "../config/is_component_loaded";
|
|
||||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
|
||||||
import { removeEntityRegistryEntry } from "../../data/entity_registry";
|
|
||||||
import type { ConfigEntry } from "../../data/config_entries";
|
import type { ConfigEntry } from "../../data/config_entries";
|
||||||
import { deleteConfigEntry } from "../../data/config_entries";
|
import { deleteConfigEntry } from "../../data/config_entries";
|
||||||
|
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||||
|
import { removeEntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||||
|
import { HELPERS_CRUD } from "../../data/helpers_crud";
|
||||||
|
import type { IntegrationManifest } from "../../data/integration";
|
||||||
|
import type { Helper } from "../../panels/config/helpers/const";
|
||||||
|
import { isHelperDomain } from "../../panels/config/helpers/const";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { isComponentLoaded } from "../config/is_component_loaded";
|
||||||
|
import { computeDomain } from "./compute_domain";
|
||||||
|
|
||||||
export const isDeletableEntity = (
|
export const isDeletableEntity = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { UNAVAILABLE_STATES } from "../../data/entity/entity";
|
||||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { computeDomain } from "./compute_domain";
|
|
||||||
import { stringCompare } from "../string/compare";
|
import { stringCompare } from "../string/compare";
|
||||||
|
import { computeDomain } from "./compute_domain";
|
||||||
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
|
|
||||||
export const FIXED_DOMAIN_STATES = {
|
export const FIXED_DOMAIN_STATES = {
|
||||||
alarm_control_panel: [
|
alarm_control_panel: [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { isUnavailableState, UNAVAILABLE } from "../../data/entity/entity";
|
||||||
import { isUnavailableState, UNAVAILABLE } from "../../data/entity";
|
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
|
|
||||||
export const computeGroupEntitiesState = (states: HassEntity[]): string => {
|
export const computeGroupEntitiesState = (states: HassEntity[]): string => {
|
||||||
if (!states.length) {
|
if (!states.length) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { isUnavailableState, OFF, UNAVAILABLE } from "../../data/entity";
|
import { isUnavailableState, OFF, UNAVAILABLE } from "../../data/entity/entity";
|
||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
|
|
||||||
export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { UNAVAILABLE } from "../../data/entity";
|
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { DOMAINS_WITH_CARD } from "../const";
|
import { DOMAINS_WITH_CARD } from "../const";
|
||||||
import { canToggleState } from "./can_toggle_state";
|
import { canToggleState } from "./can_toggle_state";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/** Return a color representing a state. */
|
/** Return a color representing a state. */
|
||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { UNAVAILABLE } from "../../data/entity";
|
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||||
import type { GroupEntity } from "../../data/group";
|
import type { GroupEntity } from "../../data/group";
|
||||||
import { computeGroupDomain } from "../../data/group";
|
import { computeGroupDomain } from "../../data/group";
|
||||||
import { computeCssVariable } from "../../resources/css-variables";
|
import { computeCssVariable } from "../../resources/css-variables";
|
||||||
|
|||||||
@@ -17,25 +17,36 @@ export interface NavigateOptions {
|
|||||||
// max time to wait for dialogs to close before navigating
|
// max time to wait for dialogs to close before navigating
|
||||||
const DIALOG_WAIT_TIMEOUT = 500;
|
const DIALOG_WAIT_TIMEOUT = 500;
|
||||||
|
|
||||||
export const navigate = async (
|
/**
|
||||||
path: string,
|
* Ensures all dialogs are closed before navigation.
|
||||||
options?: NavigateOptions,
|
* Returns true if navigation can proceed, false if a dialog refused to close.
|
||||||
timestamp = Date.now()
|
*/
|
||||||
) => {
|
const ensureDialogsClosed = async (timestamp: number): Promise<boolean> => {
|
||||||
const { history } = mainWindow;
|
const { history } = mainWindow;
|
||||||
if (history.state?.dialog && Date.now() - timestamp < DIALOG_WAIT_TIMEOUT) {
|
|
||||||
const closed = await closeAllDialogs();
|
if (!history.state?.dialog || Date.now() - timestamp >= DIALOG_WAIT_TIMEOUT) {
|
||||||
if (!closed) {
|
return true;
|
||||||
// eslint-disable-next-line no-console
|
}
|
||||||
console.warn("Navigation blocked, because dialog refused to close");
|
|
||||||
return false;
|
const closed = await closeAllDialogs();
|
||||||
}
|
if (!closed) {
|
||||||
return new Promise<boolean>((resolve) => {
|
// eslint-disable-next-line no-console
|
||||||
// need to wait for history state to be updated in case a dialog was closed
|
console.warn("Navigation blocked, because dialog refused to close");
|
||||||
setTimeout(() => {
|
return false;
|
||||||
navigate(path, options, timestamp).then(resolve);
|
}
|
||||||
});
|
|
||||||
});
|
// wait for history state to be updated after dialog closed
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
setTimeout(resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ensureDialogsClosed(timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const navigate = async (path: string, options?: NavigateOptions) => {
|
||||||
|
const canProceed = await ensureDialogsClosed(Date.now());
|
||||||
|
if (!canProceed) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
const replace = options?.replace || false;
|
const replace = options?.replace || false;
|
||||||
|
|
||||||
@@ -68,10 +79,14 @@ export const navigate = async (
|
|||||||
* Navigate back in history, with fallback to a default path if no history exists.
|
* Navigate back in history, with fallback to a default path if no history exists.
|
||||||
* This prevents a user from getting stuck when they navigate directly to a page with no history.
|
* This prevents a user from getting stuck when they navigate directly to a page with no history.
|
||||||
*/
|
*/
|
||||||
export const goBack = (fallbackPath?: string) => {
|
export const goBack = async (fallbackPath?: string): Promise<void> => {
|
||||||
const { history } = mainWindow;
|
const canProceed = await ensureDialogsClosed(Date.now());
|
||||||
|
if (!canProceed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we have history to go back to
|
// Check if we have history to go back to
|
||||||
|
const { history } = mainWindow;
|
||||||
if (history.length > 1) {
|
if (history.length > 1) {
|
||||||
history.back();
|
history.back();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type {
|
|||||||
HassEntity,
|
HassEntity,
|
||||||
HassEntityAttributeBase,
|
HassEntityAttributeBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||||
import type { FrontendLocaleData } from "../../data/translation";
|
import type { FrontendLocaleData } from "../../data/translation";
|
||||||
import { NumberFormat } from "../../data/translation";
|
import { NumberFormat } from "../../data/translation";
|
||||||
import { round } from "./round";
|
import { round } from "./round";
|
||||||
|
|||||||
@@ -1,30 +1,45 @@
|
|||||||
/**
|
/**
|
||||||
* Executes a callback within a View Transition if supported, otherwise runs it directly.
|
* Executes a synchronous callback within a View Transition if supported, otherwise runs it directly.
|
||||||
*
|
*
|
||||||
* @param callback - Function to execute. Can be synchronous or return a Promise. The callback will be passed a boolean indicating whether the view transition is available.
|
* @param callback - Synchronous function to execute. The callback will be passed a boolean indicating whether the view transition is available.
|
||||||
* @returns Promise that resolves when the transition completes (or immediately if not supported)
|
* @returns Promise that resolves when the transition completes (or immediately if not supported)
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* // Synchronous callback
|
|
||||||
* withViewTransition(() => {
|
* withViewTransition(() => {
|
||||||
* this.large = !this.large;
|
* this.large = !this.large;
|
||||||
* });
|
* });
|
||||||
*
|
|
||||||
* // Async callback
|
|
||||||
* await withViewTransition(async () => {
|
|
||||||
* await this.updateData();
|
|
||||||
* });
|
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const withViewTransition = (
|
export const withViewTransition = (
|
||||||
callback: (viewTransitionAvailable: boolean) => void | Promise<void>
|
callback: (viewTransitionAvailable: boolean) => void
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (document.startViewTransition) {
|
if (!document.startViewTransition) {
|
||||||
return document.startViewTransition(() => callback(true)).finished;
|
callback(false);
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: Execute callback directly without transition
|
let callbackInvoked = false;
|
||||||
const result = callback(false);
|
|
||||||
return result instanceof Promise ? result : Promise.resolve();
|
try {
|
||||||
|
// View Transitions require DOM updates to happen synchronously within
|
||||||
|
// the callback. Execute the callback immediately (synchronously).
|
||||||
|
const transition = document.startViewTransition(() => {
|
||||||
|
callbackInvoked = true;
|
||||||
|
callback(true);
|
||||||
|
});
|
||||||
|
return transition.finished;
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
"View transition failed, falling back to direct execution.",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
// Make sure the callback is invoked exactly once.
|
||||||
|
if (!callbackInvoked) {
|
||||||
|
callback(false);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
207
src/components/chart/ha-sunburst-chart.ts
Normal file
207
src/components/chart/ha-sunburst-chart.ts
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import type { EChartsType } from "echarts/core";
|
||||||
|
import type { SunburstSeriesOption } from "echarts/types/dist/echarts";
|
||||||
|
import type { CallbackDataParams } from "echarts/types/src/util/types";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { getGraphColorByIndex } from "../../common/color/colors";
|
||||||
|
import { filterXSS } from "../../common/util/xss";
|
||||||
|
import type { ECOption } from "../../resources/echarts/echarts";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "./ha-chart-base";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/consistent-type-imports
|
||||||
|
let SunburstChart: typeof import("echarts/lib/chart/sunburst/install");
|
||||||
|
|
||||||
|
export interface SunburstNode {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
value: number;
|
||||||
|
itemStyle?: {
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
children?: SunburstNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-sunburst-chart")
|
||||||
|
export class HaSunburstChart extends LitElement {
|
||||||
|
public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public data?: SunburstNode;
|
||||||
|
|
||||||
|
@property({ type: String, attribute: false }) public valueFormatter?: (
|
||||||
|
value: number
|
||||||
|
) => string;
|
||||||
|
|
||||||
|
public chart?: EChartsType;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
if (!SunburstChart) {
|
||||||
|
import("echarts/lib/chart/sunburst/install").then((module) => {
|
||||||
|
SunburstChart = module;
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!SunburstChart || !this.data) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item",
|
||||||
|
formatter: this._renderTooltip,
|
||||||
|
appendTo: document.body,
|
||||||
|
},
|
||||||
|
} as ECOption;
|
||||||
|
|
||||||
|
return html`<ha-chart-base
|
||||||
|
.data=${this._createData(this.data)}
|
||||||
|
.options=${options}
|
||||||
|
height="100%"
|
||||||
|
.extraComponents=${[SunburstChart]}
|
||||||
|
></ha-chart-base>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderTooltip = (params: CallbackDataParams) => {
|
||||||
|
const data = params.data as { name: string; value: number };
|
||||||
|
const value = this.valueFormatter
|
||||||
|
? this.valueFormatter(data.value)
|
||||||
|
: data.value;
|
||||||
|
return `${params.marker} ${filterXSS(data.name)}<br>${value}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _createData = memoizeOne(
|
||||||
|
(data: SunburstNode): SunburstSeriesOption => {
|
||||||
|
const computedStyles = getComputedStyle(this);
|
||||||
|
|
||||||
|
// Transform to echarts format (uses 'name' instead of 'id')
|
||||||
|
const transformNode = (
|
||||||
|
node: SunburstNode,
|
||||||
|
index: number,
|
||||||
|
depth: number,
|
||||||
|
parentColor?: string
|
||||||
|
) => {
|
||||||
|
const result = {
|
||||||
|
...node,
|
||||||
|
name: node.name || node.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (depth > 0 && !node.itemStyle?.color) {
|
||||||
|
// Don't assign color to root node
|
||||||
|
result.itemStyle = {
|
||||||
|
color: parentColor ?? getGraphColorByIndex(index, computedStyles),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
result.children = node.children.map((child, i) =>
|
||||||
|
transformNode(child, i, depth + 1, result.itemStyle?.color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedData = transformNode(data, 0, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "sunburst",
|
||||||
|
data: transformedData.children || [transformedData],
|
||||||
|
radius: [0, "90%"],
|
||||||
|
sort: undefined, // Keep original order
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
align: "center",
|
||||||
|
rotate: "radial",
|
||||||
|
minAngle: 15,
|
||||||
|
hideOverlap: true,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
focus: "ancestor",
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
levels: this._generateLevels(this._getMaxDepth(data)),
|
||||||
|
} as SunburstSeriesOption;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _getMaxDepth(node: SunburstNode, currentDepth = 0): number {
|
||||||
|
if (!node.children || node.children.length === 0) {
|
||||||
|
return currentDepth;
|
||||||
|
}
|
||||||
|
return Math.max(
|
||||||
|
...node.children.map((child) =>
|
||||||
|
this._getMaxDepth(child, currentDepth + 1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _generateLevels(depth: number): SunburstSeriesOption["levels"] {
|
||||||
|
const levels: SunburstSeriesOption["levels"] = [];
|
||||||
|
|
||||||
|
// Root level (center) - transparent, small fixed size
|
||||||
|
const rootRadius = 15;
|
||||||
|
const outerRadius = 95;
|
||||||
|
const availableRadius = outerRadius - rootRadius;
|
||||||
|
|
||||||
|
levels.push({
|
||||||
|
r0: "0%",
|
||||||
|
r: `${rootRadius}%`,
|
||||||
|
itemStyle: {
|
||||||
|
color: "transparent",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (depth === 0) {
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute remaining radius among data levels using weighted distribution
|
||||||
|
// First level gets most space, each subsequent level gets progressively smaller
|
||||||
|
const weights = Array.from({ length: depth }, (_, i) => depth - i);
|
||||||
|
const totalWeight = weights.reduce((sum, w) => sum + w, 0);
|
||||||
|
|
||||||
|
let currentRadius = rootRadius;
|
||||||
|
for (let i = 0; i < depth; i++) {
|
||||||
|
const levelRadius = (weights[i] / totalWeight) * availableRadius;
|
||||||
|
const r0 = currentRadius;
|
||||||
|
const r = currentRadius + levelRadius;
|
||||||
|
currentRadius = r;
|
||||||
|
|
||||||
|
levels.push({
|
||||||
|
r0: `${r0}%`,
|
||||||
|
r: `${r}%`,
|
||||||
|
// Show labels only on first level
|
||||||
|
...(i === 0 ? { label: { show: true } } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
ha-chart-base {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-sunburst-chart": HaSunburstChart;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -184,17 +184,11 @@ export class StatisticsChart extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _datasetHidden(ev: CustomEvent) {
|
private _datasetHidden(ev: CustomEvent) {
|
||||||
if (!this._legendData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._hiddenStats.add(ev.detail.id);
|
this._hiddenStats.add(ev.detail.id);
|
||||||
this.requestUpdate("_hiddenStats");
|
this.requestUpdate("_hiddenStats");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _datasetUnhidden(ev: CustomEvent) {
|
private _datasetUnhidden(ev: CustomEvent) {
|
||||||
if (!this._legendData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._hiddenStats.delete(ev.detail.id);
|
this._hiddenStats.delete(ev.detail.id);
|
||||||
this.requestUpdate("_hiddenStats");
|
this.requestUpdate("_hiddenStats");
|
||||||
}
|
}
|
||||||
@@ -521,7 +515,9 @@ export class StatisticsChart extends LitElement {
|
|||||||
`ui.components.statistics_charts.statistic_types.${type}`
|
`ui.components.statistics_charts.statistic_types.${type}`
|
||||||
),
|
),
|
||||||
symbol: "none",
|
symbol: "none",
|
||||||
sampling: "minmax",
|
// minmax sampling operates independently per series, breaking stacking alignment
|
||||||
|
// https://github.com/apache/echarts/issues/11879
|
||||||
|
sampling: band && drawBands ? "lttb" : "minmax",
|
||||||
animationDurationUpdate: 0,
|
animationDurationUpdate: 0,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
@@ -539,10 +535,17 @@ export class StatisticsChart extends LitElement {
|
|||||||
if (band && this.chartType === "line") {
|
if (band && this.chartType === "line") {
|
||||||
series.stack = `band-${statistic_id}`;
|
series.stack = `band-${statistic_id}`;
|
||||||
series.stackStrategy = "all";
|
series.stackStrategy = "all";
|
||||||
if (drawBands && type === bandTop) {
|
if (this._hiddenStats.has(`${statistic_id}-${bandBottom}`)) {
|
||||||
(series as LineSeriesOption).areaStyle = {
|
// changing the stackOrder forces echarts to render the stacked series that are not hidden #28472
|
||||||
color: color + "3F",
|
series.stackOrder = "seriesDesc";
|
||||||
};
|
(series as LineSeriesOption).areaStyle = undefined;
|
||||||
|
} else {
|
||||||
|
series.stackOrder = "seriesAsc";
|
||||||
|
if (drawBands && type === bandTop) {
|
||||||
|
(series as LineSeriesOption).areaStyle = {
|
||||||
|
color: color + "3F",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.hideLegend) {
|
if (!this.hideLegend) {
|
||||||
@@ -586,7 +589,8 @@ export class StatisticsChart extends LitElement {
|
|||||||
} else if (
|
} else if (
|
||||||
type === bandTop &&
|
type === bandTop &&
|
||||||
this.chartType === "line" &&
|
this.chartType === "line" &&
|
||||||
drawBands
|
drawBands &&
|
||||||
|
!this._hiddenStats.has(`${statistic_id}-${bandBottom}`)
|
||||||
) {
|
) {
|
||||||
const top = stat[bandTop] || 0;
|
const top = stat[bandTop] || 0;
|
||||||
val.push(Math.abs(top - (stat[bandBottom] || 0)));
|
val.push(Math.abs(top - (stat[bandBottom] || 0)));
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { getGraphColorByIndex } from "../../common/color/colors";
|
|||||||
import { hex2rgb, lab2hex, rgb2lab } from "../../common/color/convert-color";
|
import { hex2rgb, lab2hex, rgb2lab } from "../../common/color/convert-color";
|
||||||
import { labBrighten } from "../../common/color/lab";
|
import { labBrighten } from "../../common/color/lab";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { stateColorProperties } from "../../common/entity/state_color";
|
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
|
||||||
import { computeCssValue } from "../../resources/css-variables";
|
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
||||||
|
import { stateColorProperties } from "../../common/entity/state_color";
|
||||||
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||||
|
import { computeCssValue } from "../../resources/css-variables";
|
||||||
|
|
||||||
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
||||||
media_player: {
|
media_player: {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { computeCssColor } from "../../common/color/compute-color";
|
|||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||||
import "../chips/ha-chip-set";
|
import "../chips/ha-chip-set";
|
||||||
import "../ha-dropdown";
|
import "../ha-dropdown";
|
||||||
import "../ha-dropdown-item";
|
import "../ha-dropdown-item";
|
||||||
|
|||||||
@@ -101,6 +101,10 @@ const Component = Vue.extend({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "en",
|
default: "en",
|
||||||
},
|
},
|
||||||
|
opensVertical: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -129,6 +133,11 @@ const Component = Vue.extend({
|
|||||||
},
|
},
|
||||||
expression: "dateRange",
|
expression: "dateRange",
|
||||||
},
|
},
|
||||||
|
on: {
|
||||||
|
toggle: (open: boolean) => {
|
||||||
|
fireEvent(this.$el as HTMLElement, "toggle", { open });
|
||||||
|
},
|
||||||
|
},
|
||||||
scopedSlots: {
|
scopedSlots: {
|
||||||
input() {
|
input() {
|
||||||
return createElement("slot", {
|
return createElement("slot", {
|
||||||
@@ -309,6 +318,10 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
min-width: unset !important;
|
min-width: unset !important;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
:host([opens-vertical="up"]) .daterangepicker {
|
||||||
|
bottom: 100%;
|
||||||
|
top: auto !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
if (mainWindow.document.dir === "rtl") {
|
if (mainWindow.document.dir === "rtl") {
|
||||||
style.innerHTML += `
|
style.innerHTML += `
|
||||||
@@ -340,4 +353,7 @@ declare global {
|
|||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"date-range-picker": DateRangePickerElement;
|
"date-range-picker": DateRangePickerElement;
|
||||||
}
|
}
|
||||||
|
interface HASSDomEvents {
|
||||||
|
toggle: { open: boolean };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import type { DeviceAction } from "../../data/device_automation";
|
import type { DeviceAction } from "../../data/device/device_automation";
|
||||||
import {
|
import {
|
||||||
fetchDeviceActions,
|
fetchDeviceActions,
|
||||||
localizeDeviceAutomationAction,
|
localizeDeviceAutomationAction,
|
||||||
} from "../../data/device_automation";
|
} from "../../data/device/device_automation";
|
||||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||||
|
|
||||||
@customElement("ha-device-action-picker")
|
@customElement("ha-device-action-picker")
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { consume } from "@lit/context";
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
import { fullEntitiesContext } from "../../data/context";
|
import { fullEntitiesContext } from "../../data/context";
|
||||||
import type { DeviceAutomation } from "../../data/device_automation";
|
import type { DeviceAutomation } from "../../data/device/device_automation";
|
||||||
import {
|
import {
|
||||||
deviceAutomationsEqual,
|
deviceAutomationsEqual,
|
||||||
sortDeviceAutomations,
|
sortDeviceAutomations,
|
||||||
} from "../../data/device_automation";
|
} from "../../data/device/device_automation";
|
||||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-md-select-option";
|
|
||||||
import "../ha-md-select";
|
import "../ha-md-select";
|
||||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
import "../ha-md-select-option";
|
||||||
|
|
||||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import type { DeviceCondition } from "../../data/device_automation";
|
import type { DeviceCondition } from "../../data/device/device_automation";
|
||||||
import {
|
import {
|
||||||
fetchDeviceConditions,
|
fetchDeviceConditions,
|
||||||
localizeDeviceAutomationCondition,
|
localizeDeviceAutomationCondition,
|
||||||
} from "../../data/device_automation";
|
} from "../../data/device/device_automation";
|
||||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||||
|
|
||||||
@customElement("ha-device-condition-picker")
|
@customElement("ha-device-condition-picker")
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
deviceComboBoxKeys,
|
deviceComboBoxKeys,
|
||||||
getDevices,
|
getDevices,
|
||||||
type DevicePickerItem,
|
type DevicePickerItem,
|
||||||
type DeviceRegistryEntry,
|
} from "../../data/device/device_picker";
|
||||||
} from "../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { brandsUrl } from "../../util/brands-url";
|
import { brandsUrl } from "../../util/brands-url";
|
||||||
import "../ha-generic-picker";
|
import "../ha-generic-picker";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import type { DeviceTrigger } from "../../data/device_automation";
|
import type { DeviceTrigger } from "../../data/device/device_automation";
|
||||||
import {
|
import {
|
||||||
fetchDeviceTriggers,
|
fetchDeviceTriggers,
|
||||||
localizeDeviceAutomationTrigger,
|
localizeDeviceAutomationTrigger,
|
||||||
} from "../../data/device_automation";
|
} from "../../data/device/device_automation";
|
||||||
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
||||||
|
|
||||||
@customElement("ha-device-trigger-picker")
|
@customElement("ha-device-trigger-picker")
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ class HaDevicesPicker extends LitElement {
|
|||||||
(entityId) => html`
|
(entityId) => html`
|
||||||
<div>
|
<div>
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
allow-custom-entity
|
|
||||||
.curValue=${entityId}
|
.curValue=${entityId}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.deviceFilter=${this.deviceFilter}
|
.deviceFilter=${this.deviceFilter}
|
||||||
@@ -79,7 +78,6 @@ class HaDevicesPicker extends LitElement {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
allow-custom-entity
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.deviceFilter=${this.deviceFilter}
|
.deviceFilter=${this.deviceFilter}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||||
import "../ha-sortable";
|
import "../ha-sortable";
|
||||||
import "./ha-entity-picker";
|
import "./ha-entity-picker";
|
||||||
@@ -99,7 +99,6 @@ class HaEntitiesPicker extends LitElement {
|
|||||||
(entityId) => html`
|
(entityId) => html`
|
||||||
<div class="entity">
|
<div class="entity">
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
allow-custom-entity
|
|
||||||
.curValue=${entityId}
|
.curValue=${entityId}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
@@ -129,7 +128,6 @@ class HaEntitiesPicker extends LitElement {
|
|||||||
</ha-sortable>
|
</ha-sortable>
|
||||||
<div>
|
<div>
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
allow-custom-entity
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { computeEntityNameList } from "../../common/entity/compute_entity_name_display";
|
import { computeEntityNameList } from "../../common/entity/compute_entity_name_display";
|
||||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||||
import {
|
import {
|
||||||
entityComboBoxKeys,
|
entityComboBoxKeys,
|
||||||
getEntities,
|
getEntities,
|
||||||
type EntityComboBoxItem,
|
type EntityComboBoxItem,
|
||||||
} from "../../data/entity_registry";
|
} from "../../data/entity/entity_picker";
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
import {
|
import {
|
||||||
isHelperDomain,
|
isHelperDomain,
|
||||||
@@ -228,7 +228,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
if (!createDomains?.length) {
|
if (!createDomains?.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
this.hass.loadFragmentTranslation("config");
|
||||||
return createDomains.map((domain) => {
|
return createDomains.map((domain) => {
|
||||||
const primary = localize(
|
const primary = localize(
|
||||||
"ui.components.entity.entity-picker.create_helper",
|
"ui.components.entity.entity-picker.create_helper",
|
||||||
@@ -236,7 +236,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
domain: isHelperDomain(domain)
|
domain: isHelperDomain(domain)
|
||||||
? localize(
|
? localize(
|
||||||
`ui.panel.config.helpers.types.${domain as HelperDomain}`
|
`ui.panel.config.helpers.types.${domain as HelperDomain}`
|
||||||
)
|
) || domain
|
||||||
: domainToName(localize, domain),
|
: domainToName(localize, domain),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { STATES_OFF } from "../../common/const";
|
import { STATES_OFF } from "../../common/const";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { UNAVAILABLE, UNKNOWN, isUnavailableState } from "../../data/entity";
|
import {
|
||||||
|
UNAVAILABLE,
|
||||||
|
UNKNOWN,
|
||||||
|
isUnavailableState,
|
||||||
|
} from "../../data/entity/entity";
|
||||||
import { forwardHaptic } from "../../data/haptics";
|
import { forwardHaptic } from "../../data/haptics";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-formfield";
|
import "../ha-formfield";
|
||||||
|
|||||||
@@ -14,8 +14,12 @@ import {
|
|||||||
getNumberFormatOptions,
|
getNumberFormatOptions,
|
||||||
isNumericState,
|
isNumericState,
|
||||||
} from "../../common/number/format_number";
|
} from "../../common/number/format_number";
|
||||||
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import {
|
||||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
isUnavailableState,
|
||||||
|
UNAVAILABLE,
|
||||||
|
UNKNOWN,
|
||||||
|
} from "../../data/entity/entity";
|
||||||
|
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||||
import { timerTimeRemaining } from "../../data/timer";
|
import { timerTimeRemaining } from "../../data/timer";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-label-badge";
|
import "../ha-label-badge";
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import { createAreaRegistryEntry } from "../data/area_registry";
|
|||||||
import type {
|
import type {
|
||||||
DeviceEntityDisplayLookup,
|
DeviceEntityDisplayLookup,
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
} from "../data/device_registry";
|
} from "../data/device/device_registry";
|
||||||
import { getDeviceEntityDisplayLookup } from "../data/device_registry";
|
import { getDeviceEntityDisplayLookup } from "../data/device/device_registry";
|
||||||
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
import type { EntityRegistryDisplayEntry } from "../data/entity/entity_registry";
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||||
import { showAreaRegistryDetailDialog } from "../panels/config/areas/show-dialog-area-registry-detail";
|
import { showAreaRegistryDetailDialog } from "../panels/config/areas/show-dialog-area-registry-detail";
|
||||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||||
@@ -31,10 +31,10 @@ import "./ha-svg-icon";
|
|||||||
const ADD_NEW_ID = "___ADD_NEW___";
|
const ADD_NEW_ID = "___ADD_NEW___";
|
||||||
|
|
||||||
const SEARCH_KEYS = [
|
const SEARCH_KEYS = [
|
||||||
{ name: "areaName", weight: 10 },
|
{ name: "search_labels.areaName", weight: 10 },
|
||||||
{ name: "aliases", weight: 8 },
|
{ name: "search_labels.aliases", weight: 8 },
|
||||||
{ name: "floorName", weight: 6 },
|
{ name: "search_labels.floorName", weight: 6 },
|
||||||
{ name: "id", weight: 3 },
|
{ name: "search_labels.id", weight: 3 },
|
||||||
];
|
];
|
||||||
@customElement("ha-area-picker")
|
@customElement("ha-area-picker")
|
||||||
export class HaAreaPicker extends LitElement {
|
export class HaAreaPicker extends LitElement {
|
||||||
@@ -51,6 +51,9 @@ export class HaAreaPicker extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "no-add" })
|
@property({ type: Boolean, attribute: "no-add" })
|
||||||
public noAdd = false;
|
public noAdd = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "show-label" })
|
||||||
|
public showLabel = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show only areas with entities from specific domains.
|
* Show only areas with entities from specific domains.
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
@@ -296,7 +299,6 @@ export class HaAreaPicker extends LitElement {
|
|||||||
secondary: floorName,
|
secondary: floorName,
|
||||||
icon: area.icon || undefined,
|
icon: area.icon || undefined,
|
||||||
icon_path: area.icon ? undefined : mdiTextureBox,
|
icon_path: area.icon ? undefined : mdiTextureBox,
|
||||||
sorting_label: areaName,
|
|
||||||
search_labels: {
|
search_labels: {
|
||||||
areaName: areaName || null,
|
areaName: areaName || null,
|
||||||
floorName: floorName || null,
|
floorName: floorName || null,
|
||||||
@@ -366,9 +368,17 @@ export class HaAreaPicker extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const placeholder =
|
const placeholder =
|
||||||
this.placeholder ?? this.hass.localize("ui.components.area-picker.area");
|
this.placeholder ?? this.hass.localize("ui.components.area-picker.area");
|
||||||
|
|
||||||
const valueRenderer = this._computeValueRenderer(this.hass.areas);
|
const valueRenderer = this._computeValueRenderer(this.hass.areas);
|
||||||
|
|
||||||
|
let showLabel = this.showLabel;
|
||||||
|
if (this.value) {
|
||||||
|
const area = this.hass.areas[this.value];
|
||||||
|
if (area) {
|
||||||
|
const { floor } = getAreaContext(area, this.hass.floors);
|
||||||
|
showLabel = !floor && this.showLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-generic-picker
|
<ha-generic-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -380,6 +390,7 @@ export class HaAreaPicker extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.placeholder=${placeholder}
|
.placeholder=${placeholder}
|
||||||
|
.showLabel=${showLabel}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.getItems=${this._getItems}
|
.getItems=${this._getItems}
|
||||||
.getAdditionalItems=${this._getAdditionalItems}
|
.getAdditionalItems=${this._getAdditionalItems}
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ export class HaAssistChat extends LitElement {
|
|||||||
})}"
|
})}"
|
||||||
breaks
|
breaks
|
||||||
cache
|
cache
|
||||||
assist
|
|
||||||
.content=${message.text}
|
.content=${message.text}
|
||||||
>
|
>
|
||||||
</ha-markdown>
|
</ha-markdown>
|
||||||
@@ -660,7 +659,7 @@ export class HaAssistChat extends LitElement {
|
|||||||
--markdown-table-border-color: var(--divider-color);
|
--markdown-table-border-color: var(--divider-color);
|
||||||
--markdown-code-background-color: var(--primary-background-color);
|
--markdown-code-background-color: var(--primary-background-color);
|
||||||
--markdown-code-text-color: var(--primary-text-color);
|
--markdown-code-text-color: var(--primary-text-color);
|
||||||
--markdown-list-indent: 1rem;
|
--markdown-list-indent: 1.15em;
|
||||||
&:not(:has(ha-markdown-element)) {
|
&:not(:has(ha-markdown-element)) {
|
||||||
min-height: 1lh;
|
min-height: 1lh;
|
||||||
min-width: 1lh;
|
min-width: 1lh;
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
|
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
|
||||||
|
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||||
import {
|
import {
|
||||||
STATE_ATTRIBUTES,
|
STATE_ATTRIBUTES,
|
||||||
STATE_ATTRIBUTES_DOMAIN_CLASS,
|
STATE_ATTRIBUTES_DOMAIN_CLASS,
|
||||||
} from "../data/entity_attributes";
|
} from "../data/entity/entity_attributes";
|
||||||
import { haStyle } from "../resources/styles";
|
import { haStyle } from "../resources/styles";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-attribute-value";
|
import "./ha-attribute-value";
|
||||||
import "./ha-expansion-panel";
|
import "./ha-expansion-panel";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
|
||||||
|
|
||||||
@customElement("ha-attributes")
|
@customElement("ha-attributes")
|
||||||
class HaAttributes extends LitElement {
|
class HaAttributes extends LitElement {
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ export class HaAutomationRow extends LitElement {
|
|||||||
<slot name="leading-icon"></slot>
|
<slot name="leading-icon"></slot>
|
||||||
</div>
|
</div>
|
||||||
<slot class="header" name="header"></slot>
|
<slot class="header" name="header"></slot>
|
||||||
<slot name="icons"></slot>
|
<div class="icons">
|
||||||
|
<slot name="icons"></slot>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -118,12 +120,11 @@ export class HaAutomationRow extends LitElement {
|
|||||||
}
|
}
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: var(--ha-space-0) var(--ha-space-2);
|
padding: 0 var(--ha-space-3);
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-weight: var(--ha-font-weight-medium);
|
|
||||||
outline: none;
|
outline: none;
|
||||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||||
}
|
}
|
||||||
@@ -140,11 +141,15 @@ export class HaAutomationRow extends LitElement {
|
|||||||
background-color: var(--ha-color-fill-neutral-loud-resting);
|
background-color: var(--ha-color-fill-neutral-loud-resting);
|
||||||
border-radius: var(--ha-border-radius-md);
|
border-radius: var(--ha-border-radius-md);
|
||||||
padding: var(--ha-space-1);
|
padding: var(--ha-space-1);
|
||||||
|
margin-top: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
.leading-icon-wrapper {
|
||||||
|
padding-top: var(--ha-space-3);
|
||||||
|
}
|
||||||
::slotted([slot="leading-icon"]) {
|
::slotted([slot="leading-icon"]) {
|
||||||
color: var(--ha-color-on-neutral-quiet);
|
color: var(--ha-color-on-neutral-quiet);
|
||||||
}
|
}
|
||||||
@@ -172,6 +177,10 @@ export class HaAutomationRow extends LitElement {
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
margin: var(--ha-space-0) var(--ha-space-3);
|
margin: var(--ha-space-0) var(--ha-space-3);
|
||||||
}
|
}
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
:host([sort-selected]) .row {
|
:host([sort-selected]) .row {
|
||||||
outline: solid;
|
outline: solid;
|
||||||
outline-color: rgba(var(--rgb-accent-color), 0.6);
|
outline-color: rgba(var(--rgb-accent-color), 0.6);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { ClimateEntity } from "../data/climate";
|
import type { ClimateEntity } from "../data/climate";
|
||||||
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
||||||
import { isUnavailableState, OFF } from "../data/entity";
|
import { isUnavailableState, OFF } from "../data/entity/entity";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("ha-climate-state")
|
@customElement("ha-climate-state")
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ export class HaComboBoxItem extends HaMdListItem {
|
|||||||
[slot="start"] {
|
[slot="start"] {
|
||||||
--state-icon-color: var(--secondary-text-color);
|
--state-icon-color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
[slot="overline"] {
|
||||||
|
/* mimicing a floating label of mdc-select */
|
||||||
|
line-height: 1.15rem;
|
||||||
|
font-size: calc(var(--mdc-typography-subtitle1-font-size, 1rem) * 0.75);
|
||||||
|
font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
|
||||||
|
font-family: var(
|
||||||
|
--mdc-typography-subtitle1-font-family,
|
||||||
|
var(--mdc-typography-font-family)
|
||||||
|
);
|
||||||
|
color: var(--mdc-select-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||||
|
}
|
||||||
[slot="headline"] {
|
[slot="headline"] {
|
||||||
line-height: var(--ha-line-height-normal);
|
line-height: var(--ha-line-height-normal);
|
||||||
font-size: var(--ha-font-size-m);
|
font-size: var(--ha-font-size-m);
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import type { ConfigEntry, SubEntry } from "../data/config_entries";
|
|||||||
import { getConfigEntry, getSubEntries } from "../data/config_entries";
|
import { getConfigEntry, getSubEntries } from "../data/config_entries";
|
||||||
import type { Agent } from "../data/conversation";
|
import type { Agent } from "../data/conversation";
|
||||||
import { listAgents } from "../data/conversation";
|
import { listAgents } from "../data/conversation";
|
||||||
|
import { getExtendedEntityRegistryEntry } from "../data/entity/entity_registry";
|
||||||
import { fetchIntegrationManifest } from "../data/integration";
|
import { fetchIntegrationManifest } from "../data/integration";
|
||||||
import { showOptionsFlowDialog } from "../dialogs/config-flow/show-dialog-options-flow";
|
import { showOptionsFlowDialog } from "../dialogs/config-flow/show-dialog-options-flow";
|
||||||
|
import { showSubConfigFlowDialog } from "../dialogs/config-flow/show-dialog-sub-config-flow";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-list-item";
|
import "./ha-list-item";
|
||||||
import "./ha-select";
|
import "./ha-select";
|
||||||
import type { HaSelect } from "./ha-select";
|
import type { HaSelect } from "./ha-select";
|
||||||
import { getExtendedEntityRegistryEntry } from "../data/entity_registry";
|
|
||||||
import { showSubConfigFlowDialog } from "../dialogs/config-flow/show-dialog-sub-config-flow";
|
|
||||||
|
|
||||||
const NONE = "__NONE_OPTION__";
|
const NONE = "__NONE_OPTION__";
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
@property({ attribute: "extended-presets", type: Boolean })
|
@property({ attribute: "extended-presets", type: Boolean })
|
||||||
public extendedPresets = false;
|
public extendedPresets = false;
|
||||||
|
|
||||||
|
@property({ attribute: "vertical-opening-direction" })
|
||||||
|
public verticalOpeningDirection?: "up" | "down";
|
||||||
|
|
||||||
@property({ attribute: false }) public openingDirection?:
|
@property({ attribute: false }) public openingDirection?:
|
||||||
| "right"
|
| "right"
|
||||||
| "left"
|
| "left"
|
||||||
@@ -127,6 +130,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
opening-direction=${ifDefined(
|
opening-direction=${ifDefined(
|
||||||
this.openingDirection || this._calcedOpeningDirection
|
this.openingDirection || this._calcedOpeningDirection
|
||||||
)}
|
)}
|
||||||
|
opens-vertical=${ifDefined(this.verticalOpeningDirection)}
|
||||||
first-day=${firstWeekdayIndex(this.hass.locale)}
|
first-day=${firstWeekdayIndex(this.hass.locale)}
|
||||||
language=${this.hass.locale.language}
|
language=${this.hass.locale.language}
|
||||||
@change=${this._handleChange}
|
@change=${this._handleChange}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export class HaDomainIcon extends LitElement {
|
|||||||
if (icn) {
|
if (icn) {
|
||||||
return html`<ha-icon .icon=${icn}></ha-icon>`;
|
return html`<ha-icon .icon=${icn}></ha-icon>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._renderFallback();
|
return this._renderFallback();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
|
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
|
||||||
import { css, type CSSResultGroup } from "lit";
|
import "@home-assistant/webawesome/dist/components/icon/icon";
|
||||||
|
import { css, type CSSResultGroup, html } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
import "./ha-svg-icon";
|
||||||
|
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "@mdi/js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Home Assistant dropdown item component
|
* Home Assistant dropdown item component
|
||||||
@@ -14,6 +17,16 @@ import { customElement } from "lit/decorators";
|
|||||||
*/
|
*/
|
||||||
@customElement("ha-dropdown-item")
|
@customElement("ha-dropdown-item")
|
||||||
export class HaDropdownItem extends DropdownItem {
|
export class HaDropdownItem extends DropdownItem {
|
||||||
|
protected renderCheckboxIcon() {
|
||||||
|
return html`
|
||||||
|
<ha-svg-icon
|
||||||
|
id="check"
|
||||||
|
part="checkmark"
|
||||||
|
.path=${this.checked ? mdiCheckboxMarked : mdiCheckboxBlankOutline}
|
||||||
|
></ha-svg-icon>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
DropdownItem.styles,
|
DropdownItem.styles,
|
||||||
@@ -22,6 +35,10 @@ export class HaDropdownItem extends DropdownItem {
|
|||||||
min-height: var(--ha-space-10);
|
min-height: var(--ha-space-10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#check {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
#icon ::slotted(*) {
|
#icon ::slotted(*) {
|
||||||
color: var(--ha-color-on-neutral-normal);
|
color: var(--ha-color-on-neutral-normal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import { computeCssColor } from "../common/color/compute-color";
|
|||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import { stringCompare } from "../common/string/compare";
|
import { stringCompare } from "../common/string/compare";
|
||||||
import type { LabelRegistryEntry } from "../data/label_registry";
|
import type { LabelRegistryEntry } from "../data/label/label_registry";
|
||||||
import { subscribeLabelRegistry } from "../data/label_registry";
|
import { subscribeLabelRegistry } from "../data/label/label_registry";
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import { updateAreaRegistryEntry } from "../data/area_registry";
|
|||||||
import type {
|
import type {
|
||||||
DeviceEntityDisplayLookup,
|
DeviceEntityDisplayLookup,
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
} from "../data/device_registry";
|
} from "../data/device/device_registry";
|
||||||
import { getDeviceEntityDisplayLookup } from "../data/device_registry";
|
import { getDeviceEntityDisplayLookup } from "../data/device/device_registry";
|
||||||
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
import type { EntityRegistryDisplayEntry } from "../data/entity/entity_registry";
|
||||||
import {
|
import {
|
||||||
createFloorRegistryEntry,
|
createFloorRegistryEntry,
|
||||||
getFloorAreaLookup,
|
getFloorAreaLookup,
|
||||||
@@ -36,9 +36,9 @@ import "./ha-svg-icon";
|
|||||||
const ADD_NEW_ID = "___ADD_NEW___";
|
const ADD_NEW_ID = "___ADD_NEW___";
|
||||||
|
|
||||||
const SEARCH_KEYS = [
|
const SEARCH_KEYS = [
|
||||||
{ name: "floorName", weight: 10 },
|
{ name: "search_labels.floorName", weight: 10 },
|
||||||
{ name: "aliases", weight: 8 },
|
{ name: "search_labels.aliases", weight: 8 },
|
||||||
{ name: "floor_id", weight: 3 },
|
{ name: "search_labels.floor_id", weight: 3 },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface FloorComboBoxItem extends PickerComboBoxItem {
|
interface FloorComboBoxItem extends PickerComboBoxItem {
|
||||||
@@ -291,7 +291,6 @@ export class HaFloorPicker extends LitElement {
|
|||||||
id: floor.floor_id,
|
id: floor.floor_id,
|
||||||
primary: floorName,
|
primary: floorName,
|
||||||
floor: floor,
|
floor: floor,
|
||||||
sorting_label: floor.level?.toString() || "zzzzz",
|
|
||||||
search_labels: {
|
search_labels: {
|
||||||
floorName,
|
floorName,
|
||||||
floor_id: floor.floor_id,
|
floor_id: floor.floor_id,
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ export class HaGenericPicker extends LitElement {
|
|||||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||||
public hideClearIcon = false;
|
public hideClearIcon = false;
|
||||||
|
|
||||||
|
@property({ attribute: "show-label", type: Boolean })
|
||||||
|
public showLabel = false;
|
||||||
|
|
||||||
/** To prevent lags, getItems needs to be memoized */
|
/** To prevent lags, getItems needs to be memoized */
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public getItems!: (
|
public getItems!: (
|
||||||
@@ -115,6 +118,9 @@ export class HaGenericPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "custom-value-label" })
|
||||||
|
public customValueLabel?: string;
|
||||||
|
|
||||||
@query(".container") private _containerElement?: HTMLDivElement;
|
@query(".container") private _containerElement?: HTMLDivElement;
|
||||||
|
|
||||||
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
||||||
@@ -164,12 +170,17 @@ export class HaGenericPicker extends LitElement {
|
|||||||
type="button"
|
type="button"
|
||||||
class=${this._opened ? "opened" : ""}
|
class=${this._opened ? "opened" : ""}
|
||||||
compact
|
compact
|
||||||
.unknown=${this._unknownValue(this.value, this.getItems())}
|
.unknown=${this._unknownValue(
|
||||||
|
this.allowCustomValue,
|
||||||
|
this.value,
|
||||||
|
this.getItems()
|
||||||
|
)}
|
||||||
.unknownItemText=${this.unknownItemText}
|
.unknownItemText=${this.unknownItemText}
|
||||||
aria-label=${ifDefined(this.label)}
|
aria-label=${ifDefined(this.label)}
|
||||||
@click=${this.open}
|
@click=${this.open}
|
||||||
@clear=${this._clear}
|
@clear=${this._clear}
|
||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
|
.showLabel=${this.showLabel}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@@ -240,13 +251,24 @@ export class HaGenericPicker extends LitElement {
|
|||||||
.sectionTitleFunction=${this.sectionTitleFunction}
|
.sectionTitleFunction=${this.sectionTitleFunction}
|
||||||
.selectedSection=${this.selectedSection}
|
.selectedSection=${this.selectedSection}
|
||||||
.searchKeys=${this.searchKeys}
|
.searchKeys=${this.searchKeys}
|
||||||
|
.customValueLabel=${this.customValueLabel}
|
||||||
></ha-picker-combo-box>
|
></ha-picker-combo-box>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _unknownValue = memoizeOne(
|
private _unknownValue = memoizeOne(
|
||||||
(value?: string, items?: (PickerComboBoxItem | string)[]) => {
|
(
|
||||||
if (value === undefined || !items) {
|
allowCustomValue: boolean,
|
||||||
|
value?: string,
|
||||||
|
items?: (PickerComboBoxItem | string)[]
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
allowCustomValue ||
|
||||||
|
value === undefined ||
|
||||||
|
value === null ||
|
||||||
|
value === "" ||
|
||||||
|
!items
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +389,10 @@ export class HaGenericPicker extends LitElement {
|
|||||||
|
|
||||||
wa-popover::part(body) {
|
wa-popover::part(body) {
|
||||||
width: max(var(--body-width), 250px);
|
width: max(var(--body-width), 250px);
|
||||||
max-width: max(var(--body-width), 250px);
|
max-width: var(
|
||||||
|
--ha-generic-picker-max-width,
|
||||||
|
max(var(--body-width), 250px)
|
||||||
|
);
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { isUnavailableState, OFF } from "../data/entity";
|
import { isUnavailableState, OFF } from "../data/entity/entity";
|
||||||
import type { HumidifierEntity } from "../data/humidifier";
|
import type { HumidifierEntity } from "../data/humidifier";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ import {
|
|||||||
} from "lit/decorators";
|
} from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { LabelRegistryEntry } from "../data/label_registry";
|
import { getLabels, labelComboBoxKeys } from "../data/label/label_picker";
|
||||||
import {
|
import {
|
||||||
createLabelRegistryEntry,
|
createLabelRegistryEntry,
|
||||||
getLabels,
|
|
||||||
labelComboBoxKeys,
|
|
||||||
subscribeLabelRegistry,
|
subscribeLabelRegistry,
|
||||||
} from "../data/label_registry";
|
type LabelRegistryEntry,
|
||||||
|
} from "../data/label/label_registry";
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
|
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import memoizeOne from "memoize-one";
|
|||||||
import { computeCssColor } from "../common/color/compute-color";
|
import { computeCssColor } from "../common/color/compute-color";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stringCompare } from "../common/string/compare";
|
import { stringCompare } from "../common/string/compare";
|
||||||
import type { LabelRegistryEntry } from "../data/label_registry";
|
import type { LabelRegistryEntry } from "../data/label/label_registry";
|
||||||
import {
|
import {
|
||||||
subscribeLabelRegistry,
|
subscribeLabelRegistry,
|
||||||
updateLabelRegistryEntry,
|
updateLabelRegistryEntry,
|
||||||
} from "../data/label_registry";
|
} from "../data/label/label_registry";
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
|
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
|
||||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
.placeholder=${this.label ??
|
.placeholder=${this.label ??
|
||||||
(this.hass?.localize("ui.components.language-picker.language") ||
|
(this.hass?.localize("ui.components.language-picker.language") ||
|
||||||
"Language")}
|
"Language")}
|
||||||
|
show-label
|
||||||
.value=${value}
|
.value=${value}
|
||||||
.valueRenderer=${this._valueRenderer}
|
.valueRenderer=${this._valueRenderer}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
|||||||
@@ -70,11 +70,6 @@ export class HaMarkdown extends LitElement {
|
|||||||
a {
|
a {
|
||||||
color: var(--markdown-link-color, var(--primary-color));
|
color: var(--markdown-link-color, var(--primary-color));
|
||||||
}
|
}
|
||||||
:host([assist]) img {
|
|
||||||
height: auto;
|
|
||||||
width: auto;
|
|
||||||
transition: height 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
img {
|
img {
|
||||||
background-color: var(--markdown-image-background-color);
|
background-color: var(--markdown-image-background-color);
|
||||||
border-radius: var(--markdown-image-border-radius);
|
border-radius: var(--markdown-image-border-radius);
|
||||||
@@ -86,8 +81,7 @@ export class HaMarkdown extends LitElement {
|
|||||||
p:first-child > img:last-child {
|
p:first-child > img:last-child {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
:host > ul,
|
ha-markdown-element > :is(ol, ul) {
|
||||||
:host > ol {
|
|
||||||
padding-inline-start: var(--markdown-list-indent, revert);
|
padding-inline-start: var(--markdown-list-indent, revert);
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
@@ -138,6 +132,18 @@ export class HaMarkdown extends LitElement {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
margin: var(--ha-space-4) 0;
|
margin: var(--ha-space-4) 0;
|
||||||
}
|
}
|
||||||
|
table[role="presentation"] {
|
||||||
|
--markdown-table-border-collapse: separate;
|
||||||
|
--markdown-table-border-width: attr(border, 0);
|
||||||
|
--markdown-table-padding-inline: 0;
|
||||||
|
--markdown-table-padding-block: 0;
|
||||||
|
th {
|
||||||
|
vertical-align: attr(align, center);
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
vertical-align: attr(align, left);
|
||||||
|
}
|
||||||
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: var(--markdown-table-border-collapse, collapse);
|
border-collapse: var(--markdown-table-border-collapse, collapse);
|
||||||
}
|
}
|
||||||
@@ -145,14 +151,15 @@ export class HaMarkdown extends LitElement {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
text-align: start;
|
text-align: var(--markdown-table-text-align, start);
|
||||||
}
|
}
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
border-width: var(--markdown-table-border-width, 1px);
|
border-width: var(--markdown-table-border-width, 1px);
|
||||||
border-style: var(--markdown-table-border-style, solid);
|
border-style: var(--markdown-table-border-style, solid);
|
||||||
border-color: var(--markdown-table-border-color, var(--divider-color));
|
border-color: var(--markdown-table-border-color, var(--divider-color));
|
||||||
padding: 0.25em 0.5em;
|
padding-inline: var(--markdown-table-padding-inline, 0.5em);
|
||||||
|
padding-block: var(--markdown-table-padding-block, 0.25em);
|
||||||
}
|
}
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 4px solid var(--divider-color);
|
border-left: 4px solid var(--divider-color);
|
||||||
|
|||||||
@@ -1,55 +1,17 @@
|
|||||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import type { PropertyValues, TemplateResult } from "lit";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { titleCase } from "../common/string/title-case";
|
import { titleCase } from "../common/string/title-case";
|
||||||
import { fetchConfig } from "../data/lovelace/config/types";
|
import { fetchConfig } from "../data/lovelace/config/types";
|
||||||
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
|
|
||||||
import { getPanelIcon, getPanelTitle } from "../data/panel";
|
import { getPanelIcon, getPanelTitle } from "../data/panel";
|
||||||
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
|
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||||
import "./ha-combo-box";
|
import "./ha-generic-picker";
|
||||||
import type { HaComboBox } from "./ha-combo-box";
|
|
||||||
import "./ha-combo-box-item";
|
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
|
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||||
interface NavigationItem {
|
|
||||||
path: string;
|
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_ITEMS: NavigationItem[] = [];
|
|
||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<NavigationItem> = (item) => html`
|
|
||||||
<ha-combo-box-item type="button">
|
|
||||||
<ha-icon .icon=${item.icon} slot="start"></ha-icon>
|
|
||||||
<span slot="headline">${item.title || item.path}</span>
|
|
||||||
${item.title
|
|
||||||
? html`<span slot="supporting-text">${item.path}</span>`
|
|
||||||
: nothing}
|
|
||||||
</ha-combo-box-item>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const createViewNavigationItem = (
|
|
||||||
prefix: string,
|
|
||||||
view: LovelaceViewRawConfig,
|
|
||||||
index: number
|
|
||||||
) => ({
|
|
||||||
path: `/${prefix}/${view.path ?? index}`,
|
|
||||||
icon: view.icon ?? "mdi:view-compact",
|
|
||||||
title: view.title ?? (view.path ? titleCase(view.path) : `${index}`),
|
|
||||||
});
|
|
||||||
|
|
||||||
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
|
|
||||||
path: `/${panel.url_path}`,
|
|
||||||
icon: getPanelIcon(panel) || "mdi:view-dashboard",
|
|
||||||
title: getPanelTitle(hass, panel) || "",
|
|
||||||
});
|
|
||||||
|
|
||||||
@customElement("ha-navigation-picker")
|
@customElement("ha-navigation-picker")
|
||||||
export class HaNavigationPicker extends LitElement {
|
export class HaNavigationPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@@ -61,46 +23,51 @@ export class HaNavigationPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
@state() private _opened = false;
|
@state() private _loading = true;
|
||||||
|
|
||||||
private navigationItemsLoaded = false;
|
protected firstUpdated() {
|
||||||
|
this._loadNavigationItems();
|
||||||
|
}
|
||||||
|
|
||||||
private navigationItems: NavigationItem[] = DEFAULT_ITEMS;
|
private _navigationItems: PickerComboBoxItem[] = [];
|
||||||
|
|
||||||
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
protected render() {
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
return html`
|
||||||
<ha-combo-box
|
<ha-generic-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
item-value-path="path"
|
.value=${this._loading ? undefined : this.value}
|
||||||
item-label-path="path"
|
|
||||||
.value=${this._value}
|
|
||||||
allow-custom-value
|
allow-custom-value
|
||||||
.filteredItems=${this.navigationItems}
|
.placeholder=${this.label}
|
||||||
.label=${this.label}
|
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this._loading || this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.renderer=${rowRenderer}
|
.getItems=${this._getItems}
|
||||||
@opened-changed=${this._openedChanged}
|
.valueRenderer=${this._valueRenderer}
|
||||||
|
.customValueLabel=${this.hass.localize(
|
||||||
|
"ui.components.navigation-picker.add_custom_path"
|
||||||
|
)}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@filter-changed=${this._filterChanged}
|
|
||||||
>
|
>
|
||||||
</ha-combo-box>
|
</ha-generic-picker>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _openedChanged(ev: ValueChangedEvent<boolean>) {
|
private _valueRenderer = (itemId: string) => {
|
||||||
this._opened = ev.detail.value;
|
const item = this._navigationItems.find((navItem) => navItem.id === itemId);
|
||||||
if (this._opened && !this.navigationItemsLoaded) {
|
return html`
|
||||||
this._loadNavigationItems();
|
${item?.icon
|
||||||
}
|
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||||
}
|
: nothing}
|
||||||
|
<span slot="headline">${item?.primary || itemId}</span>
|
||||||
|
${item?.primary
|
||||||
|
? html`<span slot="supporting-text">${itemId}</span>`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _getItems = () => this._navigationItems;
|
||||||
|
|
||||||
private async _loadNavigationItems() {
|
private async _loadNavigationItems() {
|
||||||
this.navigationItemsLoaded = true;
|
|
||||||
|
|
||||||
const panels = Object.entries(this.hass!.panels).map(([id, panel]) => ({
|
const panels = Object.entries(this.hass!.panels).map(([id, panel]) => ({
|
||||||
id,
|
id,
|
||||||
...panel,
|
...panel,
|
||||||
@@ -124,27 +91,47 @@ export class HaNavigationPicker extends LitElement {
|
|||||||
|
|
||||||
const panelViewConfig = new Map(viewConfigs);
|
const panelViewConfig = new Map(viewConfigs);
|
||||||
|
|
||||||
this.navigationItems = [];
|
this._navigationItems = [];
|
||||||
|
|
||||||
for (const panel of panels) {
|
for (const panel of panels) {
|
||||||
this.navigationItems.push(createPanelNavigationItem(this.hass!, panel));
|
const path = `/${panel.url_path}`;
|
||||||
|
const panelTitle = getPanelTitle(this.hass, panel);
|
||||||
|
const primary = panelTitle || path;
|
||||||
|
this._navigationItems.push({
|
||||||
|
id: path,
|
||||||
|
primary,
|
||||||
|
secondary: panelTitle ? path : undefined,
|
||||||
|
icon: getPanelIcon(panel) || "mdi:view-dashboard",
|
||||||
|
sorting_label: [
|
||||||
|
primary.startsWith("/") ? `zzz${primary}` : primary,
|
||||||
|
path,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("_"),
|
||||||
|
});
|
||||||
|
|
||||||
const config = panelViewConfig.get(panel.id);
|
const config = panelViewConfig.get(panel.id);
|
||||||
|
|
||||||
if (!config || !("views" in config)) continue;
|
if (!config || !("views" in config)) continue;
|
||||||
|
|
||||||
config.views.forEach((view, index) =>
|
config.views.forEach((view, index) => {
|
||||||
this.navigationItems.push(
|
const viewPath = `/${panel.url_path}/${view.path ?? index}`;
|
||||||
createViewNavigationItem(panel.url_path, view, index)
|
const viewPrimary =
|
||||||
)
|
view.title ?? (view.path ? titleCase(view.path) : `${index}`);
|
||||||
);
|
this._navigationItems.push({
|
||||||
|
id: viewPath,
|
||||||
|
secondary: viewPath,
|
||||||
|
icon: view.icon ?? "mdi:view-compact",
|
||||||
|
primary: viewPrimary,
|
||||||
|
sorting_label: [
|
||||||
|
viewPrimary.startsWith("/") ? `zzz${viewPrimary}` : viewPrimary,
|
||||||
|
viewPath,
|
||||||
|
].join("_"),
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.comboBox.filteredItems = this.navigationItems;
|
this._loading = false;
|
||||||
}
|
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues) {
|
|
||||||
return !this._opened || changedProps.has("_opened");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||||
@@ -152,61 +139,18 @@ export class HaNavigationPicker extends LitElement {
|
|||||||
this._setValue(ev.detail.value);
|
this._setValue(ev.detail.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setValue(value: string) {
|
private _setValue(value = "") {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
fireEvent(
|
fireEvent(
|
||||||
this,
|
this,
|
||||||
"value-changed",
|
"value-changed",
|
||||||
{ value: this._value },
|
{ value: this.value },
|
||||||
{
|
{
|
||||||
bubbles: false,
|
bubbles: false,
|
||||||
composed: false,
|
composed: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterChanged(ev: CustomEvent): void {
|
|
||||||
const filterString = ev.detail.value.toLowerCase();
|
|
||||||
const characterCount = filterString.length;
|
|
||||||
if (characterCount >= 2) {
|
|
||||||
const filteredItems: NavigationItem[] = [];
|
|
||||||
|
|
||||||
this.navigationItems.forEach((item) => {
|
|
||||||
if (
|
|
||||||
item.path.toLowerCase().includes(filterString) ||
|
|
||||||
item.title.toLowerCase().includes(filterString)
|
|
||||||
) {
|
|
||||||
filteredItems.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filteredItems.length > 0) {
|
|
||||||
this.comboBox.filteredItems = filteredItems;
|
|
||||||
} else {
|
|
||||||
this.comboBox.filteredItems = [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.comboBox.filteredItems = this.navigationItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
|
||||||
return this.value || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
ha-icon,
|
|
||||||
ha-svg-icon {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
position: relative;
|
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
*[slot="prefix"] {
|
|
||||||
margin-right: 8px;
|
|
||||||
margin-inline-end: 8px;
|
|
||||||
margin-inline-start: initial;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { LitVirtualizer } from "@lit-labs/virtualizer";
|
import type { LitVirtualizer } from "@lit-labs/virtualizer";
|
||||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||||
import { mdiMagnify, mdiMinusBoxOutline } from "@mdi/js";
|
import { mdiMagnify, mdiMinusBoxOutline, mdiPlus } from "@mdi/js";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import {
|
import {
|
||||||
@@ -91,6 +91,9 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
|||||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||||
public allowCustomValue;
|
public allowCustomValue;
|
||||||
|
|
||||||
|
@property({ attribute: "custom-value-label" })
|
||||||
|
public customValueLabel?: string;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
@@ -187,10 +190,15 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
const searchLabel =
|
||||||
|
this.label ??
|
||||||
|
(this.allowCustomValue
|
||||||
|
? (this.hass?.localize("ui.components.combo-box.search_or_custom") ??
|
||||||
|
"Search | Add custom value")
|
||||||
|
: (this.hass?.localize("ui.common.search") ?? "Search"));
|
||||||
|
|
||||||
return html`<ha-textfield
|
return html`<ha-textfield
|
||||||
.label=${this.label ??
|
.label=${searchLabel}
|
||||||
this.hass?.localize("ui.common.search") ??
|
|
||||||
"Search"}
|
|
||||||
@input=${this._filterChanged}
|
@input=${this._filterChanged}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
${this._renderSectionButtons()}
|
${this._renderSectionButtons()}
|
||||||
@@ -288,13 +296,30 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
|||||||
let items = [...this.getItems(this._search, this.selectedSection)];
|
let items = [...this.getItems(this._search, this.selectedSection)];
|
||||||
|
|
||||||
if (!this.sections?.length) {
|
if (!this.sections?.length) {
|
||||||
items = items.sort((entityA, entityB) =>
|
items = items.sort((entityA, entityB) => {
|
||||||
caseInsensitiveStringCompare(
|
const sortLabelA =
|
||||||
(entityA as PickerComboBoxItem).sorting_label!,
|
typeof entityA === "string" ? entityA : entityA.sorting_label;
|
||||||
(entityB as PickerComboBoxItem).sorting_label!,
|
const sortLabelB =
|
||||||
|
typeof entityB === "string" ? entityB : entityB.sorting_label;
|
||||||
|
|
||||||
|
if (!sortLabelA || !sortLabelB) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sortLabelB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sortLabelA) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return caseInsensitiveStringCompare(
|
||||||
|
sortLabelA,
|
||||||
|
sortLabelB,
|
||||||
this.hass?.locale.language ?? navigator.language
|
this.hass?.locale.language ?? navigator.language
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
@@ -421,13 +446,23 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.allowCustomValue && searchString) {
|
||||||
|
filteredItems.push({
|
||||||
|
id: searchString,
|
||||||
|
primary:
|
||||||
|
this.customValueLabel ??
|
||||||
|
this.hass?.localize("ui.components.combo-box.add_custom_item") ??
|
||||||
|
"Add custom item",
|
||||||
|
secondary: `"${searchString}"`,
|
||||||
|
icon_path: mdiPlus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this._items = filteredItems as PickerComboBoxItem[];
|
this._items = filteredItems as PickerComboBoxItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
this._selectedItemIndex = -1;
|
this._selectedItemIndex = -1;
|
||||||
if (this._virtualizerElement) {
|
this._valuePinned = true;
|
||||||
this._virtualizerElement.scrollTo(0, 0);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private _toggleSection(ev: Event) {
|
private _toggleSection(ev: Event) {
|
||||||
@@ -622,7 +657,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
|||||||
typeof item === "string" ? item : item?.id;
|
typeof item === "string" ? item : item?.id;
|
||||||
|
|
||||||
private _getInitialSelectedIndex() {
|
private _getInitialSelectedIndex() {
|
||||||
if (!this._virtualizerElement || !this.value) {
|
if (!this._virtualizerElement || this._search || !this.value) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export class HaPickerField extends LitElement {
|
|||||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||||
public hideClearIcon = false;
|
public hideClearIcon = false;
|
||||||
|
|
||||||
|
@property({ attribute: "show-label", type: Boolean })
|
||||||
|
public showLabel = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public valueRenderer?: PickerValueRenderer;
|
public valueRenderer?: PickerValueRenderer;
|
||||||
|
|
||||||
@@ -60,13 +63,16 @@ export class HaPickerField extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
const showClearIcon =
|
const showClearIcon =
|
||||||
!!this.value && !this.required && !this.disabled && !this.hideClearIcon;
|
!!this.value && !this.required && !this.disabled && !this.hideClearIcon;
|
||||||
|
const placeholder = this.showLabel
|
||||||
|
? html`<span slot="overline">${this.placeholder}</span>`
|
||||||
|
: nothing;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-combo-box-item .disabled=${this.disabled} type="button" compact>
|
<ha-combo-box-item .disabled=${this.disabled} type="button" compact>
|
||||||
${this.value
|
${this.value
|
||||||
? this.valueRenderer
|
? this.valueRenderer
|
||||||
? this.valueRenderer(this.value)
|
? html`${placeholder}${this.valueRenderer(this.value)}`
|
||||||
: html`<slot name="headline">${this.value}</slot>`
|
: html`${placeholder}<span slot="headline">${this.value}</span>`
|
||||||
: html`
|
: html`
|
||||||
<span slot="headline" class="placeholder">
|
<span slot="headline" class="placeholder">
|
||||||
${this.placeholder}
|
${this.placeholder}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { fullEntitiesContext } from "../../data/context";
|
|||||||
import {
|
import {
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
type EntityRegistryEntry,
|
type EntityRegistryEntry,
|
||||||
} from "../../data/entity_registry";
|
} from "../../data/entity/entity_registry";
|
||||||
import type { Action } from "../../data/script";
|
import type { Action } from "../../data/script";
|
||||||
import { migrateAutomationAction } from "../../data/script";
|
import { migrateAutomationAction } from "../../data/script";
|
||||||
import type { ActionSelector } from "../../data/selector";
|
import type { ActionSelector } from "../../data/selector";
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export class HaAddonSelector extends LitElement {
|
|||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
allow-custom-entity
|
|
||||||
></ha-addon-picker>`;
|
></ha-addon-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
|
||||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { EntitySources } from "../../data/entity_sources";
|
|
||||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
|
||||||
import type { AreaSelector } from "../../data/selector";
|
|
||||||
import type { ConfigEntry } from "../../data/config_entries";
|
import type { ConfigEntry } from "../../data/config_entries";
|
||||||
import { getConfigEntries } from "../../data/config_entries";
|
import { getConfigEntries } from "../../data/config_entries";
|
||||||
|
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||||
|
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||||
|
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||||
|
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||||
|
import type { AreaSelector } from "../../data/selector";
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export class HaConfigEntrySelector extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.integration=${this.selector.config_entry?.integration}
|
.integration=${this.selector.config_entry?.integration}
|
||||||
allow-custom-entity
|
|
||||||
></ha-config-entry-picker>`;
|
></ha-config-entry-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
|
||||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
|
||||||
import type { EntitySources } from "../../data/entity_sources";
|
|
||||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
|
||||||
import type { DeviceSelector } from "../../data/selector";
|
|
||||||
import type { ConfigEntry } from "../../data/config_entries";
|
import type { ConfigEntry } from "../../data/config_entries";
|
||||||
import { getConfigEntries } from "../../data/config_entries";
|
import { getConfigEntries } from "../../data/config_entries";
|
||||||
|
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||||
|
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||||
|
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||||
|
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||||
|
import type { DeviceSelector } from "../../data/selector";
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
@@ -107,7 +107,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
allow-custom-entity
|
|
||||||
></ha-device-picker>
|
></ha-device-picker>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { EntitySources } from "../../data/entity_sources";
|
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||||
import type { EntitySelector } from "../../data/selector";
|
import type { EntitySelector } from "../../data/selector";
|
||||||
import {
|
import {
|
||||||
filterSelectorEntities,
|
|
||||||
computeCreateDomains,
|
computeCreateDomains,
|
||||||
|
filterSelectorEntities,
|
||||||
} from "../../data/selector";
|
} from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../entity/ha-entities-picker";
|
import "../entity/ha-entities-picker";
|
||||||
@@ -74,7 +74,6 @@ export class HaEntitySelector extends LitElement {
|
|||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
allow-custom-entity
|
|
||||||
></ha-entity-picker>`;
|
></ha-entity-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
|
||||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { EntitySources } from "../../data/entity_sources";
|
|
||||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
|
||||||
import type { FloorSelector } from "../../data/selector";
|
|
||||||
import type { ConfigEntry } from "../../data/config_entries";
|
import type { ConfigEntry } from "../../data/config_entries";
|
||||||
import { getConfigEntries } from "../../data/config_entries";
|
import { getConfigEntries } from "../../data/config_entries";
|
||||||
|
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||||
|
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||||
|
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||||
|
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||||
|
import type { FloorSelector } from "../../data/selector";
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import { css, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
import { getDeviceIntegrationLookup } from "../../data/device/device_registry";
|
||||||
import type { EntitySources } from "../../data/entity_sources";
|
import type { EntitySources } from "../../data/entity/entity_sources";
|
||||||
import { fetchEntitySourcesWithCache } from "../../data/entity_sources";
|
import { fetchEntitySourcesWithCache } from "../../data/entity/entity_sources";
|
||||||
import type { TargetSelector } from "../../data/selector";
|
import type { TargetSelector } from "../../data/selector";
|
||||||
import {
|
import {
|
||||||
|
computeCreateDomains,
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
computeCreateDomains,
|
|
||||||
} from "../../data/selector";
|
} from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-target-picker";
|
import "../ha-target-picker";
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ interface ServiceComboBoxItem extends PickerComboBoxItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SEARCH_KEYS = [
|
const SEARCH_KEYS = [
|
||||||
{ name: "name", weight: 10 },
|
{ name: "search_labels.name", weight: 10 },
|
||||||
{ name: "description", weight: 8 },
|
{ name: "search_labels.description", weight: 8 },
|
||||||
{ name: "domainName", weight: 6 },
|
{ name: "search_labels.domainName", weight: 6 },
|
||||||
{ name: "serviceId", weight: 3 },
|
{ name: "search_labels.serviceId", weight: 3 },
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("ha-service-picker")
|
@customElement("ha-service-picker")
|
||||||
@@ -135,7 +135,6 @@ class HaServicePicker extends LitElement {
|
|||||||
<ha-generic-picker
|
<ha-generic-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.autofocus=${this.autofocus}
|
.autofocus=${this.autofocus}
|
||||||
allow-custom-value
|
|
||||||
.notFoundLabel=${this.hass.localize(
|
.notFoundLabel=${this.hass.localize(
|
||||||
"ui.components.service-picker.no_match"
|
"ui.components.service-picker.no_match"
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,32 +13,30 @@ import memoizeOne from "memoize-one";
|
|||||||
import { ensureArray } from "../common/array/ensure-array";
|
import { ensureArray } from "../common/array/ensure-array";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
||||||
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
import {
|
import {
|
||||||
areaFloorComboBoxKeys,
|
areaFloorComboBoxKeys,
|
||||||
getAreasAndFloors,
|
getAreasAndFloors,
|
||||||
type AreaFloorValue,
|
type AreaFloorValue,
|
||||||
type FloorComboBoxItem,
|
type FloorComboBoxItem,
|
||||||
} from "../data/area_floor";
|
} from "../data/area_floor_picker";
|
||||||
import { getConfigEntries, type ConfigEntry } from "../data/config_entries";
|
import { getConfigEntries, type ConfigEntry } from "../data/config_entries";
|
||||||
import { labelsContext } from "../data/context";
|
import { labelsContext } from "../data/context";
|
||||||
import {
|
import {
|
||||||
deviceComboBoxKeys,
|
deviceComboBoxKeys,
|
||||||
getDevices,
|
getDevices,
|
||||||
type DevicePickerItem,
|
type DevicePickerItem,
|
||||||
} from "../data/device_registry";
|
} from "../data/device/device_picker";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "../data/entity";
|
import type { HaEntityPickerEntityFilterFunc } from "../data/entity/entity";
|
||||||
import {
|
import {
|
||||||
entityComboBoxKeys,
|
entityComboBoxKeys,
|
||||||
getEntities,
|
getEntities,
|
||||||
type EntityComboBoxItem,
|
type EntityComboBoxItem,
|
||||||
} from "../data/entity_registry";
|
} from "../data/entity/entity_picker";
|
||||||
import { domainToName } from "../data/integration";
|
import { domainToName } from "../data/integration";
|
||||||
import {
|
import { getLabels, labelComboBoxKeys } from "../data/label/label_picker";
|
||||||
getLabels,
|
import type { LabelRegistryEntry } from "../data/label/label_registry";
|
||||||
labelComboBoxKeys,
|
|
||||||
type LabelRegistryEntry,
|
|
||||||
} from "../data/label_registry";
|
|
||||||
import {
|
import {
|
||||||
areaMeetsFilter,
|
areaMeetsFilter,
|
||||||
deviceMeetsFilter,
|
deviceMeetsFilter,
|
||||||
@@ -745,7 +743,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
: undefined,
|
: undefined,
|
||||||
undefined,
|
undefined,
|
||||||
`entity${SEPARATOR}`
|
`entity${SEPARATOR}`
|
||||||
);
|
).sort(this._sortBySortingLabel);
|
||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
entityItems = this._filterGroup(
|
entityItems = this._filterGroup(
|
||||||
@@ -778,7 +776,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
: undefined,
|
: undefined,
|
||||||
undefined,
|
undefined,
|
||||||
`device${SEPARATOR}`
|
`device${SEPARATOR}`
|
||||||
);
|
).sort(this._sortBySortingLabel);
|
||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
deviceItems = this._filterGroup(
|
deviceItems = this._filterGroup(
|
||||||
@@ -864,7 +862,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
entityFilter,
|
entityFilter,
|
||||||
targetValue?.label_id ? ensureArray(targetValue.label_id) : undefined,
|
targetValue?.label_id ? ensureArray(targetValue.label_id) : undefined,
|
||||||
`label${SEPARATOR}`
|
`label${SEPARATOR}`
|
||||||
);
|
).sort(this._sortBySortingLabel);
|
||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
labels = this._filterGroup(
|
labels = this._filterGroup(
|
||||||
@@ -964,10 +962,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
let hasFloor = false;
|
let hasFloor = false;
|
||||||
let rtl = false;
|
let rtl = false;
|
||||||
let showEntityId = false;
|
let showEntityId = false;
|
||||||
|
|
||||||
if (type === "area" || type === "floor") {
|
if (type === "area" || type === "floor") {
|
||||||
item.id = item[type]?.[`${type}_id`];
|
|
||||||
|
|
||||||
rtl = computeRTL(this.hass);
|
rtl = computeRTL(this.hass);
|
||||||
hasFloor =
|
hasFloor =
|
||||||
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
||||||
@@ -1073,6 +1068,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
term: html`<b>‘${search}’</b>`,
|
term: html`<b>‘${search}’</b>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private _sortBySortingLabel = (entityA, entityB) =>
|
||||||
|
caseInsensitiveStringCompare(
|
||||||
|
(entityA as PickerComboBoxItem).sorting_label!,
|
||||||
|
(entityB as PickerComboBoxItem).sorting_label!,
|
||||||
|
this.hass?.locale.language ?? navigator.language
|
||||||
|
);
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.add-target-wrapper {
|
.add-target-wrapper {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export class HaToast extends Snackbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mdc-snackbar {
|
.mdc-snackbar {
|
||||||
|
z-index: 10;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
right: calc(8px + var(--safe-area-inset-right));
|
right: calc(8px + var(--safe-area-inset-right));
|
||||||
bottom: calc(8px + var(--safe-area-inset-bottom));
|
bottom: calc(8px + var(--safe-area-inset-bottom));
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ class HaEntityMarker extends LitElement {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 48px;
|
width: var(--ha-marker-size, 48px);
|
||||||
height: 48px;
|
height: var(--ha-marker-size, 48px);
|
||||||
font-size: var(--ha-marker-font-size, var(--ha-font-size-xl));
|
font-size: var(--ha-marker-font-size, var(--ha-font-size-xl));
|
||||||
border-radius: var(--ha-marker-border-radius, 50%);
|
border-radius: var(--ha-marker-border-radius, 50%);
|
||||||
border: 1px solid var(--ha-marker-color, var(--primary-color));
|
border: 1px solid var(--ha-marker-color, var(--primary-color));
|
||||||
|
|||||||
@@ -560,10 +560,11 @@ export class HaMap extends ReactiveElement {
|
|||||||
radius,
|
radius,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const markerIconSize = this._getMarkerSize(computedStyles) / 2;
|
||||||
const marker = new DecoratedMarker([latitude, longitude], circle, {
|
const marker = new DecoratedMarker([latitude, longitude], circle, {
|
||||||
icon: Leaflet.divIcon({
|
icon: Leaflet.divIcon({
|
||||||
html: iconHTML,
|
html: iconHTML,
|
||||||
iconSize: [24, 24],
|
iconSize: [markerIconSize, markerIconSize],
|
||||||
className,
|
className,
|
||||||
}),
|
}),
|
||||||
interactive: this.interactiveZones,
|
interactive: this.interactiveZones,
|
||||||
@@ -618,10 +619,11 @@ export class HaMap extends ReactiveElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create marker with the icon
|
// create marker with the icon
|
||||||
|
const markerSize = this._getMarkerSize(computedStyles);
|
||||||
const marker = new DecoratedMarker([latitude, longitude], undefined, {
|
const marker = new DecoratedMarker([latitude, longitude], undefined, {
|
||||||
icon: Leaflet.divIcon({
|
icon: Leaflet.divIcon({
|
||||||
html: entityMarker,
|
html: entityMarker,
|
||||||
iconSize: [48, 48],
|
iconSize: [markerSize, markerSize],
|
||||||
className: "",
|
className: "",
|
||||||
}),
|
}),
|
||||||
title: title,
|
title: title,
|
||||||
@@ -657,6 +659,13 @@ export class HaMap extends ReactiveElement {
|
|||||||
this._mapZones.forEach((marker) => map.addLayer(marker));
|
this._mapZones.forEach((marker) => map.addLayer(marker));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getMarkerSize(computedStyles: CSSStyleDeclaration): number {
|
||||||
|
const markerSizeVarValue =
|
||||||
|
computedStyles.getPropertyValue("--ha-marker-size");
|
||||||
|
const parsed = parseFloat(markerSizeVarValue);
|
||||||
|
return Number.isNaN(parsed) ? 48 : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
private async _attachObserver(): Promise<void> {
|
private async _attachObserver(): Promise<void> {
|
||||||
if (!this._resizeObserver) {
|
if (!this._resizeObserver) {
|
||||||
this._resizeObserver = new ResizeObserver(() => {
|
this._resizeObserver = new ResizeObserver(() => {
|
||||||
@@ -739,14 +748,19 @@ export class HaMap extends ReactiveElement {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-icon {
|
||||||
|
--mdc-icon-size: calc(var(--ha-marker-size, 48px) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
.marker-cluster div {
|
.marker-cluster div {
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
border: 3px solid rgba(var(--rgb-primary-color), 0.2);
|
border: 3px solid rgba(var(--rgb-primary-color), 0.2);
|
||||||
width: 32px;
|
width: calc(var(--ha-marker-size, 48px) * 0.667);
|
||||||
height: 32px;
|
height: calc(var(--ha-marker-size, 48px) * 0.667);
|
||||||
border-radius: var(--ha-border-radius-2xl);
|
border-radius: 50%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
align-content: center;
|
||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
font-size: var(--ha-font-size-m);
|
font-size: var(--ha-font-size-m);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||||
import { extractApiErrorMessage } from "../../data/hassio/common";
|
import { extractApiErrorMessage } from "../../data/hassio/common";
|
||||||
import {
|
import {
|
||||||
type MediaPlayerEntity,
|
type MediaPlayerEntity,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { until } from "lit/directives/until";
|
|||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { slugify } from "../../common/string/slugify";
|
import { slugify } from "../../common/string/slugify";
|
||||||
import { debounce } from "../../common/util/debounce";
|
import { debounce } from "../../common/util/debounce";
|
||||||
import { isUnavailableState } from "../../data/entity";
|
import { isUnavailableState } from "../../data/entity/entity";
|
||||||
import type {
|
import type {
|
||||||
MediaPickedEvent,
|
MediaPickedEvent,
|
||||||
MediaPlayerBrowseAction,
|
MediaPlayerBrowseAction,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity";
|
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
|
||||||
import type { TargetType } from "../../../data/target";
|
import type { TargetType } from "../../../data/target";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||||
import type { TargetType, TargetTypeFloorless } from "../../data/target";
|
import type { TargetType, TargetTypeFloorless } from "../../data/target";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ import { computeRTL } from "../../common/util/compute_rtl";
|
|||||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||||
import { getConfigEntry } from "../../data/config_entries";
|
import { getConfigEntry } from "../../data/config_entries";
|
||||||
import { labelsContext } from "../../data/context";
|
import { labelsContext } from "../../data/context";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../data/device/device_registry";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||||
import {
|
import {
|
||||||
areaMeetsFilter,
|
areaMeetsFilter,
|
||||||
deviceMeetsFilter,
|
deviceMeetsFilter,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { slugify } from "../../common/string/slugify";
|
|||||||
import { getConfigEntry } from "../../data/config_entries";
|
import { getConfigEntry } from "../../data/config_entries";
|
||||||
import { labelsContext } from "../../data/context";
|
import { labelsContext } from "../../data/context";
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||||
import type { TargetType } from "../../data/target";
|
import type { TargetType } from "../../data/target";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { brandsUrl } from "../../util/brands-url";
|
import { brandsUrl } from "../../util/brands-url";
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export class HaTraceBlueprintConfig extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-code-editor
|
<ha-code-editor
|
||||||
.value=${dump(this.trace.blueprint_inputs || "").trimRight()}
|
.value=${dump(this.trace.blueprint_inputs || "").trimRight()}
|
||||||
|
.hass=${this.hass}
|
||||||
read-only
|
read-only
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
></ha-code-editor>
|
></ha-code-editor>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export class HaTraceConfig extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-code-editor
|
<ha-code-editor
|
||||||
.value=${dump(this.trace.config).trimRight()}
|
.value=${dump(this.trace.config).trimRight()}
|
||||||
|
.hass=${this.hass}
|
||||||
read-only
|
read-only
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
></ha-code-editor>
|
></ha-code-editor>
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { dump } from "js-yaml";
|
|
||||||
import { consume } from "@lit/context";
|
import { consume } from "@lit/context";
|
||||||
|
import { dump } from "js-yaml";
|
||||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||||
import "../ha-code-editor";
|
import { describeCondition, describeTrigger } from "../../data/automation_i18n";
|
||||||
import "../ha-icon-button";
|
import { fullEntitiesContext, labelsContext } from "../../data/context";
|
||||||
import "./hat-logbook-note";
|
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||||
|
import type { LabelRegistryEntry } from "../../data/label/label_registry";
|
||||||
import type { LogbookEntry } from "../../data/logbook";
|
import type { LogbookEntry } from "../../data/logbook";
|
||||||
|
import { describeAction } from "../../data/script_i18n";
|
||||||
import type {
|
import type {
|
||||||
ActionTraceStep,
|
ActionTraceStep,
|
||||||
ChooseActionTraceStep,
|
ChooseActionTraceStep,
|
||||||
@@ -16,19 +18,12 @@ import type {
|
|||||||
} from "../../data/trace";
|
} from "../../data/trace";
|
||||||
import { getDataFromPath } from "../../data/trace";
|
import { getDataFromPath } from "../../data/trace";
|
||||||
import "../../panels/logbook/ha-logbook-renderer";
|
import "../../panels/logbook/ha-logbook-renderer";
|
||||||
import { traceTabStyles } from "./trace-tab-styles";
|
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-code-editor";
|
||||||
|
import "../ha-icon-button";
|
||||||
|
import "./hat-logbook-note";
|
||||||
import type { NodeInfo } from "./hat-script-graph";
|
import type { NodeInfo } from "./hat-script-graph";
|
||||||
import { describeCondition, describeTrigger } from "../../data/automation_i18n";
|
import { traceTabStyles } from "./trace-tab-styles";
|
||||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
|
||||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
|
||||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
|
||||||
import {
|
|
||||||
floorsContext,
|
|
||||||
fullEntitiesContext,
|
|
||||||
labelsContext,
|
|
||||||
} from "../../data/context";
|
|
||||||
import { describeAction } from "../../data/script_i18n";
|
|
||||||
|
|
||||||
const TRACE_PATH_TABS = [
|
const TRACE_PATH_TABS = [
|
||||||
"step_config",
|
"step_config",
|
||||||
@@ -63,10 +58,6 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
@consume({ context: labelsContext, subscribe: true })
|
@consume({ context: labelsContext, subscribe: true })
|
||||||
_labelReg!: LabelRegistryEntry[];
|
_labelReg!: LabelRegistryEntry[];
|
||||||
|
|
||||||
@state()
|
|
||||||
@consume({ context: floorsContext, subscribe: true })
|
|
||||||
_floorReg!: Record<string, FloorRegistryEntry>;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="padded-box trace-info">
|
<div class="padded-box trace-info">
|
||||||
@@ -193,8 +184,6 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
${describeAction(
|
${describeAction(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entityReg,
|
this._entityReg,
|
||||||
this._labelReg,
|
|
||||||
this._floorReg,
|
|
||||||
currentDetail
|
currentDetail
|
||||||
)}
|
)}
|
||||||
</h2>`
|
</h2>`
|
||||||
@@ -278,6 +267,7 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
return config
|
return config
|
||||||
? html`<ha-code-editor
|
? html`<ha-code-editor
|
||||||
.value=${dump(config).trimEnd()}
|
.value=${dump(config).trimEnd()}
|
||||||
|
.hass=${this.hass}
|
||||||
read-only
|
read-only
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
></ha-code-editor>`
|
></ha-code-editor>`
|
||||||
|
|||||||
@@ -15,19 +15,13 @@ import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_tim
|
|||||||
import { relativeTime } from "../../common/datetime/relative_time";
|
import { relativeTime } from "../../common/datetime/relative_time";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { toggleAttribute } from "../../common/dom/toggle_attribute";
|
import { toggleAttribute } from "../../common/dom/toggle_attribute";
|
||||||
import {
|
import { fullEntitiesContext } from "../../data/context";
|
||||||
floorsContext,
|
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||||
fullEntitiesContext,
|
|
||||||
labelsContext,
|
|
||||||
} from "../../data/context";
|
|
||||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
|
||||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
|
||||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
|
||||||
import type { LogbookEntry } from "../../data/logbook";
|
import type { LogbookEntry } from "../../data/logbook";
|
||||||
import type {
|
import type {
|
||||||
ChooseAction,
|
ChooseAction,
|
||||||
Option,
|
|
||||||
IfAction,
|
IfAction,
|
||||||
|
Option,
|
||||||
ParallelAction,
|
ParallelAction,
|
||||||
RepeatAction,
|
RepeatAction,
|
||||||
SequenceAction,
|
SequenceAction,
|
||||||
@@ -197,8 +191,6 @@ class ActionRenderer {
|
|||||||
constructor(
|
constructor(
|
||||||
private hass: HomeAssistant,
|
private hass: HomeAssistant,
|
||||||
private entityReg: EntityRegistryEntry[],
|
private entityReg: EntityRegistryEntry[],
|
||||||
private labelReg: LabelRegistryEntry[],
|
|
||||||
private floorReg: Record<string, FloorRegistryEntry>,
|
|
||||||
private entries: TemplateResult[],
|
private entries: TemplateResult[],
|
||||||
private trace: AutomationTraceExtended,
|
private trace: AutomationTraceExtended,
|
||||||
private logbookRenderer: LogbookRenderer,
|
private logbookRenderer: LogbookRenderer,
|
||||||
@@ -313,14 +305,7 @@ class ActionRenderer {
|
|||||||
|
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
path,
|
path,
|
||||||
describeAction(
|
describeAction(this.hass, this.entityReg, data, actionType),
|
||||||
this.hass,
|
|
||||||
this.entityReg,
|
|
||||||
this.labelReg,
|
|
||||||
this.floorReg,
|
|
||||||
data,
|
|
||||||
actionType
|
|
||||||
),
|
|
||||||
undefined,
|
undefined,
|
||||||
data.enabled === false
|
data.enabled === false
|
||||||
);
|
);
|
||||||
@@ -485,13 +470,7 @@ class ActionRenderer {
|
|||||||
|
|
||||||
const name =
|
const name =
|
||||||
repeatConfig.alias ||
|
repeatConfig.alias ||
|
||||||
describeAction(
|
describeAction(this.hass, this.entityReg, repeatConfig);
|
||||||
this.hass,
|
|
||||||
this.entityReg,
|
|
||||||
this.labelReg,
|
|
||||||
this.floorReg,
|
|
||||||
repeatConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
this._renderEntry(repeatPath, name, undefined, disabled);
|
this._renderEntry(repeatPath, name, undefined, disabled);
|
||||||
|
|
||||||
@@ -585,14 +564,7 @@ class ActionRenderer {
|
|||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
sequencePath,
|
sequencePath,
|
||||||
sequenceConfig.alias ||
|
sequenceConfig.alias ||
|
||||||
describeAction(
|
describeAction(this.hass, this.entityReg, sequenceConfig, "sequence"),
|
||||||
this.hass,
|
|
||||||
this.entityReg,
|
|
||||||
this.labelReg,
|
|
||||||
this.floorReg,
|
|
||||||
sequenceConfig,
|
|
||||||
"sequence"
|
|
||||||
),
|
|
||||||
undefined,
|
undefined,
|
||||||
sequenceConfig.enabled === false
|
sequenceConfig.enabled === false
|
||||||
);
|
);
|
||||||
@@ -683,14 +655,6 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
_entityReg!: EntityRegistryEntry[];
|
_entityReg!: EntityRegistryEntry[];
|
||||||
|
|
||||||
@state()
|
|
||||||
@consume({ context: labelsContext, subscribe: true })
|
|
||||||
_labelReg!: LabelRegistryEntry[];
|
|
||||||
|
|
||||||
@state()
|
|
||||||
@consume({ context: floorsContext, subscribe: true })
|
|
||||||
_floorReg!: Record<string, FloorRegistryEntry>;
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.trace) {
|
if (!this.trace) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -707,8 +671,6 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
const actionRenderer = new ActionRenderer(
|
const actionRenderer = new ActionRenderer(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entityReg,
|
this._entityReg,
|
||||||
this._labelReg,
|
|
||||||
this._floorReg,
|
|
||||||
entries,
|
entries,
|
||||||
this.trace,
|
this.trace,
|
||||||
logbookRenderer,
|
logbookRenderer,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type {
|
|||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { getExtendedEntityRegistryEntry } from "./entity_registry";
|
import { getExtendedEntityRegistryEntry } from "./entity/entity_registry";
|
||||||
|
|
||||||
export const FORMAT_TEXT = "text";
|
export const FORMAT_TEXT = "text";
|
||||||
export const FORMAT_NUMBER = "number";
|
export const FORMAT_NUMBER = "number";
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import {
|
|||||||
getDeviceEntityDisplayLookup,
|
getDeviceEntityDisplayLookup,
|
||||||
type DeviceEntityDisplayLookup,
|
type DeviceEntityDisplayLookup,
|
||||||
type DeviceRegistryEntry,
|
type DeviceRegistryEntry,
|
||||||
} from "./device_registry";
|
} from "./device/device_registry";
|
||||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
import type { HaEntityPickerEntityFilterFunc } from "./entity/entity";
|
||||||
import type { EntityRegistryDisplayEntry } from "./entity_registry";
|
import type { EntityRegistryDisplayEntry } from "./entity/entity_registry";
|
||||||
import type { FloorRegistryEntry } from "./floor_registry";
|
import type { FloorRegistryEntry } from "./floor_registry";
|
||||||
|
|
||||||
export interface FloorComboBoxItem extends PickerComboBoxItem {
|
export interface FloorComboBoxItem extends PickerComboBoxItem {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { DeviceRegistryEntry } from "./device_registry";
|
import type { DeviceRegistryEntry } from "./device/device_registry";
|
||||||
import type {
|
import type {
|
||||||
EntityRegistryDisplayEntry,
|
EntityRegistryDisplayEntry,
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
} from "./entity_registry";
|
} from "./entity/entity_registry";
|
||||||
import type { RegistryEntry } from "./registry";
|
import type { RegistryEntry } from "./registry";
|
||||||
|
|
||||||
export { subscribeAreaRegistry } from "./ws-area_registry";
|
export { subscribeAreaRegistry } from "./ws-area_registry";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { HomeAssistant } from "../types";
|
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import { UNAVAILABLE } from "./entity";
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { UNAVAILABLE } from "./entity/entity";
|
||||||
|
|
||||||
export const enum AssistSatelliteEntityFeature {
|
export const enum AssistSatelliteEntityFeature {
|
||||||
ANNOUNCE = 1,
|
ANNOUNCE = 1,
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import type { Context, HomeAssistant } from "../types";
|
|||||||
import type { BlueprintInput } from "./blueprint";
|
import type { BlueprintInput } from "./blueprint";
|
||||||
import type { ConditionDescription } from "./condition";
|
import type { ConditionDescription } from "./condition";
|
||||||
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
||||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
import type {
|
||||||
|
DeviceCondition,
|
||||||
|
DeviceTrigger,
|
||||||
|
} from "./device/device_automation";
|
||||||
import type { Action, Field, MODES } from "./script";
|
import type { Action, Field, MODES } from "./script";
|
||||||
import { migrateAutomationAction } from "./script";
|
import { migrateAutomationAction } from "./script";
|
||||||
import type { TriggerDescription } from "./trigger";
|
import type { TriggerDescription } from "./trigger";
|
||||||
|
|||||||
@@ -26,12 +26,15 @@ import type {
|
|||||||
Trigger,
|
Trigger,
|
||||||
} from "./automation";
|
} from "./automation";
|
||||||
import { getConditionDomain, getConditionObjectId } from "./condition";
|
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
import type {
|
||||||
|
DeviceCondition,
|
||||||
|
DeviceTrigger,
|
||||||
|
} from "./device/device_automation";
|
||||||
import {
|
import {
|
||||||
localizeDeviceAutomationCondition,
|
localizeDeviceAutomationCondition,
|
||||||
localizeDeviceAutomationTrigger,
|
localizeDeviceAutomationTrigger,
|
||||||
} from "./device_automation";
|
} from "./device/device_automation";
|
||||||
import type { EntityRegistryEntry } from "./entity_registry";
|
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||||
import type { FrontendLocaleData } from "./translation";
|
import type { FrontendLocaleData } from "./translation";
|
||||||
import { getTriggerDomain, getTriggerObjectId, isTriggerList } from "./trigger";
|
import { getTriggerDomain, getTriggerObjectId, isTriggerList } from "./trigger";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { getColorByIndex } from "../common/color/colors";
|
|||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { isUnavailableState } from "./entity";
|
import { isUnavailableState } from "./entity/entity";
|
||||||
|
|
||||||
export interface Calendar {
|
export interface Calendar {
|
||||||
entity_id: string;
|
entity_id: string;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { createContext } from "@lit/context";
|
import { createContext } from "@lit/context";
|
||||||
import type { HassConfig } from "home-assistant-js-websocket";
|
import type { HassConfig } from "home-assistant-js-websocket";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { EntityRegistryEntry } from "./entity_registry";
|
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||||
import type { LabelRegistryEntry } from "./label_registry";
|
import type { LabelRegistryEntry } from "./label/label_registry";
|
||||||
|
|
||||||
export const connectionContext =
|
export const connectionContext =
|
||||||
createContext<HomeAssistant["connection"]>("connection");
|
createContext<HomeAssistant["connection"]>("connection");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import { stateActive } from "../common/entity/state_active";
|
import { stateActive } from "../common/entity/state_active";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { UNAVAILABLE } from "./entity";
|
import { UNAVAILABLE } from "./entity/entity";
|
||||||
|
|
||||||
export const enum CoverEntityFeature {
|
export const enum CoverEntityFeature {
|
||||||
OPEN = 1,
|
OPEN = 1,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import type { HaFormSchema } from "../components/ha-form/types";
|
import type { HaFormSchema } from "../../components/ha-form/types";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import type { BaseTrigger } from "./automation";
|
import type { BaseTrigger } from "../automation";
|
||||||
import { migrateAutomationTrigger } from "./automation";
|
import { migrateAutomationTrigger } from "../automation";
|
||||||
import type { EntityRegistryEntry } from "./entity_registry";
|
import type { EntityRegistryEntry } from "../entity/entity_registry";
|
||||||
import {
|
import {
|
||||||
computeEntityRegistryName,
|
computeEntityRegistryName,
|
||||||
entityRegistryByEntityId,
|
entityRegistryByEntityId,
|
||||||
entityRegistryById,
|
entityRegistryById,
|
||||||
} from "./entity_registry";
|
} from "../entity/entity_registry";
|
||||||
|
|
||||||
export interface DeviceAutomation {
|
export interface DeviceAutomation {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
182
src/data/device/device_picker.ts
Normal file
182
src/data/device/device_picker.ts
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||||
|
import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name";
|
||||||
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
|
import { getDeviceContext } from "../../common/entity/context/get_device_context";
|
||||||
|
import type { HaDevicePickerDeviceFilterFunc } from "../../components/device/ha-device-picker";
|
||||||
|
import type { PickerComboBoxItem } from "../../components/ha-picker-combo-box";
|
||||||
|
import type { FuseWeightedKey } from "../../resources/fuseMultiTerm";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import type { ConfigEntry } from "../config_entries";
|
||||||
|
import type { HaEntityPickerEntityFilterFunc } from "../entity/entity";
|
||||||
|
import { domainToName } from "../integration";
|
||||||
|
import {
|
||||||
|
getDeviceEntityDisplayLookup,
|
||||||
|
type DeviceEntityDisplayLookup,
|
||||||
|
} from "./device_registry";
|
||||||
|
|
||||||
|
export interface DevicePickerItem extends PickerComboBoxItem {
|
||||||
|
domain?: string;
|
||||||
|
domain_name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deviceComboBoxKeys: FuseWeightedKey[] = [
|
||||||
|
{
|
||||||
|
name: "search_labels.deviceName",
|
||||||
|
weight: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_labels.areaName",
|
||||||
|
weight: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_labels.domainName",
|
||||||
|
weight: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_labels.domain",
|
||||||
|
weight: 4,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getDevices = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
configEntryLookup: Record<string, ConfigEntry>,
|
||||||
|
includeDomains?: string[],
|
||||||
|
excludeDomains?: string[],
|
||||||
|
includeDeviceClasses?: string[],
|
||||||
|
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
||||||
|
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||||
|
excludeDevices?: string[],
|
||||||
|
value?: string,
|
||||||
|
idPrefix = ""
|
||||||
|
): DevicePickerItem[] => {
|
||||||
|
const devices = Object.values(hass.devices);
|
||||||
|
const entities = Object.values(hass.entities);
|
||||||
|
|
||||||
|
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||||
|
|
||||||
|
if (
|
||||||
|
includeDomains ||
|
||||||
|
excludeDomains ||
|
||||||
|
includeDeviceClasses ||
|
||||||
|
entityFilter
|
||||||
|
) {
|
||||||
|
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputDevices = devices.filter(
|
||||||
|
(device) => device.id === value || !device.disabled_by
|
||||||
|
);
|
||||||
|
|
||||||
|
if (includeDomains) {
|
||||||
|
inputDevices = inputDevices.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deviceEntityLookup[device.id].some((entity) =>
|
||||||
|
includeDomains.includes(computeDomain(entity.entity_id))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludeDomains) {
|
||||||
|
inputDevices = inputDevices.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return entities.every(
|
||||||
|
(entity) => !excludeDomains.includes(computeDomain(entity.entity_id))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludeDevices) {
|
||||||
|
inputDevices = inputDevices.filter(
|
||||||
|
(device) => !excludeDevices!.includes(device.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeDeviceClasses) {
|
||||||
|
inputDevices = inputDevices.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deviceEntityLookup[device.id].some((entity) => {
|
||||||
|
const stateObj = hass.states[entity.entity_id];
|
||||||
|
if (!stateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
stateObj.attributes.device_class &&
|
||||||
|
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entityFilter) {
|
||||||
|
inputDevices = inputDevices.filter((device) => {
|
||||||
|
const devEntities = deviceEntityLookup[device.id];
|
||||||
|
if (!devEntities || !devEntities.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return devEntities.some((entity) => {
|
||||||
|
const stateObj = hass.states[entity.entity_id];
|
||||||
|
if (!stateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return entityFilter(stateObj);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceFilter) {
|
||||||
|
inputDevices = inputDevices.filter(
|
||||||
|
(device) =>
|
||||||
|
// We always want to include the device of the current value
|
||||||
|
device.id === value || deviceFilter!(device)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputDevices = inputDevices.map<DevicePickerItem>((device) => {
|
||||||
|
const deviceName = computeDeviceNameDisplay(
|
||||||
|
device,
|
||||||
|
hass,
|
||||||
|
deviceEntityLookup[device.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { area } = getDeviceContext(device, hass);
|
||||||
|
|
||||||
|
const areaName = area ? computeAreaName(area) : undefined;
|
||||||
|
|
||||||
|
const configEntry = device.primary_config_entry
|
||||||
|
? configEntryLookup?.[device.primary_config_entry]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const domain = configEntry?.domain;
|
||||||
|
const domainName = domain ? domainToName(hass.localize, domain) : undefined;
|
||||||
|
const primary =
|
||||||
|
deviceName || hass.localize("ui.components.device-picker.unnamed_device");
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `${idPrefix}${device.id}`,
|
||||||
|
label: "",
|
||||||
|
primary,
|
||||||
|
secondary: areaName,
|
||||||
|
domain: configEntry?.domain,
|
||||||
|
domain_name: domainName,
|
||||||
|
search_labels: {
|
||||||
|
deviceName,
|
||||||
|
areaName: areaName || null,
|
||||||
|
domain: domain || null,
|
||||||
|
domainName: domainName || null,
|
||||||
|
},
|
||||||
|
sorting_label: [primary, areaName, domainName].filter(Boolean).join("_"),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return outputDevices;
|
||||||
|
};
|
||||||
169
src/data/device/device_registry.ts
Normal file
169
src/data/device/device_registry.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import type { ConfigEntry } from "../config_entries";
|
||||||
|
import type {
|
||||||
|
EntityRegistryDisplayEntry,
|
||||||
|
EntityRegistryEntry,
|
||||||
|
} from "../entity/entity_registry";
|
||||||
|
import type { EntitySources } from "../entity/entity_sources";
|
||||||
|
import type { RegistryEntry } from "../registry";
|
||||||
|
|
||||||
|
export {
|
||||||
|
fetchDeviceRegistry,
|
||||||
|
subscribeDeviceRegistry,
|
||||||
|
} from "../ws-device_registry";
|
||||||
|
|
||||||
|
export interface DeviceRegistryEntry extends RegistryEntry {
|
||||||
|
id: string;
|
||||||
|
config_entries: string[];
|
||||||
|
config_entries_subentries: Record<string, (string | null)[]>;
|
||||||
|
connections: [string, string][];
|
||||||
|
identifiers: [string, string][];
|
||||||
|
manufacturer: string | null;
|
||||||
|
model: string | null;
|
||||||
|
model_id: string | null;
|
||||||
|
name: string | null;
|
||||||
|
labels: string[];
|
||||||
|
sw_version: string | null;
|
||||||
|
hw_version: string | null;
|
||||||
|
serial_number: string | null;
|
||||||
|
via_device_id: string | null;
|
||||||
|
area_id: string | null;
|
||||||
|
name_by_user: string | null;
|
||||||
|
entry_type: "service" | null;
|
||||||
|
disabled_by: "user" | "integration" | "config_entry" | null;
|
||||||
|
configuration_url: string | null;
|
||||||
|
primary_config_entry: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeviceEntityDisplayLookup = Record<
|
||||||
|
string,
|
||||||
|
EntityRegistryDisplayEntry[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type DeviceEntityLookup<
|
||||||
|
T extends EntityRegistryEntry | EntityRegistryDisplayEntry =
|
||||||
|
| EntityRegistryEntry
|
||||||
|
| EntityRegistryDisplayEntry,
|
||||||
|
> = Record<string, T[]>;
|
||||||
|
|
||||||
|
export interface DeviceRegistryEntryMutableParams {
|
||||||
|
area_id?: string | null;
|
||||||
|
name_by_user?: string | null;
|
||||||
|
disabled_by?: string | null;
|
||||||
|
labels?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fallbackDeviceName = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entities: EntityRegistryEntry[] | EntityRegistryDisplayEntry[] | string[]
|
||||||
|
) => {
|
||||||
|
for (const entity of entities || []) {
|
||||||
|
const entityId = typeof entity === "string" ? entity : entity.entity_id;
|
||||||
|
const stateObj = hass.states[entityId];
|
||||||
|
if (stateObj) {
|
||||||
|
return computeStateName(stateObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const devicesInArea = (devices: DeviceRegistryEntry[], areaId: string) =>
|
||||||
|
devices.filter((device) => device.area_id === areaId);
|
||||||
|
|
||||||
|
export const updateDeviceRegistryEntry = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
deviceId: string,
|
||||||
|
updates: Partial<DeviceRegistryEntryMutableParams>
|
||||||
|
) =>
|
||||||
|
hass.callWS<DeviceRegistryEntry>({
|
||||||
|
type: "config/device_registry/update",
|
||||||
|
device_id: deviceId,
|
||||||
|
...updates,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeConfigEntryFromDevice = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
deviceId: string,
|
||||||
|
configEntryId: string
|
||||||
|
) =>
|
||||||
|
hass.callWS<DeviceRegistryEntry>({
|
||||||
|
type: "config/device_registry/remove_config_entry",
|
||||||
|
device_id: deviceId,
|
||||||
|
config_entry_id: configEntryId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sortDeviceRegistryByName = (
|
||||||
|
entries: DeviceRegistryEntry[],
|
||||||
|
language: string
|
||||||
|
) =>
|
||||||
|
entries.sort((entry1, entry2) =>
|
||||||
|
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getDeviceEntityLookup = (
|
||||||
|
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||||
|
): DeviceEntityLookup => {
|
||||||
|
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||||
|
for (const entity of entities) {
|
||||||
|
if (!entity.device_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(entity.device_id in deviceEntityLookup)) {
|
||||||
|
deviceEntityLookup[entity.device_id] = [];
|
||||||
|
}
|
||||||
|
deviceEntityLookup[entity.device_id].push(entity);
|
||||||
|
}
|
||||||
|
return deviceEntityLookup;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDeviceEntityDisplayLookup = (
|
||||||
|
entities: EntityRegistryDisplayEntry[]
|
||||||
|
): DeviceEntityDisplayLookup => {
|
||||||
|
const deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||||
|
for (const entity of entities) {
|
||||||
|
if (!entity.device_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(entity.device_id in deviceEntityLookup)) {
|
||||||
|
deviceEntityLookup[entity.device_id] = [];
|
||||||
|
}
|
||||||
|
deviceEntityLookup[entity.device_id].push(entity);
|
||||||
|
}
|
||||||
|
return deviceEntityLookup;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDeviceIntegrationLookup = (
|
||||||
|
entitySources: EntitySources,
|
||||||
|
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[],
|
||||||
|
devices?: DeviceRegistryEntry[],
|
||||||
|
configEntries?: ConfigEntry[]
|
||||||
|
): Record<string, Set<string>> => {
|
||||||
|
const deviceIntegrations: Record<string, Set<string>> = {};
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
const source = entitySources[entity.entity_id];
|
||||||
|
if (!source?.domain || entity.device_id === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceIntegrations[entity.device_id!] =
|
||||||
|
deviceIntegrations[entity.device_id!] || new Set<string>();
|
||||||
|
deviceIntegrations[entity.device_id!].add(source.domain);
|
||||||
|
}
|
||||||
|
// Lookup devices that have no entities
|
||||||
|
if (devices && configEntries) {
|
||||||
|
for (const device of devices) {
|
||||||
|
for (const config_entry_id of device.config_entries) {
|
||||||
|
const entry = configEntries.find((e) => e.entry_id === config_entry_id);
|
||||||
|
if (entry?.domain) {
|
||||||
|
deviceIntegrations[device.id] =
|
||||||
|
deviceIntegrations[device.id] || new Set<string>();
|
||||||
|
deviceIntegrations[device.id].add(entry.domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceIntegrations;
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user