Compare commits

..

7 Commits

Author SHA1 Message Date
Wendelin
043849b057 Group floor and area, add label icon, remove remove group 2025-09-12 14:33:41 +02:00
Wendelin
9a69566000 Fix sublist 2025-09-12 12:02:01 +02:00
Wendelin
9cdb57476a Merge branch 'dev' of github.com:home-assistant/frontend into target-selected-value 2025-09-11 15:59:07 +02:00
Wendelin
f8d90d003e fix entity domain name 2025-09-11 14:36:44 +02:00
Wendelin
f53ee52b0e Fix typo 2025-09-11 12:27:05 +02:00
Wendelin
f8cc1531e5 Use extractFromTarget 2025-09-11 12:25:02 +02:00
Wendelin
11f65ef0f7 Add new target selected value view 2025-09-09 15:38:48 +02:00
85 changed files with 1820 additions and 1684 deletions

View File

@@ -21,12 +21,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
with:
ref: dev
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -56,12 +56,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
with:
ref: master
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -37,7 +37,7 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
uses: actions/cache@v4.2.4
with:
path: |
node_modules/.cache/prettier
@@ -58,9 +58,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -76,9 +76,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -89,7 +89,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: frontend-bundle-stats
path: build/stats/*.json
@@ -100,9 +100,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -113,7 +113,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: supervisor-bundle-stats
path: build/stats/*.json

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0
uses: github/codeql-action/analyze@v3

View File

@@ -22,12 +22,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
with:
ref: dev
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -57,12 +57,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
with:
ref: master
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -16,10 +16,10 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -21,10 +21,10 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -10,6 +10,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Apply labels
uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
uses: actions/labeler@v6.0.1
with:
sync-labels: true

View File

@@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
- uses: dessant/lock-threads@v5.0.1
with:
github-token: ${{ github.token }}
process-only: "issues, prs"

View File

@@ -20,15 +20,15 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: translations
path: translations.tar.gz

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send bundle stats and build information to RelativeCI
uses: relative-ci/agent-action@1707825cbfcc7452b2913d273414705415ae64d4 # v3.0.1
uses: relative-ci/agent-action@v3.0.1
with:
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
token: ${{ github.token }}

View File

@@ -18,6 +18,6 @@ jobs:
pull-requests: read
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
- uses: release-drafter/release-drafter@v6.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -23,10 +23,10 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -34,7 +34,7 @@ jobs:
uses: home-assistant/actions/helpers/verify-version@master
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -55,7 +55,7 @@ jobs:
script/release
- name: Upload release assets
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
uses: softprops/action-gh-release@v2.3.3
with:
files: |
dist/*.whl
@@ -73,7 +73,6 @@ jobs:
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt
# home-assistant/wheels doesn't support SHA pinning
- name: Build wheels
uses: home-assistant/wheels@2025.07.0
with:
@@ -91,9 +90,9 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -108,7 +107,7 @@ jobs:
- name: Tar folder
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
- name: Upload release asset
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
uses: softprops/action-gh-release@v2.3.3
with:
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
@@ -120,9 +119,9 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@v5.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -137,6 +136,6 @@ jobs:
- name: Tar folder
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
- name: Upload release asset
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
uses: softprops/action-gh-release@v2.3.3
with:
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz

View File

@@ -12,7 +12,7 @@ jobs:
if: github.event.issue.type.name == 'Task'
steps:
- name: Check if user is authorized
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
script: |
const issueAuthor = context.payload.issue.user.login;

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
uses: actions/stale@v10.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v5.0.0
- name: Upload Translations
run: |

View File

@@ -6,23 +6,21 @@ A tooltip's target is its _first child element_, so you should only wrap one ele
Tooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout.
<ha-button id="hover">Hover Me</ha-button>
<ha-tooltip for="hover">
This is a tooltip
<ha-tooltip content="This is a tooltip">
<ha-button>Hover Me</ha-button>
</ha-tooltip>
```
<ha-button id="hover">Hover Me</ha-button>
<ha-tooltip for="hover">
This is a tooltip
<ha-tooltip content="This is a tooltip">
<ha-button>Hover Me</ha-button>
</ha-tooltip>
```
## Documentation
This element is based on webawesome `wa-tooltip` it only sets some css tokens and has a custom show/hide animation.
This element is based on shoelace `sl-tooltip` it only sets some css tokens and has a custom show/hide animation.
<a href="https://webawesome.com/docs/components/tooltip/" target="_blank" rel="noopener noreferrer">Webawesome documentation</a>
<a href="https://shoelace.style/components/tooltip" target="_blank" rel="noopener noreferrer">Shoelace documentation</a>
### HA style tokens
@@ -30,7 +28,7 @@ In your theme settings use this without the prefixed `--`.
- `--ha-tooltip-border-radius` (Default: 4px)
- `--ha-tooltip-arrow-size` (Default: 8px)
- `--wa-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
- `--sl-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
- `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`)
- `--wa-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
- `--wa-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)
- `--sl-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
- `--sl-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)

View File

@@ -199,7 +199,6 @@ class HassioAddonConfig extends LitElement {
<div class="card-content">
${showForm
? html`<ha-form
.hass=${this.hass}
.disabled=${this.disabled}
.data=${this._options!}
@value-changed=${this._configChanged}

View File

@@ -119,27 +119,26 @@ class HassioRepositoriesDialog extends LitElement {
<div>${repo.url}</div>
</div>
<ha-tooltip
.for="icon-button-${repo.slug}"
class="delete"
slot="end"
>
${this._dialogParams!.supervisor.localize(
.content=${this._dialogParams!.supervisor.localize(
usedRepositories.includes(repo.slug)
? "dialog.repositories.used"
: "dialog.repositories.remove"
)}
>
<div>
<ha-icon-button
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
</div>
</ha-tooltip>
<div .id="icon-button-${repo.slug}">
<ha-icon-button
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
</div>
</ha-md-list-item>
`
)

View File

@@ -3,7 +3,7 @@ import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { goBack, navigate } from "../../../src/common/navigate";
import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import { nextRender } from "../../../src/common/util/render-status";
import "../../../src/components/ha-icon-button";
@@ -193,7 +193,7 @@ class HassioIngressView extends LitElement {
title: addon.name,
});
await nextRender();
goBack();
history.back();
return;
}
@@ -275,7 +275,7 @@ class HassioIngressView extends LitElement {
title: addon.name,
});
await nextRender();
goBack();
history.back();
return;
}

View File

@@ -2,7 +2,6 @@ import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { Supervisor } from "../../../src/data/supervisor/supervisor";
import { goBack } from "../../../src/common/navigate";
import "../../../src/layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../src/types";
import "./update-available-card";
@@ -36,7 +35,7 @@ class UpdateAvailableDashboard extends LitElement {
}
private _updateComplete() {
goBack();
history.back();
}
static styles = css`

View File

@@ -51,7 +51,7 @@
"@fullcalendar/list": "6.1.19",
"@fullcalendar/luxon3": "6.1.19",
"@fullcalendar/timegrid": "6.1.19",
"@home-assistant/webawesome": "3.0.0-beta.4.ha.3",
"@home-assistant/webawesome": "3.0.0-beta.4.ha.2",
"@lezer/highlight": "1.2.1",
"@lit-labs/motion": "1.0.9",
"@lit-labs/observers": "2.0.6",
@@ -84,6 +84,7 @@
"@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47",
"@replit/codemirror-indentation-markers": "6.5.3",
"@shoelace-style/shoelace": "2.20.1",
"@swc/helpers": "0.5.17",
"@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "3.9.1",
@@ -111,7 +112,7 @@
"fuse.js": "7.1.0",
"google-timezones-json": "1.2.0",
"gulp-zopfli-green": "6.0.2",
"hls.js": "1.6.12",
"hls.js": "1.6.11",
"home-assistant-js-websocket": "9.5.0",
"idb-keyval": "6.2.2",
"intl-messageformat": "10.7.16",
@@ -135,7 +136,7 @@
"stacktrace-js": "2.0.2",
"superstruct": "2.0.2",
"tinykeys": "3.0.0",
"ua-parser-js": "2.0.5",
"ua-parser-js": "2.0.4",
"vue": "2.7.16",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
@@ -158,7 +159,7 @@
"@octokit/plugin-retry": "8.0.1",
"@octokit/rest": "22.0.0",
"@rsdoctor/rspack-plugin": "1.2.3",
"@rspack/core": "1.5.3",
"@rspack/core": "1.5.2",
"@rspack/dev-server": "1.1.4",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.24",
@@ -217,7 +218,7 @@
"terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.2",
"typescript-eslint": "8.43.0",
"typescript-eslint": "8.42.0",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
"webpack-stats-plugin": "1.1.3",
@@ -231,7 +232,7 @@
"clean-css": "5.3.3",
"@lit/reactive-element": "2.1.1",
"@fullcalendar/daygrid": "6.1.19",
"globals": "16.4.0",
"globals": "16.3.0",
"tslib": "2.8.1",
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch"
},

View File

@@ -63,21 +63,3 @@ export const navigate = async (
});
return true;
};
/**
* 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.
*/
export const goBack = (fallbackPath?: string) => {
const { history } = mainWindow;
// Check if we have history to go back to
if (history.length > 1) {
history.back();
return;
}
// No history available, navigate to fallback path
const fallback = fallbackPath || "/";
navigate(fallback, { replace: true });
};

View File

@@ -12,8 +12,9 @@ class HaDataTableIcon extends LitElement {
protected render(): TemplateResult {
return html`
<ha-tooltip for="svg-icon">${this.tooltip}</ha-tooltip>
<ha-svg-icon id="svg-icon" .path=${this.path}></ha-svg-icon>
<ha-tooltip .content=${this.tooltip}>
<ha-svg-icon .path=${this.path}></ha-svg-icon>
</ha-tooltip>
`;
}

View File

@@ -36,38 +36,39 @@ class StateInfo extends LitElement {
</div>
${this.inDialog
? html`<div class="time-ago">
<ha-tooltip for="relative-time">
<div class="row">
<span class="column-name">
${this.hass.localize(
"ui.dialogs.more_info_control.last_changed"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.dialogs.more_info_control.last_updated"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
capitalize
></ha-relative-time>
<ha-tooltip>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
<div slot="content">
<div class="row">
<span class="column-name">
${this.hass.localize(
"ui.dialogs.more_info_control.last_changed"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.dialogs.more_info_control.last_updated"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
capitalize
></ha-relative-time>
</div>
</div>
</ha-tooltip>
<ha-relative-time
id="relative-time"
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>`
: html`<div class="extra-info"><slot></slot></div>`}
</div>`;

View File

@@ -67,19 +67,20 @@ export class HaAnalytics extends LitElement {
)}
</span>
<span>
<ha-switch
.id="switch-${preference}"
@change=${this._handleRowClick}
.checked=${this.analytics?.preferences[preference]}
.preference=${preference}
name=${preference}
?disabled=${baseEnabled}
>
</ha-switch>
<ha-tooltip .for="switch-${preference}" placement="right">
${this.localize(
<ha-tooltip
content=${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
)}
placement="right"
?disabled=${baseEnabled}
>
<ha-switch
@change=${this._handleRowClick}
.checked=${this.analytics?.preferences[preference]}
.preference=${preference}
name=${preference}
>
</ha-switch>
</ha-tooltip>
</span>
</ha-settings-row>

View File

@@ -83,6 +83,15 @@ export class HaAutomationRow extends LitElement {
!(ev.ctrlKey || ev.metaKey) &&
!ev.shiftKey &&
(ev.key === "ArrowUp" || ev.key === "ArrowDown")
) &&
!(
(ev.ctrlKey || ev.metaKey) &&
!ev.shiftKey &&
!ev.altKey &&
(ev.key === "c" ||
ev.key === "x" ||
ev.key === "Delete" ||
ev.key === "Backspace")
)
) {
return;
@@ -103,6 +112,22 @@ export class HaAutomationRow extends LitElement {
return;
}
if (ev.ctrlKey || ev.metaKey) {
if (ev.key === "c") {
fireEvent(this, "copy-row");
return;
}
if (ev.key === "x") {
fireEvent(this, "cut-row");
return;
}
if (ev.key === "Delete" || ev.key === "Backspace") {
fireEvent(this, "delete-row");
return;
}
}
this.click();
}

View File

@@ -1,62 +1,262 @@
import { css, html, LitElement, type PropertyValues } from "lit";
import "@home-assistant/webawesome/dist/components/drawer/drawer";
import { customElement, property, state } from "lit/decorators";
import { css, html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
const ANIMATION_DURATION_MS = 300;
/**
* A bottom sheet component that slides up from the bottom of the screen.
*
* The bottom sheet provides a draggable interface that allows users to resize
* the sheet by dragging the handle at the top. It supports both mouse and touch
* interactions and automatically closes when dragged below a 20% of screen height.
*
* @fires bottom-sheet-closed - Fired when the bottom sheet is closed
*
* @cssprop --ha-bottom-sheet-border-width - Border width for the sheet
* @cssprop --ha-bottom-sheet-border-style - Border style for the sheet
* @cssprop --ha-bottom-sheet-border-color - Border color for the sheet
*/
@customElement("ha-bottom-sheet")
export class HaBottomSheet extends LitElement {
@property({ type: Boolean }) public open = false;
@query("dialog") private _dialog!: HTMLDialogElement;
@state() private _drawerOpen = false;
private _dragging = false;
private _handleAfterHide() {
this.open = false;
const ev = new Event("closed", {
bubbles: true,
composed: true,
});
this.dispatchEvent(ev);
private _dragStartY = 0;
private _initialSize = 0;
@state() private _dialogMaxViewpointHeight = 70;
@state() private _dialogMinViewpointHeight = 55;
@state() private _dialogViewportHeight?: number;
render() {
return html`<dialog
open
@transitionend=${this._handleTransitionEnd}
style=${styleMap({
height: this._dialogViewportHeight
? `${this._dialogViewportHeight}vh`
: "auto",
maxHeight: `${this._dialogMaxViewpointHeight}vh`,
minHeight: `${this._dialogMinViewpointHeight}vh`,
})}
>
<div class="handle-wrapper">
<div
@mousedown=${this._handleMouseDown}
@touchstart=${this._handleTouchStart}
class="handle"
></div>
</div>
<slot></slot>
</dialog>`;
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("open")) {
this._drawerOpen = this.open;
protected firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._openSheet();
}
private _openSheet() {
requestAnimationFrame(() => {
// trigger opening animation
this._dialog.classList.add("show");
});
}
public closeSheet() {
requestAnimationFrame(() => {
this._dialog.classList.remove("show");
});
}
private _handleTransitionEnd() {
if (this._dialog.classList.contains("show")) {
// after show animation is done
// - set the height to the natural height, to prevent content shift when switch content
// - set max height to 90vh, so it opens at max 70vh but can be resized to 90vh
this._dialogViewportHeight =
(this._dialog.offsetHeight / window.innerHeight) * 100;
this._dialogMaxViewpointHeight = 90;
this._dialogMinViewpointHeight = 20;
} else {
// after close animation is done close dialog element and fire closed event
this._dialog.close();
fireEvent(this, "bottom-sheet-closed");
}
}
render() {
return html`
<wa-drawer
placement="bottom"
.open=${this._drawerOpen}
@wa-after-hide=${this._handleAfterHide}
without-header
>
<slot></slot>
</wa-drawer>
`;
connectedCallback() {
super.connectedCallback();
// register event listeners for drag handling
document.addEventListener("mousemove", this._handleMouseMove);
document.addEventListener("mouseup", this._handleMouseUp);
document.addEventListener("touchmove", this._handleTouchMove, {
passive: false,
});
document.addEventListener("touchend", this._handleTouchEnd);
document.addEventListener("touchcancel", this._handleTouchEnd);
}
disconnectedCallback() {
super.disconnectedCallback();
// unregister event listeners for drag handling
document.removeEventListener("mousemove", this._handleMouseMove);
document.removeEventListener("mouseup", this._handleMouseUp);
document.removeEventListener("touchmove", this._handleTouchMove);
document.removeEventListener("touchend", this._handleTouchEnd);
document.removeEventListener("touchcancel", this._handleTouchEnd);
}
private _handleMouseDown = (ev: MouseEvent) => {
this._startDrag(ev.clientY);
};
private _handleTouchStart = (ev: TouchEvent) => {
// Prevent the browser from interpreting this as a scroll/PTR gesture.
ev.preventDefault();
this._startDrag(ev.touches[0].clientY);
};
private _startDrag(clientY: number) {
this._dragging = true;
this._dragStartY = clientY;
this._initialSize = (this._dialog.offsetHeight / window.innerHeight) * 100;
document.body.style.setProperty("cursor", "grabbing");
}
private _handleMouseMove = (ev: MouseEvent) => {
if (!this._dragging) {
return;
}
this._updateSize(ev.clientY);
};
private _handleTouchMove = (ev: TouchEvent) => {
if (!this._dragging) {
return;
}
ev.preventDefault(); // Prevent scrolling
this._updateSize(ev.touches[0].clientY);
};
private _updateSize(clientY: number) {
const deltaY = this._dragStartY - clientY;
const viewportHeight = window.innerHeight;
const deltaVh = (deltaY / viewportHeight) * 100;
// Calculate new size and clamp between 10vh and 90vh
let newSize = this._initialSize + deltaVh;
newSize = Math.max(10, Math.min(90, newSize));
// on drag down and below 20vh
if (newSize < 20 && deltaY < 0) {
this._endDrag();
this.closeSheet();
return;
}
this._dialogViewportHeight = newSize;
}
private _handleMouseUp = () => {
this._endDrag();
};
private _handleTouchEnd = () => {
this._endDrag();
};
private _endDrag() {
if (!this._dragging) {
return;
}
this._dragging = false;
document.body.style.removeProperty("cursor");
}
static styles = css`
wa-drawer {
--wa-color-surface-raised: var(
.handle-wrapper {
position: absolute;
top: 0;
width: 100%;
padding-bottom: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
touch-action: none;
}
.handle-wrapper .handle {
height: 20px;
width: 200px;
display: flex;
justify-content: center;
align-items: center;
z-index: 7;
padding-bottom: 76px;
}
.handle-wrapper .handle::after {
content: "";
border-radius: 8px;
height: 4px;
background: var(--divider-color, #e0e0e0);
width: 80px;
}
.handle-wrapper .handle:active::after {
cursor: grabbing;
}
dialog {
height: auto;
max-height: 70vh;
min-height: 30vh;
background-color: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
--spacing: 0;
--size: auto;
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
display: flex;
flex-direction: column;
top: 0;
inset-inline-start: 0;
position: fixed;
width: calc(100% - 4px);
max-width: 100%;
border: none;
box-shadow: var(--wa-shadow-l);
padding: 0;
margin: 0;
top: auto;
inset-inline-end: auto;
bottom: 0;
inset-inline-start: 0;
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
border-top-left-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
border-top-right-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
transform: translateY(100%);
transition: transform ${ANIMATION_DURATION_MS}ms ease;
border-top-width: var(--ha-bottom-sheet-border-width);
border-right-width: var(--ha-bottom-sheet-border-width);
border-left-width: var(--ha-bottom-sheet-border-width);
border-bottom-width: 0;
border-style: var(--ha-bottom-sheet-border-style);
border-color: var(--ha-bottom-sheet-border-color);
}
wa-drawer::part(dialog) {
border-top-left-radius: var(--ha-border-radius-lg);
border-top-right-radius: var(--ha-border-radius-lg);
max-height: 90vh;
}
wa-drawer::part(body) {
padding-bottom: var(--safe-area-inset-bottom);
dialog.show {
transform: translateY(0);
}
`;
}
@@ -65,4 +265,8 @@ declare global {
interface HTMLElementTagNameMap {
"ha-bottom-sheet": HaBottomSheet;
}
interface HASSDomEvents {
"bottom-sheet-closed": undefined;
}
}

View File

@@ -57,8 +57,6 @@ export class HaButton extends Button {
font-size: var(--ha-font-size-m);
line-height: 1;
transition: background-color 0.15s ease-in-out;
}
:host([size="small"]) .button {

View File

@@ -49,6 +49,7 @@ export class HaExpansionPanel extends LitElement {
tabindex=${this.noCollapse ? -1 : 0}
aria-expanded=${this.expanded}
aria-controls="sect1"
part="summary"
>
${this.leftChevron ? chevronIcon : nothing}
<slot name="leading-icon"></slot>

View File

@@ -25,9 +25,8 @@ export class HaHelpTooltip extends LitElement {
protected render(): TemplateResult {
return html`
<ha-svg-icon id="svg-icon" .path=${mdiHelpCircle}></ha-svg-icon>
<ha-tooltip for="svg-icon" .placement=${this.position}>
${this.label}
<ha-tooltip .placement=${this.position} .content=${this.label}>
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
</ha-tooltip>
`;
}

View File

@@ -74,16 +74,16 @@ export class HaIconOverflowMenu extends LitElement {
: item.divider
? html`<div role="separator"></div>`
: html`<ha-tooltip
.disabled=${!item.tooltip}
.for="icon-button-${item.label}"
>${item.tooltip ?? ""} </ha-tooltip
><ha-icon-button
.id="icon-button-${item.label}"
.disabled=${!item.tooltip}
.content=${item.tooltip ?? ""}
>
<ha-icon-button
@click=${item.action}
.label=${item.label}
.path=${item.path}
?disabled=${item.disabled}
></ha-icon-button> `
></ha-icon-button>
</ha-tooltip>`
)}
`}
`;

View File

@@ -1,271 +0,0 @@
import { css, html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import { BOTTOM_SHEET_ANIMATION_DURATION_MS } from "./ha-bottom-sheet";
/**
* A bottom sheet component that slides up from the bottom of the screen.
*
* The bottom sheet provides a draggable interface that allows users to resize
* the sheet by dragging the handle at the top. It supports both mouse and touch
* interactions and automatically closes when dragged below a 20% of screen height.
*
* @fires bottom-sheet-closed - Fired when the bottom sheet is closed
*
* @cssprop --ha-bottom-sheet-border-width - Border width for the sheet
* @cssprop --ha-bottom-sheet-border-style - Border style for the sheet
* @cssprop --ha-bottom-sheet-border-color - Border color for the sheet
*/
@customElement("ha-resizable-bottom-sheet")
export class HaResizableBottomSheet extends LitElement {
@query("dialog") private _dialog!: HTMLDialogElement;
private _dragging = false;
private _dragStartY = 0;
private _initialSize = 0;
@state() private _dialogMaxViewpointHeight = 70;
@state() private _dialogMinViewpointHeight = 55;
@state() private _dialogViewportHeight?: number;
render() {
return html`<dialog
open
@transitionend=${this._handleTransitionEnd}
style=${styleMap({
height: this._dialogViewportHeight
? `${this._dialogViewportHeight}vh`
: "auto",
maxHeight: `${this._dialogMaxViewpointHeight}vh`,
minHeight: `${this._dialogMinViewpointHeight}vh`,
})}
>
<div class="handle-wrapper">
<div
@mousedown=${this._handleMouseDown}
@touchstart=${this._handleTouchStart}
class="handle"
></div>
</div>
<slot></slot>
</dialog>`;
}
protected firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._openSheet();
}
private _openSheet() {
requestAnimationFrame(() => {
// trigger opening animation
this._dialog.classList.add("show");
});
}
public closeSheet() {
requestAnimationFrame(() => {
this._dialog.classList.remove("show");
});
}
private _handleTransitionEnd() {
if (this._dialog.classList.contains("show")) {
// after show animation is done
// - set the height to the natural height, to prevent content shift when switch content
// - set max height to 90vh, so it opens at max 70vh but can be resized to 90vh
this._dialogViewportHeight =
(this._dialog.offsetHeight / window.innerHeight) * 100;
this._dialogMaxViewpointHeight = 90;
this._dialogMinViewpointHeight = 20;
} else {
// after close animation is done close dialog element and fire closed event
this._dialog.close();
fireEvent(this, "bottom-sheet-closed");
}
}
connectedCallback() {
super.connectedCallback();
// register event listeners for drag handling
document.addEventListener("mousemove", this._handleMouseMove);
document.addEventListener("mouseup", this._handleMouseUp);
document.addEventListener("touchmove", this._handleTouchMove, {
passive: false,
});
document.addEventListener("touchend", this._handleTouchEnd);
document.addEventListener("touchcancel", this._handleTouchEnd);
}
disconnectedCallback() {
super.disconnectedCallback();
// unregister event listeners for drag handling
document.removeEventListener("mousemove", this._handleMouseMove);
document.removeEventListener("mouseup", this._handleMouseUp);
document.removeEventListener("touchmove", this._handleTouchMove);
document.removeEventListener("touchend", this._handleTouchEnd);
document.removeEventListener("touchcancel", this._handleTouchEnd);
}
private _handleMouseDown = (ev: MouseEvent) => {
this._startDrag(ev.clientY);
};
private _handleTouchStart = (ev: TouchEvent) => {
// Prevent the browser from interpreting this as a scroll/PTR gesture.
ev.preventDefault();
this._startDrag(ev.touches[0].clientY);
};
private _startDrag(clientY: number) {
this._dragging = true;
this._dragStartY = clientY;
this._initialSize = (this._dialog.offsetHeight / window.innerHeight) * 100;
document.body.style.setProperty("cursor", "grabbing");
}
private _handleMouseMove = (ev: MouseEvent) => {
if (!this._dragging) {
return;
}
this._updateSize(ev.clientY);
};
private _handleTouchMove = (ev: TouchEvent) => {
if (!this._dragging) {
return;
}
ev.preventDefault(); // Prevent scrolling
this._updateSize(ev.touches[0].clientY);
};
private _updateSize(clientY: number) {
const deltaY = this._dragStartY - clientY;
const viewportHeight = window.innerHeight;
const deltaVh = (deltaY / viewportHeight) * 100;
// Calculate new size and clamp between 10vh and 90vh
let newSize = this._initialSize + deltaVh;
newSize = Math.max(10, Math.min(90, newSize));
// on drag down and below 20vh
if (newSize < 20 && deltaY < 0) {
this._endDrag();
this.closeSheet();
return;
}
this._dialogViewportHeight = newSize;
}
private _handleMouseUp = () => {
this._endDrag();
};
private _handleTouchEnd = () => {
this._endDrag();
};
private _endDrag() {
if (!this._dragging) {
return;
}
this._dragging = false;
document.body.style.removeProperty("cursor");
}
static styles = css`
.handle-wrapper {
position: absolute;
top: 0;
width: 100%;
padding-bottom: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
touch-action: none;
}
.handle-wrapper .handle {
height: 20px;
width: 200px;
display: flex;
justify-content: center;
align-items: center;
z-index: 7;
padding-bottom: 76px;
}
.handle-wrapper .handle::after {
content: "";
border-radius: 8px;
height: 4px;
background: var(--divider-color, #e0e0e0);
width: 80px;
}
.handle-wrapper .handle:active::after {
cursor: grabbing;
}
dialog {
height: auto;
max-height: 70vh;
min-height: 30vh;
background-color: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
display: flex;
flex-direction: column;
top: 0;
inset-inline-start: 0;
position: fixed;
width: calc(100% - 4px);
max-width: 100%;
border: none;
box-shadow: var(--wa-shadow-l);
padding: 0;
margin: 0;
top: auto;
inset-inline-end: auto;
bottom: 0;
inset-inline-start: 0;
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
border-top-left-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
border-top-right-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
transform: translateY(100%);
transition: transform ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms ease;
border-top-width: var(--ha-bottom-sheet-border-width);
border-right-width: var(--ha-bottom-sheet-border-width);
border-left-width: var(--ha-bottom-sheet-border-width);
border-bottom-width: 0;
border-style: var(--ha-bottom-sheet-border-style);
border-color: var(--ha-bottom-sheet-border-color);
}
dialog.show {
transform: translateY(0);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-resizable-bottom-sheet": HaResizableBottomSheet;
}
interface HASSDomEvents {
"bottom-sheet-closed": undefined;
}
}

View File

@@ -1,39 +1,16 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import "@material/mwc-menu/mwc-menu-surface";
import {
mdiClose,
mdiDevices,
mdiHome,
mdiLabel,
mdiPlus,
mdiTextureBox,
mdiUnfoldMoreVertical,
} from "@mdi/js";
import { mdiPlus } from "@mdi/js";
import type { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import type {
HassEntity,
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing, unsafeCSS } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ensureArray } from "../common/array/ensure-array";
import { computeCssColor } from "../common/color/compute-color";
import { hex2rgb } from "../common/color/convert-color";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id";
import type { AreaRegistryEntry } from "../data/area_registry";
import type { DeviceRegistryEntry } from "../data/device_registry";
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
import type { LabelRegistryEntry } from "../data/label_registry";
import { subscribeLabelRegistry } from "../data/label_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import type { HomeAssistant } from "../types";
import "./device/ha-device-picker";
@@ -41,12 +18,13 @@ import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-floor-picker";
import { floorDefaultIconPath } from "./ha-floor-icon";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-label-picker";
import "./ha-svg-icon";
import "./ha-tooltip";
import "./target-picker/ha-target-picker-item-group";
import type { TargetType } from "./target-picker/ha-target-picker-item-row";
@customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) {
@@ -58,6 +36,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@property({ type: Boolean }) public compact = false;
@property({ attribute: false, type: Array }) public createDomains?: string[];
/**
@@ -96,18 +76,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@query(".add-container", true) private _addContainer?: HTMLDivElement;
@state() private _labels?: LabelRegistryEntry[];
private _opened = false;
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
protected render() {
if (this.addOnTop) {
return html` ${this._renderChips()} ${this._renderItems()} `;
@@ -116,87 +86,68 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
}
private _renderItems() {
if (
!this.value?.floor_id &&
!this.value?.area_id &&
!this.value?.device_id &&
!this.value?.entity_id &&
!this.value?.label_id
) {
return nothing;
}
return html`
<div class="mdc-chip-set items">
${this.value?.floor_id
? ensureArray(this.value.floor_id).map((floor_id) => {
const floor = this.hass.floors[floor_id];
return this._renderChip(
"floor_id",
floor_id,
floor?.name || floor_id,
undefined,
floor?.icon,
floor ? floorDefaultIconPath(floor) : mdiHome
);
})
: ""}
${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => {
const area = this.hass.areas![area_id];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
area?.icon,
mdiTextureBox
);
})
<div class="item-groups">
${this.value?.floor_id || this.value?.area_id
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="area"
.hass=${this.hass}
.items=${{
floor: ensureArray(this.value?.floor_id),
area: ensureArray(this.value?.area_id),
}}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.device_id
? ensureArray(this.value.device_id).map((device_id) => {
const device = this.hass.devices![device_id];
return this._renderChip(
"device_id",
device_id,
device
? computeDeviceNameDisplay(device, this.hass)
: device_id,
undefined,
undefined,
mdiDevices
);
})
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="device"
.hass=${this.hass}
.items=${{ device: ensureArray(this.value?.device_id) }}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.entity_id
? ensureArray(this.value.entity_id).map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity
);
})
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="entity"
.hass=${this.hass}
.items=${{ entity: ensureArray(this.value?.entity_id) }}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.label_id
? ensureArray(this.value.label_id).map((label_id) => {
const label = this._labels?.find(
(lbl) => lbl.label_id === label_id
);
let color = label?.color
? computeCssColor(label.color)
: undefined;
if (color?.startsWith("var(")) {
const computedStyles = getComputedStyle(this);
color = computedStyles.getPropertyValue(
color.substring(4, color.length - 1)
);
}
if (color?.startsWith("#")) {
color = hex2rgb(color).join(",");
}
return this._renderChip(
"label_id",
label_id,
label ? label.name : label_id,
undefined,
label?.icon,
mdiLabel,
color
);
})
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="label"
.hass=${this.hass}
.items=${{ label: ensureArray(this.value?.label_id) }}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
</div>
`;
@@ -299,85 +250,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
this._addMode = ev.currentTarget.type;
}
private _renderChip(
type: "floor_id" | "area_id" | "device_id" | "entity_id" | "label_id",
id: string,
name: string,
entityState?: HassEntity,
icon?: string | null,
fallbackIconPath?: string,
color?: string
) {
return html`
<div
class="mdc-chip ${classMap({
[type]: true,
})}"
style=${color
? `--color: rgb(${color}); --background-color: rgba(${color}, .5)`
: ""}
>
${icon
? html`<ha-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.icon=${icon}
></ha-icon>`
: fallbackIconPath
? html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${fallbackIconPath}
></ha-svg-icon>`
: ""}
${entityState
? html`<ha-state-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.hass=${this.hass}
.stateObj=${entityState}
></ha-state-icon>`
: ""}
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">${name}</span>
</span>
</span>
${type === "entity_id"
? ""
: html`<span role="gridcell">
<ha-tooltip .for="expand-${id}"
>${this.hass.localize(
`ui.components.target-picker.expand_${type}`
)}
</ha-tooltip>
<ha-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
.label=${this.hass.localize(
"ui.components.target-picker.expand"
)}
.path=${mdiUnfoldMoreVertical}
hide-title
.id="expand-${id}"
.type=${type}
@click=${this._handleExpand}
></ha-icon-button>
</span>`}
<span role="gridcell">
<ha-tooltip .for="remove-${id}">
${this.hass.localize(`ui.components.target-picker.remove_${type}`)}
</ha-tooltip>
<ha-icon-button
class="mdc-chip__icon mdc-chip__icon--trailing"
.label=${this.hass.localize("ui.components.target-picker.remove")}
.path=${mdiClose}
hide-title
.id="remove-${id}"
.type=${type}
@click=${this._handleRemove}
></ha-icon-button>
</span>
</div>
`;
}
private _renderPicker() {
if (!this._addMode) {
return nothing;
@@ -520,130 +392,31 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
});
}
private _handleExpand(ev) {
const target = ev.currentTarget as any;
const newAreas: string[] = [];
const newDevices: string[] = [];
const newEntities: string[] = [];
if (target.type === "floor_id") {
Object.values(this.hass.areas).forEach((area) => {
if (
area.floor_id === target.id &&
!this.value!.area_id?.includes(area.area_id) &&
this._areaMeetsFilter(area)
) {
newAreas.push(area.area_id);
}
});
} else if (target.type === "area_id") {
Object.values(this.hass.devices).forEach((device) => {
if (
device.area_id === target.id &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.area_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "device_id") {
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.device_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "label_id") {
Object.values(this.hass.areas).forEach((area) => {
if (
area.labels.includes(target.id) &&
!this.value!.area_id?.includes(area.area_id) &&
this._areaMeetsFilter(area)
) {
newAreas.push(area.area_id);
}
});
Object.values(this.hass.devices).forEach((device) => {
if (
device.labels.includes(target.id) &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.labels.includes(target.id) &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity, true)
) {
newEntities.push(entity.entity_id);
}
});
} else {
return;
}
let value = this.value;
if (newEntities.length) {
value = this._addItems(value, "entity_id", newEntities);
}
if (newDevices.length) {
value = this._addItems(value, "device_id", newDevices);
}
if (newAreas.length) {
value = this._addItems(value, "area_id", newAreas);
}
value = this._removeItem(value, target.type, target.id);
fireEvent(this, "value-changed", { value });
}
private _handleRemove(ev) {
const target = ev.currentTarget as any;
const { type, id } = ev.detail;
fireEvent(this, "value-changed", {
value: this._removeItem(this.value, target.type, target.id),
value: this._removeItem(this.value, type, id),
});
}
private _addItems(
value: this["value"],
type: string,
ids: string[]
): this["value"] {
return {
...value,
[type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids,
};
}
private _removeItem(
value: this["value"],
type: string,
type: TargetType,
id: string
): this["value"] {
const newVal = ensureArray(value![type])!.filter(
const typeId = `${type}_id`;
const newVal = ensureArray(value![typeId])!.filter(
(val) => String(val) !== id
);
if (newVal.length) {
return {
...value,
[type]: newVal,
[typeId]: newVal,
};
}
const val = { ...value }!;
delete val[type];
delete val[typeId];
if (Object.keys(val).length) {
return val;
}
@@ -675,83 +448,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
ev.preventDefault();
}
private _areaMeetsFilter(area: AreaRegistryEntry): boolean {
const areaDevices = Object.values(this.hass.devices).filter(
(device) => device.area_id === area.area_id
);
if (areaDevices.some((device) => this._deviceMeetsFilter(device))) {
return true;
}
const areaEntities = Object.values(this.hass.entities).filter(
(entity) => entity.area_id === area.area_id
);
if (areaEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
return true;
}
return false;
}
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
const devEntities = Object.values(this.hass.entities).filter(
(entity) => entity.device_id === device.id
);
if (!devEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
return false;
}
if (this.deviceFilter) {
if (!this.deviceFilter(device)) {
return false;
}
}
return true;
}
private _entityRegMeetsFilter(
entity: EntityRegistryDisplayEntry,
includeSecondary = false
): boolean {
if (entity.hidden || (entity.entity_category && !includeSecondary)) {
return false;
}
if (
this.includeDomains &&
!this.includeDomains.includes(computeDomain(entity.entity_id))
) {
return false;
}
if (this.includeDeviceClasses) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (
!stateObj.attributes.device_class ||
!this.includeDeviceClasses!.includes(stateObj.attributes.device_class)
) {
return false;
}
}
if (this.entityFilter) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (!this.entityFilter!(stateObj)) {
return false;
}
}
return true;
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
@@ -810,41 +506,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
margin-inline-end: 0;
margin-inline-start: initial;
}
.mdc-chip.area_id:not(.add),
.mdc-chip.floor_id:not(.add) {
border: 1px solid #fed6a4;
background: var(--card-background-color);
}
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.area_id.add,
.mdc-chip.floor_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.floor_id.add {
background: #fed6a4;
}
.mdc-chip.device_id:not(.add) {
border: 1px solid #a8e1fb;
background: var(--card-background-color);
}
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.device_id.add {
background: #a8e1fb;
}
.mdc-chip.entity_id:not(.add) {
border: 1px solid #d2e7b9;
background: var(--card-background-color);
}
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.entity_id.add {
background: #d2e7b9;
}
.mdc-chip.label_id:not(.add) {
border: 1px solid var(--color, #e0e0e0);
background: var(--card-background-color);
}
.mdc-chip.label_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.label_id.add {
background: var(--background-color, #e0e0e0);
}
.mdc-chip:hover {
z-index: 5;
}
@@ -864,6 +525,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
ha-tooltip {
--ha-tooltip-arrow-size: 0;
}
.item-groups {
overflow: hidden;
border: 2px solid var(--divider-color);
border-radius: var(--ha-border-radius-lg);
}
`;
}
}
@@ -872,4 +539,12 @@ declare global {
interface HTMLElementTagNameMap {
"ha-target-picker": HaTargetPicker;
}
interface HASSDomEvents {
"remove-target-item": {
type: string;
id: string;
};
"remove-target-group": string;
}
}

View File

@@ -1,47 +1,50 @@
import Tooltip from "@home-assistant/webawesome/dist/components/tooltip/tooltip";
import SlTooltip from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.component";
import styles from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.styles";
import { css } from "lit";
import type { CSSResultGroup } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement } from "lit/decorators";
import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry";
setDefaultAnimation("tooltip.show", {
keyframes: [{ opacity: 0 }, { opacity: 1 }],
options: { duration: 150, easing: "ease" },
});
setDefaultAnimation("tooltip.hide", {
keyframes: [{ opacity: 1 }, { opacity: 0 }],
options: { duration: 400, easing: "ease" },
});
@customElement("ha-tooltip")
export class HaTooltip extends Tooltip {
/** The amount of time to wait before showing the tooltip when the user mouses in. */
@property({ attribute: "show-delay", type: Number }) showDelay = 150;
/** The amount of time to wait before hiding the tooltip when the user mouses out.. */
@property({ attribute: "hide-delay", type: Number }) hideDelay = 400;
static get styles(): CSSResultGroup {
return [
Tooltip.styles,
css`
:host {
--wa-tooltip-background-color: var(--secondary-background-color);
--wa-tooltip-color: var(--primary-text-color);
--wa-tooltip-font-family: var(
--ha-tooltip-font-family,
var(--ha-font-family-body)
);
--wa-tooltip-font-size: var(
--ha-tooltip-font-size,
var(--ha-font-size-s)
);
--wa-tooltip-font-weight: var(
--ha-tooltip-font-weight,
var(--ha-font-weight-normal)
);
--wa-tooltip-line-height: var(
--ha-tooltip-line-height,
var(--ha-line-height-condensed)
);
--wa-tooltip-padding: 8px;
--wa-tooltip-border-radius: var(--ha-tooltip-border-radius, 4px);
--wa-tooltip-arrow-size: var(--ha-tooltip-arrow-size, 8px);
--wa-z-index-tooltip: var(--ha-tooltip-z-index, 1000);
}
`,
];
}
export class HaTooltip extends SlTooltip {
static override styles = [
styles,
css`
:host {
--sl-tooltip-background-color: var(--secondary-background-color);
--sl-tooltip-color: var(--primary-text-color);
--sl-tooltip-font-family: var(
--ha-tooltip-font-family,
var(--ha-font-family-body)
);
--sl-tooltip-font-size: var(
--ha-tooltip-font-size,
var(--ha-font-size-s)
);
--sl-tooltip-font-weight: var(
--ha-tooltip-font-weight,
var(--ha-font-weight-normal)
);
--sl-tooltip-line-height: var(
--ha-tooltip-line-height,
var(--ha-line-height-condensed)
);
--sl-tooltip-padding: 8px;
--sl-tooltip-border-radius: var(--ha-tooltip-border-radius, 4px);
--sl-tooltip-arrow-size: var(--ha-tooltip-arrow-size, 8px);
--sl-z-index-tooltip: var(--ha-tooltip-z-index, 1000);
}
`,
];
}
declare global {

View File

@@ -642,10 +642,9 @@ export class HaMediaPlayerBrowse extends LitElement {
`
: ""}
</div>
<ha-tooltip .for="grid-${child.title}" distance="-4">
${child.title}
<ha-tooltip distance="-4" .content=${child.title}>
<div class="title">${child.title}</div>
</ha-tooltip>
<div .id="grid-${child.title}" class="title">${child.title}</div>
</ha-card>
</div>
`;

View File

@@ -0,0 +1,78 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../types";
import "../ha-expansion-panel";
import "../ha-md-list";
import "./ha-target-picker-item-row";
@customElement("ha-target-picker-item-group")
export class HaTargetPickerItemGroup extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public type!: "entity" | "device" | "area" | "label";
@property({ attribute: false }) public items!: Partial<
Record<"entity" | "device" | "area" | "label" | "floor", string[]>
>;
@property({ type: Boolean }) public collapsed = false;
protected render() {
let count = 0;
Object.values(this.items).forEach((items) => {
if (items) {
count += items.length;
}
});
return html`<ha-expansion-panel .expanded=${!this.collapsed} left-chevron>
<div slot="header" class="heading">
${this.hass.localize(
`ui.components.target-picker.selected.${this.type}`,
{
count,
}
)}
</div>
<ha-md-list>
${Object.entries(this.items).map(([type, items]) =>
items
? items.map(
(item) =>
html`<ha-target-picker-item-row
.hass=${this.hass}
.type=${type as "entity" | "device" | "area" | "label"}
.itemId=${item}
></ha-target-picker-item-row>`
)
: nothing
)}
</ha-md-list>
</ha-expansion-panel>`;
}
static styles = css`
:host {
display: block;
--expansion-panel-content-padding: 0;
}
ha-expansion-panel::part(summary) {
background-color: var(--ha-color-fill-neutral-quiet-resting);
padding: 4px 8px;
font-weight: var(--ha-font-weight-bold);
color: var(--secondary-text-color);
display: flex;
justify-content: space-between;
min-height: unset;
}
ha-md-list {
padding: 0;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker-item-group": HaTargetPickerItemGroup;
}
}

View File

@@ -0,0 +1,478 @@
import { consume } from "@lit/context";
import {
mdiChevronDown,
mdiClose,
mdiDevices,
mdiHome,
mdiLabel,
mdiTextureBox,
} from "@mdi/js";
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeAreaName } from "../../common/entity/compute_area_name";
import {
computeDeviceName,
computeDeviceNameDisplay,
} from "../../common/entity/compute_device_name";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeEntityName } from "../../common/entity/compute_entity_name";
import { getEntityContext } from "../../common/entity/context/get_entity_context";
import { computeRTL } from "../../common/util/compute_rtl";
import { getConfigEntry } from "../../data/config_entries";
import { labelsContext } from "../../data/context";
import { domainToName } from "../../data/integration";
import type { LabelRegistryEntry } from "../../data/label_registry";
import {
extractFromTarget,
type ExtractFromTargetResult,
} from "../../data/target";
import type { HomeAssistant } from "../../types";
import { brandsUrl } from "../../util/brands-url";
import { floorDefaultIconPath } from "../ha-floor-icon";
import "../ha-icon-button";
import "../ha-md-list";
import type { HaMdList } from "../ha-md-list";
import "../ha-md-list-item";
import type { HaMdListItem } from "../ha-md-list-item";
import "../ha-state-icon";
import "../ha-svg-icon";
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
@customElement("ha-target-picker-item-row")
export class HaTargetPickerItemRow extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ reflect: true }) public type!: TargetType;
@property({ attribute: "item-id" }) public itemId!: string;
@property({ type: Boolean, attribute: "sub-entry", reflect: true })
public subEntry = false;
@property({ attribute: false })
public parentEntries?: ExtractFromTargetResult;
@state() private _expanded = false;
@state() private _iconImg?: string;
@state() private _domainName?: string;
@state() private _entries?: ExtractFromTargetResult;
@state()
@consume({ context: labelsContext, subscribe: true })
_labelRegistry!: LabelRegistryEntry[];
@query("ha-md-list-item") public item?: HaMdListItem;
@query("ha-md-list") public list?: HaMdList;
@query("ha-target-picker-item-row") public itemRow?: HaTargetPickerItemRow;
protected willUpdate(changedProps: PropertyValues) {
if (!this.subEntry && changedProps.has("itemId")) {
this._updateItemData();
this._expanded = false;
}
}
protected render() {
const { name, context, iconPath, fallbackIconPath, stateObject } =
this._itemData(this.type, this.itemId);
const showDevices = ["floor", "area", "label"].includes(this.type);
const showEntities = this.type !== "entity";
const entries = this.parentEntries || this._entries;
// Don't show sub entries that have no entities
if (
this.subEntry &&
this.type !== "entity" &&
(!entries || entries.referenced_entities.length === 0)
) {
return nothing;
}
return html`
<ha-md-list-item
.disabled=${entries?.referenced_entities.length === 0}
.type=${this.type === "entity" ? "text" : "button"}
@click=${this._toggleExpand}
>
${this.type !== "entity"
? html`<ha-svg-icon
class="expand-button ${entries?.referenced_entities.length &&
this._expanded
? "expanded"
: ""}"
.path=${mdiChevronDown}
slot="start"
></ha-svg-icon>`
: nothing}
${iconPath
? html`<ha-icon slot="start" .icon=${iconPath}></ha-icon>`
: this._iconImg
? html`<img
slot="start"
alt=${this._domainName || ""}
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${this._iconImg}
/>`
: fallbackIconPath
? html`<ha-svg-icon
slot="start"
.path=${fallbackIconPath}
></ha-svg-icon>`
: stateObject
? html`
<ha-state-icon
.hass=${this.hass}
.stateObj=${stateObject}
slot="start"
>
</ha-state-icon>
`
: nothing}
<div slot="headline">${name}</div>
${context && !this.subEntry
? html`<span slot="supporting-text">${context}</span>`
: nothing}
${showEntities || showDevices || this._domainName
? html`
<div slot="end" class="summary">
${showEntities
? html`<span class="main"
>${this.hass.localize(
"ui.components.target-picker.entities_count",
{
count: entries?.referenced_entities.length,
}
)}</span
>`
: nothing}
${showDevices
? html`<span class="secondary"
>${this.hass.localize(
"ui.components.target-picker.devices_count",
{
count: entries?.referenced_devices.length,
}
)}</span
>`
: nothing}
${this._domainName && !showDevices
? html`<span class="secondary domain"
>${this._domainName}</span
>`
: nothing}
</div>
`
: nothing}
${!this.subEntry
? html`
<ha-icon-button
.path=${mdiClose}
slot="end"
@click=${this._removeItem}
></ha-icon-button>
`
: nothing}
</ha-md-list-item>
${this._expanded && entries && entries.referenced_entities
? this._renderEntries()
: nothing}
`;
}
private _renderEntries() {
const entries = this.parentEntries || this._entries;
let nextType =
this.type === "floor"
? "area"
: this.type === "area"
? "device"
: "entity";
if (this.type === "label") {
if (entries?.referenced_areas.length) {
nextType = "area";
} else if (entries?.referenced_devices.length) {
nextType = "device";
}
}
const rows1 =
(nextType === "area"
? entries?.referenced_areas
: nextType === "device"
? entries?.referenced_devices
: entries?.referenced_entities) || [];
const rows1Entries =
nextType === "entity"
? undefined
: rows1.map((rowItem) => {
const nextEntries = {
missing_areas: [] as string[],
missing_devices: [] as string[],
missing_floors: [] as string[],
missing_labels: [] as string[],
referenced_areas: [] as string[],
referenced_devices: [] as string[],
referenced_entities: [] as string[],
};
if (nextType === "area") {
nextEntries.referenced_devices =
entries?.referenced_devices.filter(
(device_id) =>
this.hass.devices?.[device_id]?.area_id === rowItem &&
entries?.referenced_entities.some(
(entity_id) =>
this.hass.entities?.[entity_id]?.device_id === device_id
)
) || ([] as string[]);
nextEntries.referenced_entities =
entries?.referenced_entities.filter((entity_id) => {
const entity = this.hass.entities[entity_id];
return (
entity.area_id === rowItem ||
!entity.device_id ||
nextEntries.referenced_devices.includes(entity.device_id)
);
}) || ([] as string[]);
return nextEntries;
}
nextEntries.referenced_entities =
entries?.referenced_entities.filter(
(entity_id) =>
this.hass.entities?.[entity_id]?.device_id === rowItem
) || ([] as string[]);
return nextEntries;
});
const rows2 =
nextType === "device" && entries
? entries.referenced_entities.filter(
(entity_id) => this.hass.entities[entity_id].area_id === this.itemId
)
: [];
return html`
<ha-md-list class="entries">
${rows1.map(
(itemId, index) => html`
<ha-target-picker-item-row
sub-entry
.hass=${this.hass}
.type=${nextType}
.itemId=${itemId}
.parentEntries=${rows1Entries?.[index]}
></ha-target-picker-item-row>
`
)}
${rows2.map(
(itemId) => html`
<ha-target-picker-item-row
sub-entry
.hass=${this.hass}
type="entity"
.itemId=${itemId}
></ha-target-picker-item-row>
`
)}
</ha-md-list>
`;
}
private async _updateItemData() {
try {
this._entries = await extractFromTarget(this.hass, {
[`${this.type}_id`]: [this.itemId],
});
} catch (e) {
// eslint-disable-next-line no-console
console.error("Failed to extract target", e);
}
}
private _itemData = memoizeOne((type: TargetType, item: string) => {
if (type === "floor") {
const floor = this.hass.floors?.[item];
return {
name: floor?.name || item,
iconPath: floor?.icon,
fallbackIconPath: floor ? floorDefaultIconPath(floor) : mdiHome,
};
}
if (type === "area") {
const area = this.hass.areas?.[item];
return {
name: area?.name || item,
context: area.floor_id && this.hass.floors?.[area.floor_id]?.name,
iconPath: area?.icon,
fallbackIconPath: mdiTextureBox,
};
}
if (type === "device") {
const device = this.hass.devices?.[item];
if (device.primary_config_entry) {
this._getDeviceDomain(device.primary_config_entry);
}
return {
name: device ? computeDeviceNameDisplay(device, this.hass) : item,
context: device?.area_id && this.hass.areas?.[device.area_id]?.name,
fallbackIconPath: mdiDevices,
};
}
if (type === "entity") {
this._setDomainName(computeDomain(item));
const stateObject = this.hass.states[item];
const entityName = computeEntityName(stateObject, this.hass);
const { area, device } = getEntityContext(stateObject, this.hass);
const deviceName = device ? computeDeviceName(device) : undefined;
const areaName = area ? computeAreaName(area) : undefined;
const context = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
return {
name: entityName || deviceName || item,
context,
stateObject,
};
}
// type label
const label = this._labelRegistry.find((lab) => lab.label_id === item);
return {
name: label?.name || item,
iconPath: label?.icon,
fallbackIconPath: mdiLabel,
};
});
private _setDomainName(domain: string) {
this._domainName = domainToName(this.hass.localize, domain);
}
private _removeItem(ev) {
ev.stopPropagation();
fireEvent(this, "remove-target-item", {
type: this.type,
id: this.itemId,
});
}
private async _getDeviceDomain(configEntryId: string) {
try {
const data = await getConfigEntry(this.hass, configEntryId);
const domain = data.config_entry.domain;
this._iconImg = brandsUrl({
domain: domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
});
this._setDomainName(domain);
} catch {
// failed to load config entry -> ignore
}
}
private _toggleExpand() {
const entries = this.parentEntries || this._entries;
if (
this.type === "entity" ||
!entries ||
entries.referenced_entities.length === 0
) {
return;
}
this._expanded = !this._expanded;
}
static styles = css`
:host {
--md-list-item-top-space: 0;
--md-list-item-bottom-space: 0;
--md-list-item-leading-space: 8px;
--md-list-item-trailing-space: 8px;
--md-list-item-two-line-container-height: 56px;
}
state-badge {
color: var(--ha-color-on-neutral-quiet);
}
img {
width: 24px;
height: 24px;
}
.expand-button {
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.expand-button.expanded {
transform: rotate(180deg);
}
ha-icon-button {
--mdc-icon-button-size: 32px;
}
.summary {
display: flex;
flex-direction: column;
align-items: flex-end;
line-height: var(--ha-line-height-condensed);
}
:host([sub-entry]) .summary {
margin-right: 48px;
}
.summary .main {
font-weight: var(--ha-font-weight-medium);
}
.summary .secondary {
font-size: var(--ha-font-size-s);
color: var(--secondary-text-color);
}
.summary .secondary.domain {
font-family: var(--ha-font-family-code);
}
@media all and (max-width: 870px) {
:host([sub-entry]) .summary {
display: none;
}
}
.entries {
padding: 0;
padding-left: 40px;
overflow: hidden;
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
border-bottom: 1px solid var(--ha-color-border-neutral-quiet);
}
:host([sub-entry]) .entries {
border-bottom: none;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker-item-row": HaTargetPickerItemRow;
}
}

21
src/data/target.ts Normal file
View File

@@ -0,0 +1,21 @@
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../types";
export interface ExtractFromTargetResult {
missing_areas: string[];
missing_devices: string[];
missing_floors: string[];
missing_labels: string[];
referenced_areas: string[];
referenced_devices: string[];
referenced_entities: string[];
}
export const extractFromTarget = async (
hass: HomeAssistant,
target: HassServiceTarget
) =>
hass.callWS<ExtractFromTargetResult>({
type: "extract_from_target",
target,
});

View File

@@ -1,7 +1,6 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-bottom-sheet";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-icon";
import "../../components/ha-md-list";
@@ -41,54 +40,6 @@ export class ListItemsDialog
return nothing;
}
const content = html`
<div class="container">
<ha-md-list>
${this._params.items.map(
(item) => html`
<ha-md-list-item
type="button"
@click=${this._itemClicked}
.item=${item}
>
${item.iconPath
? html`
<ha-svg-icon
.path=${item.iconPath}
slot="start"
class="item-icon"
></ha-svg-icon>
`
: item.icon
? html`
<ha-icon
icon=${item.icon}
slot="start"
class="item-icon"
></ha-icon>
`
: nothing}
<span class="headline">${item.label}</span>
${item.description
? html`
<span class="supporting-text">${item.description}</span>
`
: nothing}
</ha-md-list-item>
`
)}
</ha-md-list>
</div>
`;
if (this._params.mode === "bottom-sheet") {
return html`
<ha-bottom-sheet placement="bottom" open @closed=${this._dialogClosed}>
${content}
</ha-bottom-sheet>
`;
}
return html`
<ha-dialog
open
@@ -96,7 +47,43 @@ export class ListItemsDialog
@closed=${this._dialogClosed}
hideActions
>
${content}
<div class="container">
<ha-md-list>
${this._params.items.map(
(item) => html`
<ha-md-list-item
type="button"
@click=${this._itemClicked}
.item=${item}
>
${item.iconPath
? html`
<ha-svg-icon
.path=${item.iconPath}
slot="start"
class="item-icon"
></ha-svg-icon>
`
: item.icon
? html`
<ha-icon
icon=${item.icon}
slot="start"
class="item-icon"
></ha-icon>
`
: nothing}
<span class="headline">${item.label}</span>
${item.description
? html`
<span class="supporting-text">${item.description}</span>
`
: nothing}
</ha-md-list-item>
`
)}
</ha-md-list>
</div>
</ha-dialog>
`;
}

View File

@@ -11,7 +11,6 @@ interface ListItem {
export interface ListItemsDialogParams {
title?: string;
items: ListItem[];
mode?: "dialog" | "bottom-sheet";
}
export const showListItemsDialog = (

View File

@@ -166,36 +166,37 @@ class MoreInfoWeather extends LitElement {
${this.hass.formatEntityState(this.stateObj)}
</div>
<div class="time-ago">
<ha-relative-time
id="relative-time"
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
<ha-tooltip for="relative-time">
<div class="row">
<span class="column-name">
${this.hass.localize(
"ui.dialogs.more_info_control.last_changed"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.dialogs.more_info_control.last_updated"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
capitalize
></ha-relative-time>
<ha-tooltip>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
<div slot="content">
<div class="row">
<span class="column-name">
${this.hass.localize(
"ui.dialogs.more_info_control.last_changed"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.dialogs.more_info_control.last_updated"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
capitalize
></ha-relative-time>
</div>
</div>
</ha-tooltip>
</div>

View File

@@ -28,14 +28,15 @@ export class HuiPersistentNotificationItem extends LitElement {
<div class="time">
<span>
<ha-relative-time
id="relative-time"
.hass=${this.hass}
.datetime=${this.notification.created_at}
capitalize
></ha-relative-time>
<ha-tooltip for="relative-time" placement="bottom">
${this._computeTooltip(this.hass, this.notification)}
<ha-tooltip
.content=${this._computeTooltip(this.hass, this.notification)}
placement="bottom"
>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.notification.created_at}
capitalize
></ha-relative-time>
</ha-tooltip>
</span>
</div>

View File

@@ -1,7 +1,6 @@
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { goBack } from "../common/navigate";
import "../components/ha-icon-button-arrow-prev";
import "../components/ha-button";
import "../components/ha-menu-button";
@@ -51,7 +50,7 @@ class HassErrorScreen extends LitElement {
}
private _handleBack(): void {
goBack();
history.back();
}
static get styles(): CSSResultGroup {

View File

@@ -1,7 +1,6 @@
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { goBack } from "../common/navigate";
import "../components/ha-spinner";
import "../components/ha-icon-button-arrow-prev";
import "../components/ha-menu-button";
@@ -50,7 +49,7 @@ class HassLoadingScreen extends LitElement {
}
private _handleBack() {
goBack();
history.back();
}
static get styles(): CSSResultGroup {

View File

@@ -2,7 +2,6 @@ import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, eventOptions, property } from "lit/decorators";
import { restoreScroll } from "../common/decorators/restore-scroll";
import { goBack } from "../common/navigate";
import "../components/ha-icon-button-arrow-prev";
import "../components/ha-menu-button";
import type { HomeAssistant } from "../types";
@@ -79,7 +78,7 @@ class HassSubpage extends LitElement {
this.backCallback();
return;
}
goBack();
history.back();
}
static get styles(): CSSResultGroup {

View File

@@ -4,7 +4,6 @@ import { customElement, eventOptions, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { canShowPage } from "../common/config/can_show_page";
import { goBack } from "../common/navigate";
import { restoreScroll } from "../common/decorators/restore-scroll";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-icon-button-arrow-prev";
@@ -206,7 +205,7 @@ class HassTabsSubpage extends LitElement {
this.backCallback();
return;
}
goBack();
history.back();
}
static get styles(): CSSResultGroup {

View File

@@ -7,7 +7,6 @@ import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { goBack } from "../../../common/navigate";
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
@@ -51,7 +50,6 @@ import {
loadAreaRegistryDetailDialog,
showAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail";
import { slugify } from "../../../common/string/slugify";
declare interface NameAndEntity<EntityType extends HassEntity> {
name: string;
@@ -550,14 +548,11 @@ class HaConfigAreaPage extends LitElement {
private _renderScene(name: string, entityState: SceneEntity) {
return html`<ha-tooltip
.for="scene-${slugify(entityState.entity_id)}"
.distance=${-4}
.disabled=${!!entityState.attributes.id}
>
${this.hass.localize("ui.panel.config.devices.cant_edit")}
</ha-tooltip>
.distance=${-4}
.disabled=${!!entityState.attributes.id}
.content=${this.hass.localize("ui.panel.config.devices.cant_edit")}
>
<a
.id="scene-${slugify(entityState.entity_id)}"
href=${ifDefined(
entityState.attributes.id
? `/config/scene/edit/${entityState.attributes.id}`
@@ -568,12 +563,17 @@ class HaConfigAreaPage extends LitElement {
<span>${name}</span>
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
</a> `;
</a>
</ha-tooltip>`;
}
private _renderAutomation(name: string, entityState: AutomationEntity) {
return html`<a
id="automation-${slugify(entityState.entity_id)}"
return html`<ha-tooltip
.disabled=${!!entityState.attributes.id}
.distance=${-4}
.content=${this.hass.localize("ui.panel.config.devices.cant_edit")}
>
<a
href=${ifDefined(
entityState.attributes.id
? `/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}`
@@ -585,12 +585,7 @@ class HaConfigAreaPage extends LitElement {
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
</a>
<ha-tooltip
for="automation-${slugify(entityState.entity_id)}"
.disabled=${!!entityState.attributes.id}
.distance=${-4}
>${this.hass.localize("ui.panel.config.devices.cant_edit")}
</ha-tooltip>`;
</ha-tooltip>`;
}
private _renderScript(name: string, entityState: ScriptEntity) {
@@ -648,7 +643,7 @@ class HaConfigAreaPage extends LitElement {
destructive: true,
confirm: async () => {
await deleteAreaRegistryEntry(this.hass!, area!.area_id);
afterNextRender(() => goBack("/config"));
afterNextRender(() => history.back());
},
});
}

View File

@@ -258,16 +258,14 @@ export default class HaAutomationActionRow extends LitElement {
${type !== "condition" &&
(this.action as NonConditionAction).continue_on_error === true
? html`<ha-svg-icon
id="svg-icon"
slot="icons"
.path=${mdiAlertCircleCheck}
></ha-svg-icon>
<ha-tooltip for="svg-icon">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.continue_on_error"
)}
</ha-tooltip>`
? html`<ha-tooltip
slot="icons"
.content=${this.hass.localize(
"ui.panel.config.automation.editor.actions.continue_on_error"
)}
>
<ha-svg-icon .path=${mdiAlertCircleCheck}></ha-svg-icon>
</ha-tooltip>`
: nothing}
${!this.optionsInSidebar
? html`<ha-md-button-menu
@@ -462,6 +460,9 @@ export default class HaAutomationActionRow extends LitElement {
.sortSelected=${this.sortSelected}
@click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse}
@copy-row=${this._copyAction}
@cut-row=${this._cutAction}
@delete-row=${this._onDelete}
>${this._renderRow()}</ha-automation-row
>`
: html`

View File

@@ -369,6 +369,9 @@ export default class HaAutomationConditionRow extends LitElement {
.sortSelected=${this.sortSelected}
@click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse}
@copy-row=${this._copyCondition}
@cut-row=${this._cutCondition}
@delete-row=${this._onDelete}
>${this._renderRow()}</ha-automation-row
>`
: html`

View File

@@ -24,7 +24,7 @@ import { property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate";
import { navigate } from "../../../common/navigate";
import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button";
@@ -702,7 +702,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
{ err_no: err.status_code }
),
});
goBack("/config");
history.back();
}
}
@@ -853,7 +853,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
private _backTapped = async () => {
const result = await this._confirmUnsavedChanged();
if (result) {
afterNextRender(() => goBack("/config"));
afterNextRender(() => history.back());
}
};
@@ -941,7 +941,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
private async _delete() {
if (this.automationId) {
await deleteAutomation(this.hass, this.automationId);
goBack("/config");
history.back();
}
}

View File

@@ -1,7 +1,7 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import "../../../components/ha-resizable-bottom-sheet";
import type { HaResizableBottomSheet } from "../../../components/ha-resizable-bottom-sheet";
import "../../../components/ha-bottom-sheet";
import type { HaBottomSheet } from "../../../components/ha-bottom-sheet";
import {
isCondition,
isScriptField,
@@ -37,8 +37,7 @@ export default class HaAutomationSidebar extends LitElement {
@state() private _yamlMode = false;
@query("ha-resizable-bottom-sheet")
private _bottomSheetElement?: HaResizableBottomSheet;
@query("ha-bottom-sheet") private _bottomSheetElement?: HaBottomSheet;
private _renderContent() {
// get config type
@@ -148,9 +147,9 @@ export default class HaAutomationSidebar extends LitElement {
if (this.narrow) {
return html`
<ha-resizable-bottom-sheet @bottom-sheet-closed=${this._closeSidebar}>
<ha-bottom-sheet @bottom-sheet-closed=${this._closeSidebar}>
${this._renderContent()}
</ha-resizable-bottom-sheet>
</ha-bottom-sheet>
`;
}

View File

@@ -254,7 +254,4 @@ export const sidebarEditorStyles = css`
display: none;
}
}
ha-md-menu-item {
--mdc-icon-size: 24px;
}
`;

View File

@@ -358,6 +358,9 @@ export default class HaAutomationTriggerRow extends LitElement {
.highlight=${this.highlight}
.sortSelected=${this.sortSelected}
@click=${this._toggleSidebar}
@copy-row=${this._copyTrigger}
@cut-row=${this._cutTrigger}
@delete-row=${this._onDelete}
>${this._selected
? "selected"
: nothing}${this._renderRow()}</ha-automation-row

View File

@@ -5,7 +5,7 @@ import {
mdiGroup,
mdiPlus,
} from "@mdi/js";
import { goBack, navigate } from "../../../../../../common/navigate";
import { navigate } from "../../../../../../common/navigate";
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import { fetchZHADevice } from "../../../../../../data/zha";
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
@@ -102,7 +102,7 @@ export const getZHADeviceActions = async (
ieee: zhaDevice.ieee,
});
goBack("/config");
history.back();
},
});
}

View File

@@ -89,7 +89,6 @@ import {
loadDeviceRegistryDetailDialog,
showDeviceRegistryDetailDialog,
} from "./device-registry-detail/show-dialog-device-registry-detail";
import { slugify } from "../../../common/string/slugify";
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string | null;
@@ -556,21 +555,16 @@ export class HaConfigDevicePage extends LitElement {
</a>
`
: html`
<ha-list-item
.id="scene-${slugify(entityState.entity_id)}"
hasMeta
.scene=${entityState}
>
${computeStateName(entityState)}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
<ha-tooltip
.for="scene-${slugify(entityState.entity_id)}"
placement="left"
>
${this.hass.localize(
.content=${this.hass.localize(
"ui.panel.config.devices.cant_edit"
)}
>
<ha-list-item hasMeta .scene=${entityState}>
${computeStateName(entityState)}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
</ha-tooltip>
`;
})}

View File

@@ -671,14 +671,13 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon
.id="svg-icon-${device.id}"
.path=${mdiCancel}
></ha-svg-icon>
<ha-tooltip .for="svg-icon-${device.id}" placement="left">
${this.hass.localize(
<ha-tooltip
placement="left"
.content=${this.hass.localize(
"ui.panel.config.entities.picker.status.disabled"
)}
>
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
</ha-tooltip>
</div>
`

View File

@@ -114,7 +114,6 @@ import { isHelperDomain } from "../helpers/const";
import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { slugify } from "../../../common/string/slugify";
export interface StateEntity
extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
@@ -393,27 +392,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon
.id="status-icon-${slugify(entry.entity_id)}"
style=${styleMap({
color: entry.unavailable ? "var(--error-color)" : "",
})}
.path=${entry.restored
? mdiRestoreAlert
: entry.unavailable
? mdiAlertCircle
: entry.disabled_by
? mdiCancel
: entry.hidden_by
? mdiEyeOff
: mdiPencilOff}
></ha-svg-icon>
<ha-tooltip
.for="status-icon-${slugify(entry.entity_id)}"
placement="left"
>
${entry.restored
.content=${entry.restored
? this.hass.localize(
"ui.panel.config.entities.picker.status.not_provided"
)
@@ -432,6 +413,21 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
: this.hass.localize(
"ui.panel.config.entities.picker.status.unmanageable"
)}
>
<ha-svg-icon
style=${styleMap({
color: entry.unavailable ? "var(--error-color)" : "",
})}
.path=${entry.restored
? mdiRestoreAlert
: entry.unavailable
? mdiAlertCircle
: entry.disabled_by
? mdiCancel
: entry.hidden_by
? mdiEyeOff
: mdiPencilOff}
></ha-svg-icon>
</ha-tooltip>
</div>
`

View File

@@ -236,18 +236,17 @@ export class DialogHelperDetail extends LitElement {
<span class="item-text"> ${label} </span>
${isLoaded
? html`<ha-icon-next slot="meta"></ha-icon-next>`
: html` <ha-svg-icon
slot="meta"
.id="icon-${domain}"
path=${mdiAlertOutline}
@click=${stopPropagation}
></ha-svg-icon>
<ha-tooltip .for="icon-${domain}">
${this.hass.localize(
"ui.dialogs.helper_settings.platform_not_loaded",
{ platform: domain }
)}
</ha-tooltip>`}
: html`<ha-tooltip
hoist
slot="meta"
.content=${this.hass.localize(
"ui.dialogs.helper_settings.platform_not_loaded",
{ platform: domain }
)}
@click=${stopPropagation}
>
<ha-svg-icon path=${mdiAlertOutline}></ha-svg-icon>
</ha-tooltip>`}
</ha-list-item>
`;
})}

View File

@@ -110,7 +110,6 @@ import { renderConfigEntryError } from "../integrations/ha-config-integration-pa
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { isHelperDomain } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import { slugify } from "../../../common/string/slugify";
interface HelperItem {
id: string;
@@ -362,16 +361,13 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon
.id="icon-edit-${slugify(helper.entity_id)}"
.path=${mdiPencilOff}
></ha-svg-icon>
<ha-tooltip
.for="icon-edit-${slugify(helper.entity_id)}"
placement="left"
>${this.hass.localize(
.content=${this.hass.localize(
"ui.panel.config.entities.picker.status.unmanageable"
)}
>
<ha-svg-icon .path=${mdiPencilOff}></ha-svg-icon>
</ha-tooltip>
</div>
`

View File

@@ -159,32 +159,29 @@ export class HaIntegrationCard extends LitElement {
? "overwrites"
: "custom"}"
>
<ha-svg-icon
id="icon-custom"
.path=${mdiPackageVariant}
></ha-svg-icon>
<ha-tooltip
for="icon-custom"
hoist
.placement=${computeRTL(this.hass) ? "right" : "left"}
>
${this.hass.localize(
.content=${this.hass.localize(
this.manifest.overwrites_built_in
? "ui.panel.config.integrations.config_entry.custom_overwrites_core"
: "ui.panel.config.integrations.config_entry.custom_integration"
)}
>
<ha-svg-icon .path=${mdiPackageVariant}></ha-svg-icon>
</ha-tooltip>
</span>`
: nothing}
${this.manifest && this.manifest.iot_class?.startsWith("cloud_")
? html`<div class="icon cloud">
<ha-svg-icon id="icon-cloud" .path=${mdiWeb}></ha-svg-icon>
<ha-tooltip
for="icon-cloud"
hoist
.placement=${computeRTL(this.hass) ? "right" : "left"}
>
${this.hass.localize(
.content=${this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud"
)}
>
<ha-svg-icon .path=${mdiWeb}></ha-svg-icon>
</ha-tooltip>
</div>`
: nothing}
@@ -192,18 +189,15 @@ export class HaIntegrationCard extends LitElement {
!this.manifest?.config_flow &&
!this.items.every((itm) => itm.source === "system")
? html`<div class="icon yaml">
<ha-svg-icon
id="icon-yaml"
.path=${mdiFileCodeOutline}
></ha-svg-icon>
<ha-tooltip
for="icon-yaml"
hoist
.placement=${computeRTL(this.hass) ? "right" : "left"}
>
${this.hass.localize(
.content=${this.hass.localize(
"ui.panel.config.integrations.config_entry.no_config_flow"
)}
</ha-tooltip>
>
<ha-svg-icon .path=${mdiFileCodeOutline}></ha-svg-icon
></ha-tooltip>
</div>`
: nothing}
</div>

View File

@@ -74,45 +74,45 @@ export class HaIntegrationListItem extends ListItemBase {
}
return html`<span class="mdc-deprecated-list-item__meta material-icons">
${this.integration.cloud
? html` <ha-svg-icon id="icon-cloud" .path=${mdiWeb}></ha-svg-icon>
<ha-tooltip for="icon-cloud" placement="left"
>${this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud"
)}
</ha-tooltip>`
? html`<ha-tooltip
placement="left"
.content=${this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud"
)}
><ha-svg-icon .path=${mdiWeb}></ha-svg-icon
></ha-tooltip>`
: nothing}
${!this.integration.is_built_in
? html`<span
class=${this.integration.overwrites_built_in
? "overwrites"
: "custom"}
>
<ha-svg-icon
id="icon-custom"
.path=${mdiPackageVariant}
></ha-svg-icon>
<ha-tooltip for="icon-custom" placement="left"
>${this.hass.localize(
><ha-tooltip
placement="left"
.content=${this.hass.localize(
this.integration.overwrites_built_in
? "ui.panel.config.integrations.config_entry.custom_overwrites_core"
: "ui.panel.config.integrations.config_entry.custom_integration"
)}</ha-tooltip
></span
>`
)}
><ha-svg-icon
.path=${mdiPackageVariant}
></ha-svg-icon></ha-tooltip
></span>`
: nothing}
${!this.integration.config_flow &&
!this.integration.integrations &&
!this.integration.iot_standards
? html` <ha-svg-icon
id="icon-yaml"
? html`<ha-tooltip
placement="left"
.content=${this.hass.localize(
"ui.panel.config.integrations.config_entry.yaml_only"
)}
>
<ha-svg-icon
.path=${mdiFileCodeOutline}
class="open-in-new"
></ha-svg-icon>
<ha-tooltip for="icon-yaml" placement="left">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.yaml_only"
)}
</ha-tooltip>`
</ha-tooltip>`
: html`<ha-icon-next></ha-icon-next>`}
</span>`;
}

View File

@@ -307,18 +307,14 @@ class DialogZHAReconfigureDevice extends LitElement {
`
: html`
<span class="stage">
<ha-svg-icon
.id="svg-icon-${clusterStatus
.cluster.name}"
.path=${mdiCloseCircle}
class="failed"
></ha-svg-icon>
<ha-tooltip
.for="svg-icon-${clusterStatus
.cluster.name}"
placement="top"
.content=${attribute.status}
>
${attribute.status}
<ha-svg-icon
.path=${mdiCloseCircle}
class="failed"
></ha-svg-icon>
</ha-tooltip>
</span>
`}

View File

@@ -21,7 +21,6 @@ import "../../../../../components/ha-list-item";
import "../../../../../components/ha-progress-ring";
import "../../../../../components/ha-spinner";
import "../../../../../components/ha-svg-icon";
import { goBack } from "../../../../../common/navigate";
import type { ConfigEntry } from "../../../../../data/config_entries";
import {
ERROR_STATES,
@@ -619,7 +618,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
}
private _handleBack(): void {
goBack("/config");
history.back();
}
private _fetchData = async () => {

View File

@@ -367,7 +367,6 @@ class ZWaveJSNodeConfig extends LitElement {
return html`
${labelAndDescription}
<ha-select
fixedMenuPosition
.disabled=${!item.metadata.writeable}
.value=${item.value?.toString()}
.key=${id}

View File

@@ -156,18 +156,16 @@ export class HaConfigLovelaceDashboards extends LitElement {
${dashboard.title}
${dashboard.default
? html`
<ha-svg-icon
.id="default-icon-${dashboard.title}"
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);"
.path=${mdiCheckCircleOutline}
></ha-svg-icon>
<ha-tooltip
.for="default-icon-${dashboard.title}"
placement="right"
>
${this.hass.localize(
.content=${this.hass.localize(
`ui.panel.config.lovelace.dashboards.default_dashboard`
)}
placement="right"
>
<ha-svg-icon
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);"
.path=${mdiCheckCircleOutline}
></ha-svg-icon>
</ha-tooltip>
`
: nothing}

View File

@@ -106,7 +106,6 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { slugify } from "../../../common/string/slugify";
type SceneItem = SceneEntity & {
name: string;
@@ -319,18 +318,16 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
template: (scene) =>
!scene.attributes.id
? html`
<ha-svg-icon
.id="svg-icon-${slugify(scene.entity_id)}"
.path=${mdiPencilOff}
style="color: var(--secondary-text-color)"
></ha-svg-icon>
<ha-tooltip
.for="svg-icon-${slugify(scene.entity_id)}"
placement="left"
>
${this.hass.localize(
.content=${this.hass.localize(
"ui.panel.config.scene.picker.only_editable"
)}
>
<ha-svg-icon
.path=${mdiPencilOff}
style="color: var(--secondary-text-color)"
></ha-svg-icon>
</ha-tooltip>
`
: nothing,

View File

@@ -24,7 +24,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { goBack, navigate } from "../../../common/navigate";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/device/ha-device-picker";
@@ -806,7 +806,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
{ err_no: err.status_code }
),
});
goBack("/config");
history.back();
return;
}
@@ -988,7 +988,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
if (this._mode === "live") {
applyScene(this.hass, this._storedStates);
}
afterNextRender(() => goBack("/config"));
afterNextRender(() => history.back());
}
private _deleteTapped(): void {
@@ -1012,7 +1012,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
if (this._mode === "live") {
applyScene(this.hass, this._storedStates);
}
goBack("/config");
history.back();
}
private async _confirmUnsavedChanged(): Promise<boolean> {

View File

@@ -21,7 +21,7 @@ import { LitElement, css, html, nothing } from "lit";
import { property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status";
@@ -596,7 +596,7 @@ export class HaScriptEditor extends SubscribeMixin(
{ err_no: resp.status_code || resp.code }
)
);
goBack("/config");
history.back();
}
);
}
@@ -762,7 +762,7 @@ export class HaScriptEditor extends SubscribeMixin(
private _backTapped = async () => {
const result = await this._confirmUnsavedChanged();
if (result) {
afterNextRender(() => goBack("/config"));
afterNextRender(() => history.back());
}
};
@@ -852,7 +852,7 @@ export class HaScriptEditor extends SubscribeMixin(
private async _delete() {
await deleteScript(this.hass, this.scriptId!);
goBack("/config");
history.back();
}
private async _switchUiMode() {

View File

@@ -25,47 +25,48 @@ export class VoiceAssistantExposeAssistantIcon extends LitElement {
if (!this.assistant || !voiceAssistants[this.assistant]) return nothing;
return html`
<div class="container" id="container">
<img
class="logo"
style=${styleMap({
filter: this.manual ? "grayscale(100%)" : undefined,
})}
alt=${voiceAssistants[this.assistant].name}
src=${brandsUrl({
domain: voiceAssistants[this.assistant].domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
slot="prefix"
/>
${this.unsupported
? html`
<ha-svg-icon
.path=${mdiAlertCircle}
class="unsupported"
></ha-svg-icon>
`
: nothing}
</div>
<ha-tooltip
for="container"
placement="left"
.disabled=${!this.unsupported && !this.manual}
placement="left"
>
${this.unsupported
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.not_supported"
)
: ""}
${this.unsupported && this.manual ? html`<br />` : nothing}
${this.manual
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.manually_configured"
)
: nothing}
<div class="container">
<img
class="logo"
style=${styleMap({
filter: this.manual ? "grayscale(100%)" : undefined,
})}
alt=${voiceAssistants[this.assistant].name}
src=${brandsUrl({
domain: voiceAssistants[this.assistant].domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
slot="prefix"
/>
${this.unsupported
? html`
<ha-svg-icon
.path=${mdiAlertCircle}
class="unsupported"
></ha-svg-icon>
`
: nothing}
</div>
<span slot="content">
${this.unsupported
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.not_supported"
)
: ""}
${this.unsupported && this.manual ? html`<br />` : nothing}
${this.manual
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.manually_configured"
)
: nothing}
</span>
</ha-tooltip>
`;
}

View File

@@ -607,32 +607,34 @@ export class VoiceAssistantsExpose extends LitElement {
>
`
: html`
<ha-icon-button
id="expose-button"
@click=${this._exposeSelected}
.path=${mdiPlusBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.expose"
)}
></ha-icon-button>
<ha-tooltip for="expose-button" placement="left">
${this.hass.localize(
<ha-tooltip
.content=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.expose"
)}
placement="left"
>
<ha-icon-button
@click=${this._exposeSelected}
.path=${mdiPlusBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.expose"
)}
></ha-icon-button>
</ha-tooltip>
<ha-tooltip for="unexpose-button" placement="left">
${this.hass.localize(
<ha-tooltip
content=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.unexpose"
)}
placement="left"
>
<ha-icon-button
@click=${this._unexposeSelected}
.path=${mdiCloseBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.unexpose"
)}
></ha-icon-button>
</ha-tooltip>
<ha-icon-button
id="unexpose-button"
@click=${this._unexposeSelected}
.path=${mdiCloseBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.unexpose"
)}
></ha-icon-button>
`}
</div>
`

View File

@@ -46,7 +46,6 @@ import "../ha-config-section";
import { configSections } from "../ha-panel-config";
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
import { slugify } from "../../../common/string/slugify";
@customElement("ha-config-zone")
export class HaConfigZone extends SubscribeMixin(LitElement) {
@@ -201,8 +200,17 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
stateObject.entity_id === "zone.home" &&
!this._canEditCore
? nothing
: html`<ha-icon-button
.id="zone-${slugify(stateObject.entity_id)}"
: html`<ha-tooltip
slot="meta"
placement="left"
.content=${hass.localize(
"ui.panel.config.zone.configured_in_yaml"
)}
.disabled=${stateObject.entity_id === "zone.home"}
hoist
>
<ha-icon-button
.id=${!this.narrow ? stateObject.entity_id : ""}
.entityId=${stateObject.entity_id}
.noEdit=${stateObject.entity_id !== "zone.home" ||
!this._canEditCore}
@@ -214,18 +222,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
name: hass.config.location_name,
})}
@click=${this._editHomeZone}
slot="meta"
></ha-icon-button>
<ha-tooltip
.for="zone-${slugify(stateObject.entity_id)}"
placement="left"
.disabled=${stateObject.entity_id === "zone.home"}
hoist
>
${hass.localize(
"ui.panel.config.zone.configured_in_yaml"
)}
</ha-tooltip>`}
</ha-tooltip>`}
</ha-list-item>
`
)}

View File

@@ -13,7 +13,7 @@ import "../lovelace/components/hui-energy-period-selector";
import type { Lovelace } from "../lovelace/types";
import "../lovelace/views/hui-view";
import "../lovelace/views/hui-view-container";
import { goBack, navigate } from "../../common/navigate";
import { navigate } from "../../common/navigate";
import type {
GridSourceTypeEnergyPreference,
SolarSourceTypeEnergyPreference,
@@ -70,7 +70,7 @@ class PanelEnergy extends LitElement {
private _back(ev) {
ev.stopPropagation();
goBack();
history.back();
}
protected render(): TemplateResult {

View File

@@ -17,7 +17,7 @@ import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import { storage } from "../../common/decorators/storage";
import { computeDomain } from "../../common/entity/compute_domain";
import { goBack, navigate } from "../../common/navigate";
import { navigate } from "../../common/navigate";
import { constructUrlCurrentPath } from "../../common/url/construct-url";
import {
createSearchParam,
@@ -114,7 +114,7 @@ class HaPanelHistory extends LitElement {
}
private _goBack(): void {
goBack();
history.back();
}
protected render() {
@@ -182,6 +182,7 @@ class HaPanelHistory extends LitElement {
.disabled=${this._isLoading}
add-on-top
@value-changed=${this._targetsChanged}
compact
></ha-target-picker>
</div>
${this._isLoading

View File

@@ -4,7 +4,7 @@ import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { goBack, navigate } from "../../common/navigate";
import { navigate } from "../../common/navigate";
import { constructUrlCurrentPath } from "../../common/url/construct-url";
import {
createSearchParam,
@@ -60,7 +60,7 @@ export class HaPanelLogbook extends LitElement {
}
private _goBack(): void {
goBack();
history.back();
}
protected render() {
@@ -104,6 +104,7 @@ export class HaPanelLogbook extends LitElement {
.value=${this._targetPickerValue}
add-on-top
@value-changed=${this._targetsChanged}
compact
></ha-target-picker>
</div>

View File

@@ -133,12 +133,14 @@ class HuiEnergyCarbonGaugeCard
"--gauge-color": this._computeSeverity(value),
})}
></ha-gauge>
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<ha-tooltip for="info" placement="left">
${this.hass.localize(
<ha-tooltip
.content=${this.hass.localize(
"ui.panel.lovelace.cards.energy.carbon_consumed_gauge.card_indicates_energy_used"
)}
placement="left"
hoist
>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip>
<div class="name">
${this.hass.localize(

View File

@@ -114,15 +114,17 @@ class HuiEnergyGridGaugeCard
label="kWh"
needle
></ha-gauge>
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<ha-tooltip for="info" placement="left">
${this.hass.localize(
"ui.panel.lovelace.cards.energy.grid_neutrality_gauge.energy_dependency"
)}
<br /><br />
${this.hass.localize(
"ui.panel.lovelace.cards.energy.grid_neutrality_gauge.color_explain"
)}
<ha-tooltip placement="left" hoist>
<span slot="content">
${this.hass.localize(
"ui.panel.lovelace.cards.energy.grid_neutrality_gauge.energy_dependency"
)}
<br /><br />
${this.hass.localize(
"ui.panel.lovelace.cards.energy.grid_neutrality_gauge.color_explain"
)}
</span>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip>
<div class="name">
${returnedToGrid! >= consumedFromGrid!

View File

@@ -110,11 +110,14 @@ class HuiEnergySelfSufficiencyGaugeCard
"--gauge-color": this._computeSeverity(value),
})}
></ha-gauge>
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<ha-tooltip for="info" placement="left">
${this.hass.localize(
<ha-tooltip
placement="left"
.content=${this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.card_indicates_self_sufficiency_quota"
)}
hoist
>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip>
<div class="name">
${this.hass.localize(

View File

@@ -102,15 +102,17 @@ class HuiEnergySolarGaugeCard
"--gauge-color": this._computeSeverity(value),
})}
></ha-gauge>
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<ha-tooltip for="info" placement="left">
${this.hass.localize(
"ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used"
)}
<br /><br />
${this.hass.localize(
"ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used_charge_home_bat"
)}
<ha-tooltip placement="left" hoist>
<span slot="content">
${this.hass.localize(
"ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used"
)}
<br /><br />
${this.hass.localize(
"ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used_charge_home_bat"
)}
</span>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip>
<div class="name">
${this.hass.localize(
@@ -174,6 +176,10 @@ class HuiEnergySolarGaugeCard
top: 4px;
color: var(--secondary-text-color);
}
ha-tooltip::part(base__popup) {
margin-top: 4px;
}
`;
}

View File

@@ -1,152 +0,0 @@
import { mdiDelete, mdiDrag, mdiPencil } from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { deleteSection } from "../editor/config-util";
import { findLovelaceContainer } from "../editor/lovelace-path";
import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog";
import type { Lovelace } from "../types";
@customElement("hui-section-edit-mode")
export class HuiSectionEditMode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace!: Lovelace;
@property({ attribute: false, type: Number }) public index!: number;
@property({ attribute: false, type: Number }) public viewIndex!: number;
protected render(): TemplateResult {
return html`
<div class="section-header">
<div class="section-actions">
<ha-svg-icon
aria-hidden="true"
class="handle"
.path=${mdiDrag}
></ha-svg-icon>
<ha-icon-button
.label=${this.hass.localize("ui.common.edit")}
@click=${this._editSection}
.path=${mdiPencil}
></ha-icon-button>
<ha-icon-button
.label=${this.hass.localize("ui.common.delete")}
@click=${this._deleteSection}
.path=${mdiDelete}
></ha-icon-button>
</div>
</div>
<div class="section-wrapper">
<slot></slot>
</div>
`;
}
private async _editSection(ev) {
ev.stopPropagation();
showEditSectionDialog(this, {
lovelace: this.lovelace!,
lovelaceConfig: this.lovelace!.config,
saveConfig: (newConfig) => {
this.lovelace!.saveConfig(newConfig);
},
viewIndex: this.viewIndex,
sectionIndex: this.index,
});
}
private async _deleteSection(ev) {
ev.stopPropagation();
const path = [this.viewIndex, this.index] as [number, number];
const section = findLovelaceContainer(this.lovelace!.config, path);
const cardCount = "cards" in section && section.cards?.length;
if (cardCount) {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.lovelace.editor.delete_section.title"
),
text: this.hass.localize(
`ui.panel.lovelace.editor.delete_section.text`
),
confirmText: this.hass.localize("ui.common.delete"),
destructive: true,
});
if (!confirm) return;
}
const newConfig = deleteSection(
this.lovelace!.config,
this.viewIndex,
this.index
);
this.lovelace!.saveConfig(newConfig);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.section-header {
position: relative;
height: 34px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.section-actions {
position: absolute;
height: 36px;
bottom: -2px;
right: 0;
inset-inline-end: 0;
inset-inline-start: initial;
opacity: 1;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s ease-in-out;
border-radius: var(--ha-card-border-radius, 12px);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
background: var(--secondary-background-color);
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--primary-text-color);
}
.handle {
cursor: grab;
padding: 8px;
}
.section-wrapper {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
border-start-end-radius: 0;
border: 2px dashed var(--divider-color);
min-height: var(--row-height);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-section-edit-mode": HuiSectionEditMode;
}
}

View File

@@ -26,7 +26,7 @@ import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
import { goBack, navigate } from "../../common/navigate";
import { navigate } from "../../common/navigate";
import type { LocalizeKeys } from "../../common/translations/localize";
import { constructUrlCurrentPath } from "../../common/url/construct-url";
import {
@@ -47,7 +47,6 @@ import "../../components/ha-menu-button";
import "../../components/ha-svg-icon";
import "../../components/ha-tab-group";
import "../../components/ha-tab-group-tab";
import "../../components/ha-tooltip";
import { createAreaRegistryEntry } from "../../data/area_registry";
import type { LovelacePanelConfig } from "../../data/lovelace";
import type { LovelaceConfig } from "../../data/lovelace/config/types";
@@ -230,10 +229,10 @@ class HUIRoot extends LitElement {
},
{
icon: mdiSofa,
key: "ui.panel.lovelace.menu.create_area",
key: "ui.panel.lovelace.menu.add_area",
visible: true,
action: this._createArea,
overflowAction: this._handleCreateArea,
action: this._addArea,
overflowAction: this._handleAddArea,
},
{
icon: mdiAccount,
@@ -308,7 +307,7 @@ class HUIRoot extends LitElement {
(i) => i.visible && (!i.overflow || overflowCanPromote)
);
buttonItems.forEach((item, index) => {
buttonItems.forEach((item) => {
const label = [this.hass!.localize(item.key), item.suffix].join(" ");
const button = item.subItems
? html`
@@ -342,14 +341,11 @@ class HUIRoot extends LitElement {
</ha-button-menu>
`
: html`
<ha-icon-button
slot="actionItems"
.id="button-${index}"
.path=${item.icon}
@click=${item.buttonAction}
></ha-icon-button>
<ha-tooltip placement="bottom" .for="button-${index}">
${label}
<ha-tooltip slot="actionItems" placement="bottom" .content=${label}>
<ha-icon-button
.path=${item.icon}
@click=${item.buttonAction}
></ha-icon-button>
</ha-tooltip>
`;
result.push(button);
@@ -366,7 +362,6 @@ class HUIRoot extends LitElement {
}
showListItemsDialog(this, {
title: title,
mode: this.narrow ? "bottom-sheet" : "dialog",
items: i.subItems!.map((si) => ({
iconPath: si.icon,
label: this.hass!.localize(si.key),
@@ -804,7 +799,7 @@ class HUIRoot extends LitElement {
if (curViewConfig?.back_path != null) {
navigate(curViewConfig.back_path, { replace: true });
} else if (history.length > 1) {
goBack();
history.back();
} else if (!views[0].subview) {
navigate(this.route!.prefix, { replace: true });
} else {
@@ -838,14 +833,14 @@ class HUIRoot extends LitElement {
showNewAutomationDialog(this, { mode: "automation" });
};
private _handleCreateArea(ev: CustomEvent<RequestSelectedDetail>): void {
private _handleAddArea(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._createArea();
this._addArea();
}
private _createArea = async () => {
private _addArea = async () => {
await this.hass.loadFragmentTranslation("config");
showAreaRegistryDetailDialog(this, {
createEntry: async (values) => {
@@ -855,15 +850,13 @@ class HUIRoot extends LitElement {
}
showToast(this, {
message: this.hass.localize(
"ui.panel.lovelace.menu.create_area_success"
"ui.panel.lovelace.menu.add_area_success"
),
action: {
action: () => {
navigate(`/config/areas/area/${area.area_id}`);
},
text: this.hass.localize(
"ui.panel.lovelace.menu.create_area_action"
),
text: this.hass.localize("ui.panel.lovelace.menu.add_area_action"),
},
});
},

View File

@@ -99,7 +99,12 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
@item-removed=${this._cardRemoved}
invert-swap
>
<div class="container">
<div
class="container ${classMap({
"edit-mode": editMode,
"import-only": this.importOnly,
})}"
>
${repeat(
cardsConfig,
(cardConfig) => this._getKey(cardConfig),
@@ -233,6 +238,19 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
margin: 0 auto;
}
.container.edit-mode {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
border-start-end-radius: 0;
border: 2px dashed var(--divider-color);
min-height: var(--row-height);
}
.container.import-only {
border: none;
padding: 0 !important;
}
.card {
border-radius: var(--ha-card-border-radius, 12px);
position: relative;

View File

@@ -66,34 +66,9 @@ export const HOME_SUMMARIES_FILTERS: Record<HomeSummary, EntityFilter[]> = {
},
{
domain: "binary_sensor",
device_class: [
// Locks
"lock",
// Openings
"door",
"window",
"garage_door",
"opening",
// Humans
"motion",
"presence",
"occupancy",
// Safety
"carbon_monoxide",
"gas",
"moisture",
"safety",
"smoke",
"tamper",
],
device_class: ["door", "garage_door", "motion"],
entity_category: "none",
},
// We also want the tamper sensors when they are diagnostic
{
domain: "binary_sensor",
device_class: ["tamper"],
entity_category: "diagnostic",
},
],
media_players: [{ domain: "media_player", entity_category: "none" }],
};

View File

@@ -1,5 +1,11 @@
import { ResizeController } from "@lit-labs/observers/resize-controller";
import { mdiEyeOff, mdiViewGridPlus } from "@mdi/js";
import {
mdiDelete,
mdiDrag,
mdiEyeOff,
mdiPencil,
mdiViewGridPlus,
} from "@mdi/js";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -16,21 +22,28 @@ import type { LovelaceViewElement } from "../../../data/lovelace";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types";
import type { HuiBadge } from "../badges/hui-badge";
import "./hui-view-header";
import type { HuiCard } from "../cards/hui-card";
import "../components/hui-badge-edit-mode";
import "../components/hui-section-edit-mode";
import { addSection, moveCard, moveSection } from "../editor/config-util";
import {
addSection,
deleteSection,
moveCard,
moveSection,
} from "../editor/config-util";
import type { LovelaceCardPath } from "../editor/lovelace-path";
import {
findLovelaceContainer,
findLovelaceItems,
getLovelaceContainerPath,
parseLovelaceCardPath,
} from "../editor/lovelace-path";
import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog";
import type { HuiSection } from "../sections/hui-section";
import type { Lovelace } from "../types";
import "./hui-view-header";
export const DEFAULT_MAX_COLUMNS = 4;
@@ -192,19 +205,41 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
})}
>
${
editMode
this.lovelace?.editMode
? html`
<hui-section-edit-mode
.hass=${this.hass}
.lovelace=${this.lovelace}
.index=${idx}
.viewIndex=${this.index}
>
${section}
</hui-section-edit-mode>
<div class="section-header">
${editMode
? html`
<div class="section-actions">
<ha-svg-icon
aria-hidden="true"
class="handle"
.path=${mdiDrag}
></ha-svg-icon>
<ha-icon-button
.label=${this.hass.localize(
"ui.common.edit"
)}
@click=${this._editSection}
.index=${idx}
.path=${mdiPencil}
></ha-icon-button>
<ha-icon-button
.label=${this.hass.localize(
"ui.common.delete"
)}
@click=${this._deleteSection}
.index=${idx}
.path=${mdiDelete}
></ha-icon-button>
</div>
`
: nothing}
</div>
`
: section
: nothing
}
${section}
</div>
</div>
`;
@@ -340,6 +375,48 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
this.lovelace!.saveConfig(newConfig);
}
private async _editSection(ev) {
const index = ev.currentTarget.index;
showEditSectionDialog(this, {
lovelace: this.lovelace!,
lovelaceConfig: this.lovelace!.config,
saveConfig: (newConfig) => {
this.lovelace!.saveConfig(newConfig);
},
viewIndex: this.index!,
sectionIndex: index,
});
}
private async _deleteSection(ev) {
const index = ev.currentTarget.index;
const path = [this.index!, index] as [number, number];
const section = findLovelaceContainer(this.lovelace!.config, path);
const cardCount = "cards" in section && section.cards?.length;
if (cardCount) {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.lovelace.editor.delete_section.title"
),
text: this.hass.localize(
`ui.panel.lovelace.editor.delete_section.text`
),
confirmText: this.hass.localize("ui.common.delete"),
destructive: true,
});
if (!confirm) return;
}
const newConfig = deleteSection(this.lovelace!.config, this.index!, index);
this.lovelace!.saveConfig(newConfig);
}
private _sectionMoved(ev: CustomEvent) {
ev.stopPropagation();
const { oldIndex, newIndex } = ev.detail;
@@ -409,6 +486,11 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
grid-auto-flow: row dense;
}
.handle {
cursor: grab;
padding: 8px;
}
.create-section-container {
position: relative;
display: flex;
@@ -492,6 +574,35 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
);
}
.section-header {
position: relative;
height: 34px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.section-actions {
position: absolute;
height: 36px;
bottom: -2px;
right: 0;
inset-inline-end: 0;
inset-inline-start: initial;
opacity: 1;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s ease-in-out;
border-radius: var(--ha-card-border-radius, 12px);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
background: var(--secondary-background-color);
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--primary-text-color);
}
.imported-cards {
--column-span: var(--column-count);
--row-span: 1;

View File

@@ -661,20 +661,35 @@
},
"target-picker": {
"expand": "Expand",
"collapse": "Collapse",
"expand_floor_id": "Split this floor into separate areas.",
"expand_area_id": "Split this area into separate devices and entities.",
"expand_device_id": "Split this device into separate entities.",
"expand_label_id": "Split this label into separate areas, devices and entities.",
"remove": "Remove",
"remove_floor_id": "Remove floor",
"remove_floors": "Remove floors",
"remove_area_id": "Remove area",
"remove_areas": "Remove areas",
"remove_device_id": "Remove device",
"remove_devices": "Remove devices",
"remove_entity_id": "Remove entity",
"remove_entitys": "Remove entities",
"remove_label_id": "Remove label",
"remove_labels": "Remove labels",
"add_area_id": "Choose area",
"add_device_id": "Choose device",
"add_entity_id": "Choose entity",
"add_label_id": "Choose label"
"add_label_id": "Choose label",
"devices_count": "{count} {count, plural,\n one {device}\n other {devices}\n}",
"entities_count": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
"selected": {
"entity": "Entities: {count}",
"device": "Devices: {count}",
"area": "Areas: {count}",
"label": "Labels: {count}",
"floor": "Floors: {count}"
}
},
"subpage-data-table": {
"filters": "Filters",
@@ -2461,7 +2476,7 @@
"local_backup_location": {
"title": "Change default local backup location",
"description": "Change the default location where local backups are stored on your Home Assistant instance.",
"note": "This location will be used when you create a backup using the Supervisor actions in an automation for example.",
"note": "This location will be used when you create a backup using the supervisor actions in an automation for example.",
"options": {
"default_backup_mount": {
"name": "Default location"
@@ -2893,8 +2908,8 @@
"description": "Creates a backup of your add-on and its data. That way you can keep around the previous version of the add-on, so you can always roll back to it if needed.",
"local_only": "This backup is only saved on this system.",
"retention_description": "Prevent your system from filling up with old versions.",
"error_load": "Error loading Supervisor update config: {error}",
"error_save": "Error saving Supervisor update config: {error}"
"error_load": "Error loading supervisor update config: {error}",
"error_save": "Error saving supervisor update config: {error}"
}
},
"details": {
@@ -5550,7 +5565,7 @@
},
"custom_integration": "Custom integration",
"legacy_integration": "Legacy integration",
"custom_overwrites_core": "Custom integration that replaces a Core component",
"custom_overwrites_core": "Custom integration that replaces a core component",
"depends_on_cloud": "Requires Internet",
"yaml_only": "This integration cannot be set up from the UI",
"no_config_flow": "This integration was not set up from the UI",
@@ -7059,9 +7074,9 @@
"add": "Add to Home Assistant",
"add_device": "Add device",
"create_automation": "Create automation",
"create_area": "Create area",
"create_area_success": "Area created",
"create_area_action": "View area",
"add_area": "Add area",
"add_area_success": "Area added",
"add_area_action": "View area",
"add_person_success": "Person added",
"add_person_action": "View persons",
"add_person": "Add person"
@@ -8889,13 +8904,13 @@
"title": "Entity is not recorded",
"info_text_1": "State changes of ''{name}'' ({statistic_id}) are not recorded, therefore, we cannot track long term statistics for it.",
"info_text_2": "You probably excluded this entity, or have just included some entities.",
"info_text_3_link": "See the Recorder documentation for more information."
"info_text_3_link": "See the recorder documentation for more information."
},
"entity_no_longer_recorded": {
"title": "Entity is no longer recorded",
"info_text_1": "We have generated statistics for ''{name}'' ({statistic_id}) in the past, but state changes of this entity are no longer recorded, therefore, we cannot track long term statistics for it anymore.",
"info_text_2": "You probably excluded this entity, or have just included some entities.",
"info_text_3_link": "See the Recorder documentation for more information.",
"info_text_3_link": "See the recorder documentation for more information.",
"info_text_4": "If you no longer wish to keep the long term statistics recorded in the past, you may delete them now."
},
"state_class_removed": {
@@ -9744,7 +9759,7 @@
"remote_download_text": "You are accessing Home Assistant via remote access. Downloading backups over the Nabu Casa URL will take some time. If you are at home, cancel this dialog and enter your local URL, such as 'http://homeassistant.local:8123'",
"restore_start_failed": "Failed to start restore. Unknown error.",
"no_backup_found": "No backup found.",
"restore_no_home_assistant": "Backup does not contain Home Assistant data. To restore Home Assistant you need a backup of Home Assistant Core.",
"restore_no_home_assistant": "Backup does not contain Home Assistant data. To restore Home Assistant you need a backup of Home Assistant core.",
"unnamed_backup": "Unnamed backup"
},
"dialog": {

327
yarn.lock
View File

@@ -1644,7 +1644,7 @@ __metadata:
languageName: node
linkType: hard
"@floating-ui/dom@npm:^1.6.13":
"@floating-ui/dom@npm:^1.6.12, @floating-ui/dom@npm:^1.6.13":
version: 1.7.3
resolution: "@floating-ui/dom@npm:1.7.3"
dependencies:
@@ -1905,9 +1905,9 @@ __metadata:
languageName: node
linkType: hard
"@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3":
version: 3.0.0-beta.4.ha.3
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3"
"@home-assistant/webawesome@npm:3.0.0-beta.4.ha.2":
version: 3.0.0-beta.4.ha.2
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.2"
dependencies:
"@ctrl/tinycolor": "npm:^4.1.0"
"@floating-ui/dom": "npm:^1.6.13"
@@ -1919,7 +1919,7 @@ __metadata:
nanoid: "npm:^5.1.5"
qr-creator: "npm:^1.0.0"
style-observer: "npm:^0.0.7"
checksum: 10/b9241821ed471ccbad86b0ea4697a2d41395f05fdc26f46e5edbc7f6b5eeab5d248251ef702326312ded00d5bf850ce0dcdcf7cd5e2e542b9d9cb9a84f3726da
checksum: 10/0ac66d43050571e2b86bb7b0181d428aa2a064e25745075b207a8fe96d873398eaead663172130dfe8d9ac0be575028f8f9f6b9f8a9cd12f81c8c82e9f60a0e9
languageName: node
linkType: hard
@@ -2326,7 +2326,7 @@ __metadata:
languageName: node
linkType: hard
"@lit/react@npm:^1.0.8":
"@lit/react@npm:^1.0.6, @lit/react@npm:^1.0.8":
version: 1.0.8
resolution: "@lit/react@npm:1.0.8"
peerDependencies:
@@ -4000,92 +4000,92 @@ __metadata:
languageName: node
linkType: hard
"@rspack/binding-darwin-arm64@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-darwin-arm64@npm:1.5.3"
"@rspack/binding-darwin-arm64@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-darwin-arm64@npm:1.5.2"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@rspack/binding-darwin-x64@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-darwin-x64@npm:1.5.3"
"@rspack/binding-darwin-x64@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-darwin-x64@npm:1.5.2"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@rspack/binding-linux-arm64-gnu@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-linux-arm64-gnu@npm:1.5.3"
"@rspack/binding-linux-arm64-gnu@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-linux-arm64-gnu@npm:1.5.2"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@rspack/binding-linux-arm64-musl@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-linux-arm64-musl@npm:1.5.3"
"@rspack/binding-linux-arm64-musl@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-linux-arm64-musl@npm:1.5.2"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@rspack/binding-linux-x64-gnu@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-linux-x64-gnu@npm:1.5.3"
"@rspack/binding-linux-x64-gnu@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-linux-x64-gnu@npm:1.5.2"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@rspack/binding-linux-x64-musl@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-linux-x64-musl@npm:1.5.3"
"@rspack/binding-linux-x64-musl@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-linux-x64-musl@npm:1.5.2"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@rspack/binding-wasm32-wasi@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-wasm32-wasi@npm:1.5.3"
"@rspack/binding-wasm32-wasi@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-wasm32-wasi@npm:1.5.2"
dependencies:
"@napi-rs/wasm-runtime": "npm:^1.0.1"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@rspack/binding-win32-arm64-msvc@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-win32-arm64-msvc@npm:1.5.3"
"@rspack/binding-win32-arm64-msvc@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-win32-arm64-msvc@npm:1.5.2"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@rspack/binding-win32-ia32-msvc@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-win32-ia32-msvc@npm:1.5.3"
"@rspack/binding-win32-ia32-msvc@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-win32-ia32-msvc@npm:1.5.2"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@rspack/binding-win32-x64-msvc@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding-win32-x64-msvc@npm:1.5.3"
"@rspack/binding-win32-x64-msvc@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding-win32-x64-msvc@npm:1.5.2"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@rspack/binding@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/binding@npm:1.5.3"
"@rspack/binding@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/binding@npm:1.5.2"
dependencies:
"@rspack/binding-darwin-arm64": "npm:1.5.3"
"@rspack/binding-darwin-x64": "npm:1.5.3"
"@rspack/binding-linux-arm64-gnu": "npm:1.5.3"
"@rspack/binding-linux-arm64-musl": "npm:1.5.3"
"@rspack/binding-linux-x64-gnu": "npm:1.5.3"
"@rspack/binding-linux-x64-musl": "npm:1.5.3"
"@rspack/binding-wasm32-wasi": "npm:1.5.3"
"@rspack/binding-win32-arm64-msvc": "npm:1.5.3"
"@rspack/binding-win32-ia32-msvc": "npm:1.5.3"
"@rspack/binding-win32-x64-msvc": "npm:1.5.3"
"@rspack/binding-darwin-arm64": "npm:1.5.2"
"@rspack/binding-darwin-x64": "npm:1.5.2"
"@rspack/binding-linux-arm64-gnu": "npm:1.5.2"
"@rspack/binding-linux-arm64-musl": "npm:1.5.2"
"@rspack/binding-linux-x64-gnu": "npm:1.5.2"
"@rspack/binding-linux-x64-musl": "npm:1.5.2"
"@rspack/binding-wasm32-wasi": "npm:1.5.2"
"@rspack/binding-win32-arm64-msvc": "npm:1.5.2"
"@rspack/binding-win32-ia32-msvc": "npm:1.5.2"
"@rspack/binding-win32-x64-msvc": "npm:1.5.2"
dependenciesMeta:
"@rspack/binding-darwin-arm64":
optional: true
@@ -4107,23 +4107,23 @@ __metadata:
optional: true
"@rspack/binding-win32-x64-msvc":
optional: true
checksum: 10/900fbc0d611cdbd3040e7b4f1d3680b77ec92086f126d3840d303604a06425dc20597d5435a49cfd98b479039f39b604c31588e4843f4905ee762cb93d622a36
checksum: 10/71c41c6c878445ea561b7a02d9f75ec13ce170f5d63053debd72dee82a07d23c491a55526cfe9e0aceb5ee1154a07bbe69121deb2821d1a3ac5021eea75d9114
languageName: node
linkType: hard
"@rspack/core@npm:1.5.3":
version: 1.5.3
resolution: "@rspack/core@npm:1.5.3"
"@rspack/core@npm:1.5.2":
version: 1.5.2
resolution: "@rspack/core@npm:1.5.2"
dependencies:
"@module-federation/runtime-tools": "npm:0.18.0"
"@rspack/binding": "npm:1.5.3"
"@rspack/binding": "npm:1.5.2"
"@rspack/lite-tapable": "npm:1.0.1"
peerDependencies:
"@swc/helpers": ">=0.5.1"
peerDependenciesMeta:
"@swc/helpers":
optional: true
checksum: 10/1e8839b81c2f83951ac128ae11cfc62c366bb068fa7b64a96425e0236f30a64816b04ecb49a9cd5e5a1f0db5afeb00526eb7cfdfce30c51e545e3a5c4e7719d5
checksum: 10/e72023c8eea0ed351d950a28b6897ca7143ad749a65380ab855e12f96f8ce692ab044c14acf9b030bca740b722c197ad3075eaadac4fe480389e3131c519ac0e
languageName: node
linkType: hard
@@ -4170,6 +4170,22 @@ __metadata:
languageName: node
linkType: hard
"@shoelace-style/shoelace@npm:2.20.1":
version: 2.20.1
resolution: "@shoelace-style/shoelace@npm:2.20.1"
dependencies:
"@ctrl/tinycolor": "npm:^4.1.0"
"@floating-ui/dom": "npm:^1.6.12"
"@lit/react": "npm:^1.0.6"
"@shoelace-style/animations": "npm:^1.2.0"
"@shoelace-style/localize": "npm:^3.2.1"
composed-offset-position: "npm:^0.0.6"
lit: "npm:^3.2.1"
qr-creator: "npm:^1.0.0"
checksum: 10/c3aabeac03d5fd5bc43799783562ab09c92bae98efbc43a931c7dcec608acc393771b6ed0da3f29e08570bb9d9a9e3bff7637cbf6f79ba7aa439f6641da4eb7c
languageName: node
linkType: hard
"@sindresorhus/merge-streams@npm:^2.1.0":
version: 2.3.0
resolution: "@sindresorhus/merge-streams@npm:2.3.0"
@@ -4752,6 +4768,16 @@ __metadata:
languageName: node
linkType: hard
"@types/node-fetch@npm:^2.6.12":
version: 2.6.13
resolution: "@types/node-fetch@npm:2.6.13"
dependencies:
"@types/node": "npm:*"
form-data: "npm:^4.0.4"
checksum: 10/944d52214791ebba482ca1393a4f0d62b0dbac5f7343ff42c128b75d5356d8bcefd4df77771b55c1acd19d118e16e9bd5d2792819c51bc13402d1c87c0975435
languageName: node
linkType: hard
"@types/node-forge@npm:^1.3.0":
version: 1.3.14
resolution: "@types/node-forge@npm:1.3.14"
@@ -4948,106 +4974,106 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.43.0"
"@typescript-eslint/eslint-plugin@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.42.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.43.0"
"@typescript-eslint/type-utils": "npm:8.43.0"
"@typescript-eslint/utils": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.43.0"
"@typescript-eslint/scope-manager": "npm:8.42.0"
"@typescript-eslint/type-utils": "npm:8.42.0"
"@typescript-eslint/utils": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
"@typescript-eslint/parser": ^8.43.0
"@typescript-eslint/parser": ^8.42.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/0e9d31f6c7d69f152c8ff32ca501f03834b44945f4587419e26f821841dd1c2705db5648f1bef68985f8c8d7300ca63b9c6dee4e0e756f337f96f60372c7b1f7
checksum: 10/fb5b0e0785f9fa9d5ef88e78ff189334b2d1c558efd7b5063508d50275224a8aa38d4af0478228b90d6be6620289384a8d814f05e0af8c952c204515c0f3514e
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/parser@npm:8.43.0"
"@typescript-eslint/parser@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/parser@npm:8.42.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.43.0"
"@typescript-eslint/scope-manager": "npm:8.42.0"
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/cb3bd8bd48627cd502bb3cc5bb444e32c99d47ac41c092c457fcf0109f4a67491a42537abee51eee13498345f5dbd00dd11ccbf7a1d782a81d5ec9ee3e5df3ad
checksum: 10/25eb2d08c118742dc01c2aa279ea4ba2d277e2d9a042ffd4f9bda9e94d7ff2aa90b63aad1204a82617a5c63ddd3dd553d927944cd9c8345826484d0d523cf7ad
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/project-service@npm:8.43.0"
"@typescript-eslint/project-service@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/project-service@npm:8.42.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.43.0"
"@typescript-eslint/types": "npm:^8.43.0"
"@typescript-eslint/tsconfig-utils": "npm:^8.42.0"
"@typescript-eslint/types": "npm:^8.42.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/ab22f5d6b72dc4f46e7e0e01df549702b60c51941072a4a2a803f006134cad49687a4444f423db1d0d9e84c57f84dbc1458b5db6866b39a292412db96c756846
checksum: 10/3e91fd4b4d60edd6fe3e108e8e75947de8aa060aab1de63c23017e8afeca72ef405faa6fcdd17e8aa0023261a81135d095072dc31343c57395e50450258d9fa5
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/scope-manager@npm:8.43.0"
"@typescript-eslint/scope-manager@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/scope-manager@npm:8.42.0"
dependencies:
"@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.43.0"
checksum: 10/a975ae96bdc019510e1dedd672f1877e6389837774d221240d37196610b307dc59f845f33e23dfff9a96de6e2c3b75e5571a8acc145238408c1e06286efc9de2
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
checksum: 10/81be2d908a9d2d83bc9fe5e9219b04277b9fa466bfa7faf45dc076e4b33b39db2fb99b34b8832e329c7db48ddfdc7b78f6c92b564cd6eec99e124d3feaad8645
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.43.0, @typescript-eslint/tsconfig-utils@npm:^8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.43.0"
"@typescript-eslint/tsconfig-utils@npm:8.42.0, @typescript-eslint/tsconfig-utils@npm:^8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.42.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/20cb7b553eba44a8c4b4af2d0cabbcff248494b8c87243be7fcd1bb00846344f0bbc5b2353027d8e9053ee3e0c3b491cbf1c024f9f60b7e370220e7b0620b96f
checksum: 10/927aa127983a62ddcbfbcd18806fd278e0bf18fade3cca658946f9ff4915e6a5c5cc85926afaa490512c88dd2950b2059f22b50b6d1f4461c9dbd755a4c71c1c
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/type-utils@npm:8.43.0"
"@typescript-eslint/type-utils@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/type-utils@npm:8.42.0"
dependencies:
"@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.43.0"
"@typescript-eslint/utils": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0"
"@typescript-eslint/utils": "npm:8.42.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/b82184ba5079b95cc7775ddda3f40a994b0594375c0e5597d89db0e74e4e8d0e4b8a29fea646c6ed126af04729a7caa1052c0726e8f170a4106802486879a00b
checksum: 10/8d876bbd23c956b604d973c49720060c251f4d8cab255f1fd04826a9a1e3ab7c1310400d49d9ec6cdac3288d7a23cd9fb48d42777651ba53c02b5e1a34efd6e9
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.43.0, @typescript-eslint/types@npm:^8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/types@npm:8.43.0"
checksum: 10/f2c3b3f9cfb680dcf52b686b978176ea095dfb16db3c720149784f40a34c73c861fc57a707b64658bc0409d54ecd0e0d23d5bc41ba7d3b94db47772e2609062a
"@typescript-eslint/types@npm:8.42.0, @typescript-eslint/types@npm:^8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/types@npm:8.42.0"
checksum: 10/7c39a35e5bb7083070872edc797ea60a3d6ceff0e3bdf85701919b71da83a51963562053a4b35c9e2a2b08c138fb595e14bc0b5c450e671a26059b58f8d8b4f4
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/typescript-estree@npm:8.43.0"
"@typescript-eslint/typescript-estree@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/typescript-estree@npm:8.42.0"
dependencies:
"@typescript-eslint/project-service": "npm:8.43.0"
"@typescript-eslint/tsconfig-utils": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.43.0"
"@typescript-eslint/project-service": "npm:8.42.0"
"@typescript-eslint/tsconfig-utils": "npm:8.42.0"
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -5056,32 +5082,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/d2a054b6279107150e9c15569e18c861a89e504caa0a14716a2c73a09174814a993748ff637941757e3e9af033a7eeed511c8dcf17f25d3b3322245af35fd1d0
checksum: 10/9bb5df97a2ac31e6e3ee6941e10702498a76d23235ba28a23d93e09aa75a2cbcd40dc74935d86706c8e2e55e1a8b6a34bb9fb234461920ed3d8a5abed68ba36b
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/utils@npm:8.43.0"
"@typescript-eslint/utils@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/utils@npm:8.42.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.43.0"
"@typescript-eslint/scope-manager": "npm:8.42.0"
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/2c04182084bf3ba391198c723635ce50557ec73b1ebcc7970f0281c345db92aebdbbd1202e9bb3152b3c62a61b043907dde385bb44fce33841c52257c18b0064
checksum: 10/41c6c0d01c414c94d7109e21deee73b416547b3be26240d0237a3004c6198f146afefc75feee5333bc957ece6a0856518750655e794fd68c96feec1001edbfe8
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/visitor-keys@npm:8.43.0"
"@typescript-eslint/visitor-keys@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/visitor-keys@npm:8.42.0"
dependencies:
"@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.42.0"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10/d694425dd8592b9452640a82d638f4161ac880a8825f1cd6ce41b227bacff3a2e9106238344cbb85cb432593caf892bf4dcca0b73dcc884449ba88ee0ebec94a
checksum: 10/ef3aeabf7b01eb72e176053a4fe7a4c4f0769a9f58d1f7a920c97d365305b950c402ad34227209781996ae187652ccf0f47c31015f992c502b5fa898a9d44bd5
languageName: node
linkType: hard
@@ -9022,10 +9048,10 @@ __metadata:
languageName: node
linkType: hard
"globals@npm:16.4.0":
version: 16.4.0
resolution: "globals@npm:16.4.0"
checksum: 10/1627a9f42fb4c82d7af6a0c8b6cd616e00110908304d5f1ddcdf325998f3aed45a4b29d8a1e47870f328817805263e31e4f1673f00022b9c2b210552767921cf
"globals@npm:16.3.0":
version: 16.3.0
resolution: "globals@npm:16.3.0"
checksum: 10/accb0939d993a1c461df8d961ce9911a9a96120929e0a61057ae8e75b7df0a8bf8089da0f4e3a476db0211156416fbd26e222a56f74b389a140b34481c0a72b0
languageName: node
linkType: hard
@@ -9267,10 +9293,10 @@ __metadata:
languageName: node
linkType: hard
"hls.js@npm:1.6.12":
version: 1.6.12
resolution: "hls.js@npm:1.6.12"
checksum: 10/b0f23fcda44c6a4dc16dc501b3a17829417133079fefb7463a1e3d22ae9da24cb970e9e45165e4223b1e6b3a3d0f253ca680dcc9daf2e27d3d8153390ed3b9be
"hls.js@npm:1.6.11":
version: 1.6.11
resolution: "hls.js@npm:1.6.11"
checksum: 10/a7fb6407bd9729186fcdff14fb37d98d24ddfdce69e30165179a1af4c66fc83252bd85ee9199df91220ca5573fed7529b861d40fe86d93b4c06dc89ccc33382a
languageName: node
linkType: hard
@@ -9308,7 +9334,7 @@ __metadata:
"@fullcalendar/list": "npm:6.1.19"
"@fullcalendar/luxon3": "npm:6.1.19"
"@fullcalendar/timegrid": "npm:6.1.19"
"@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.3"
"@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.2"
"@lezer/highlight": "npm:1.2.1"
"@lit-labs/motion": "npm:1.0.9"
"@lit-labs/observers": "npm:2.0.6"
@@ -9346,8 +9372,9 @@ __metadata:
"@octokit/rest": "npm:22.0.0"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.2.3"
"@rspack/core": "npm:1.5.3"
"@rspack/core": "npm:1.5.2"
"@rspack/dev-server": "npm:1.1.4"
"@shoelace-style/shoelace": "npm:2.20.1"
"@swc/helpers": "npm:0.5.17"
"@thomasloven/round-slider": "npm:0.6.0"
"@tsparticles/engine": "npm:3.9.1"
@@ -9414,7 +9441,7 @@ __metadata:
gulp-json-transform: "npm:0.5.0"
gulp-rename: "npm:2.1.0"
gulp-zopfli-green: "npm:6.0.2"
hls.js: "npm:1.6.12"
hls.js: "npm:1.6.11"
home-assistant-js-websocket: "npm:9.5.0"
html-minifier-terser: "npm:7.2.0"
husky: "npm:9.1.7"
@@ -9456,8 +9483,8 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.2"
typescript-eslint: "npm:8.43.0"
ua-parser-js: "npm:2.0.5"
typescript-eslint: "npm:8.42.0"
ua-parser-js: "npm:2.0.4"
vite-tsconfig-paths: "npm:5.1.4"
vitest: "npm:3.2.4"
vue: "npm:2.7.16"
@@ -11500,7 +11527,7 @@ __metadata:
languageName: node
linkType: hard
"node-fetch@npm:^2.6.1":
"node-fetch@npm:^2.6.1, node-fetch@npm:^2.7.0":
version: 2.7.0
resolution: "node-fetch@npm:2.7.0"
dependencies:
@@ -14488,18 +14515,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.43.0":
version: 8.43.0
resolution: "typescript-eslint@npm:8.43.0"
"typescript-eslint@npm:8.42.0":
version: 8.42.0
resolution: "typescript-eslint@npm:8.42.0"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.43.0"
"@typescript-eslint/parser": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.43.0"
"@typescript-eslint/utils": "npm:8.43.0"
"@typescript-eslint/eslint-plugin": "npm:8.42.0"
"@typescript-eslint/parser": "npm:8.42.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0"
"@typescript-eslint/utils": "npm:8.42.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/51bd655f43aa6363932dee0d290fb26752875afdf6360a9940bc1c744b67ef82a1715392a65490ba4aa8a0490ad0ae0eb8903d831949f68af6a4e89e01a85b1c
checksum: 10/7f71501823b2c1e87e89ff00d6d8eb40c7514630dbb6b7b44c4dd830c95709357270763df2d711a8ea7bb0b58bd69534f15b01db4550dc6e745df8fec8f6a3ae
languageName: node
linkType: hard
@@ -14550,17 +14577,18 @@ __metadata:
languageName: node
linkType: hard
"ua-parser-js@npm:2.0.5":
version: 2.0.5
resolution: "ua-parser-js@npm:2.0.5"
"ua-parser-js@npm:2.0.4":
version: 2.0.4
resolution: "ua-parser-js@npm:2.0.4"
dependencies:
"@types/node-fetch": "npm:^2.6.12"
detect-europe-js: "npm:^0.1.2"
is-standalone-pwa: "npm:^0.1.1"
node-fetch: "npm:^2.7.0"
ua-is-frozen: "npm:^0.1.2"
undici: "npm:^7.12.0"
bin:
ua-parser-js: script/cli.js
checksum: 10/e946cb1c85bfcd0f2d30c7d5e1b605e340bb458432e7e87fc4aa1b2f90117e4220521d4e0bc7dd8c2a5cadd0935dedb5ac434b70efdc0007221288c1d98b3cd5
checksum: 10/eb3a57cd4aea6c42d2d766761ccf38cdc4576075646dec611efc336f0d1e640896ec4ca084142a1fedbf25c589e093e2cad50c49a22d089e234029ecb9b8d2e4
languageName: node
linkType: hard
@@ -14623,13 +14651,6 @@ __metadata:
languageName: node
linkType: hard
"undici@npm:^7.12.0":
version: 7.16.0
resolution: "undici@npm:7.16.0"
checksum: 10/2bb71672b23d3dc0f56f1b7fb6c936e4487a350db46eaafc03f2f9107f99cdf8e51ecdd32e589e2381ef47a64b6369cfb31f328b2c3ea663023aa47bc5258b9e
languageName: node
linkType: hard
"unicode-canonical-property-names-ecmascript@npm:^2.0.0":
version: 2.0.1
resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1"