Compare commits

...
22 Commits
Author SHA1 Message Date
Chanelle 4cb82bf40b Announce the number of search results in pickers (#55324)
* Announce the number of search results in pickers

Screen readers stay on the search field while the list filters, so typing in a picker or the quick search said nothing. A hidden status message now gives the number of matching items, or the not found label when nothing matches.

* Test the picker search results announcement

The status message is never seen, so a regression would go unnoticed. The test checks the count, the not found label, the cleared search, and that section titles are not counted.

* Count only matches and name the search in the picker announcement

The custom value row and the additional items, like the add new area row, were counted as results, so a search that matched nothing said there were results. They are no longer counted. A live region is only read out when its text changes, so a new search with the same count, or a second search that found nothing, said nothing. The message now names the search.

* Add the picker announcement messages to the gallery demo

The picker gallery's localizer only knew the older keys and returned the key for anything else, so searching a demo announced "ui.components.combo-box.result_count" instead of the count and the search. It now has both new messages, copied from en.json, and formats them with their values, so the demos announce "1 result for ‘desk’", "2 results for ‘kitchen’" and "No results for ‘zzz’".

* Move the search results message into a helper and test it there

The frontend testing guidance asks for no rendering tests, and the picker test mounted the component and checked its status text. The counting and the message now live in searchResultsStatus, a plain function the picker calls, with a unit test in its place: rows that are not results (section titles, placeholders, rows like "Add custom item") are not counted, a not found label that takes the search is used, and the message names the search so a new search is read out. The announcements are unchanged.

* Wait for typing to pause before announcing, and don't count Ask Assist

The picker announcement changed on every key press, so a screen reader started a new announcement for each character, over its own echo of the typed characters. The message now updates 500 ms after the rows stop changing, using the existing debounce helper, and is no longer recounted on every render.

The quick bar adds an "Ask Assist" row to every search through getItems, so it was counted: searching for something that matched nothing announced "1 result". The picker takes a new isSearchResult callback for rows like this, and the quick bar uses it to leave the assist row out. It isn't skipped in _isOption, because that would also stop the row being selectable. The gallery page documents both.

* Clear the picker announcement as soon as the rows change

With the pause, the last message stayed in the status region for half a second after the rows had changed, so it could describe the previous search. Going back to a search that had already been announced also wrote the same text again, so nothing was read out. The message is now cleared as soon as the rows change, at once when the search is emptied, and when the picker is removed. Clearing a status region isn't read out, and the next message is a change, so it is.
2026-10-30 12:58:47 +02:00
Daan Vervacke 449e7f8501 Wrap long title in YAML-only integration dialog (#55481) 2026-10-30 09:22:40 +01:00
Paul Bottein 7b0d350fb7 Add translation for the Marketplace panel title (#55486) 2026-10-30 08:19:14 +01:00
Maarten Lakerveld ed2cc613e1 Move the panel background stacking context off partial-panel-resolver (#55485)
Since #54801 the resolver creates a stacking context (isolation: isolate) so
its fixed ::before background sits behind the panel. The resolver is rendered
conditionally, once the startup data is available, and isolating it can
intermittently leave the Android WebView accessibility tree (Chromium
109-124) with an empty panel subtree while the page paints normally. UI
automation then never sees the dashboard; the companion app nightly E2E has
hit this on the legacy build (API 32/33/35) since the change landed.

Move the isolation to home-assistant-main's host, which exists before the
resolver is rendered. The pseudo-element and the Lovelace view background
both stay at z-index -1 in that context, in the same order as before, so the
themed background behaves exactly as #54801 intended. Measured with Maestro
on a Chrome 109 emulator: 0 stale trees in 62 cold loads, against 11 in 100
with the isolation on the resolver.
2026-10-30 08:18:36 +02:00
renovate[bot] 79f193f0d8 Update pnpm to v12.16.0 (#55490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-30 08:14:44 +02:00
renovate[bot] 149f24255b Update dependency typescript-eslint to v8.72.1 (#55483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-30 08:14:37 +02:00
renovate[bot] c1a82bfe7f Update dependency browserslist to v4.29.5 (#55482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-30 08:14:29 +02:00
renovate[bot] 5083578c1e Update Node.js to v24.22.2 (#55489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-30 08:14:21 +02:00
Paul Bottein 5f11dc4580 Replace hide-zoom-controls with a zoom slider mode on charts (#55468) 2026-10-29 17:12:40 +01:00
Simon Lamon 27489f188e date-range-picker migrate list (#55471) 2026-10-29 17:57:24 +02:00
Simon Lamon 97fffc3b43 ha-config-person migrate list (#55473) 2026-10-29 17:56:23 +02:00
renovate[bot] a30193b8ef Update dependency marked to v18.1.2 (#55470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-29 17:50:42 +02:00
Aidan Timson cf063dc19f Update the PR template for the contributors docs (#55459) 2026-10-29 17:05:36 +02:00
Simon Lamon 5db829b7be pick-auth-provider migrate list (#55462) 2026-10-29 17:04:51 +02:00
Maarten Lakerveld 421f8b1986 Load the legacy Intl polyfills once per page and bundle PluralRules with its data (#55452)
Each legacy entry bundle carries its own copy of intl-polyfill.ts, so the
module-level guard only stopped re-runs within one bundle. With the bundles
loading in parallel, the second bundle could probe Intl.PluralRules after the
first had installed the polyfill but before its locale data chunk arrived. The
FormatJS should-polyfill probe calls select(), which throws on a data-less
polyfill, rejecting the top-level await so the entry never evaluated. On the
companion app this surfaces as ExternalBusTimeout on old WebViews.

Share one promise per page on globalThis so every bundle waits for the same
run, and import the PluralRules polyfill and its English data from one module
so Intl.PluralRules is never installed without data.
2026-10-29 17:02:23 +02:00
Simon Lamon a140eedc6e Clean up ha-radio-list-item (#55454) 2026-10-29 17:00:59 +02:00
renovate[bot] 2933568658 Update dependency home-assistant-js-websocket to v9.8.1 (#55450)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-29 16:59:26 +02:00
Simon Lamon 29faeaf7fa Remove incorrect type usage from date-range-picker (#55460) 2026-10-29 16:58:55 +02:00
Simon Lamon 95ba532ceb Removes incorrect type from cast (#55461) 2026-10-29 16:58:33 +02:00
Guillaume Rischard 352ecb9d6d Simplify OpenStreetMap attribution (#55447)
As per https://osmfoundation.org/wiki/Licence/Attribution_Guidelines:

> Attribution must be to “OpenStreetMap”.
[...]
> The historical forms of attribution “© OpenStreetMap contributors” or “© OpenStreetMap” are acceptable.
2026-10-29 15:27:49 +01:00
Erwin Douna 3f73049a3d Fade out energy period loading spinner (#55431) 2026-10-29 15:06:53 +01:00
Petar Petrov 50ad0c6cd0 Name new disk mounts after the partition's volume label (#55441) 2026-10-29 14:58:56 +01:00
28 changed files with 579 additions and 337 deletions

No files matched your search

+5 -3
View File
@@ -51,8 +51,8 @@
- This PR fixes or closes issue: fixes #
- This PR is related to issue or discussion:
- Link to documentation pull request:
- Link to developer documentation pull request:
- Link to [documentation][docs-repository] pull request:
- Link to [contributors documentation][contributors-repository] pull request:
- Link to backend pull request:
## Checklist
@@ -63,7 +63,8 @@
for before merging your code.
AI tools are welcome, but contributors are responsible for *fully*
understanding the code before submitting a PR.
understanding the code before submitting a PR. Please follow our AI policy:
https://contributors.home-assistant.io/start/ai-policy/
-->
- [ ] I understand the code I am submitting and can explain how it works.
@@ -105,4 +106,5 @@ To help with the load of incoming pull requests:
Below, some useful links you could explore:
-->
[docs-repository]: https://github.com/home-assistant/home-assistant.io
[contributors-repository]: https://github.com/home-assistant/contributors.home-assistant.io
[perfect-pr]: https://contributors.home-assistant.io/start/pull-requests/#before-you-open-it
+1 -1
View File
@@ -1 +1 @@
24.22.1
24.22.2
+1 -2
View File
@@ -1,4 +1,3 @@
import type { ActionDetail } from "@material/mwc-list/mwc-list";
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
import type { Auth, Connection } from "home-assistant-js-websocket";
import type { TemplateResult, PropertyValues } from "lit";
@@ -215,7 +214,7 @@ class HcCast extends LitElement {
this.castManager.requestSession();
}
private async _handlePickView(ev: CustomEvent<ActionDetail>) {
private async _handlePickView(ev: CustomEvent<{ index: number }>) {
const path = this.lovelaceViews?.[ev.detail.index]?.path ?? ev.detail.index;
await ensureConnectedCastSession(this.castManager!, this.auth!);
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
@@ -50,6 +50,18 @@ With no cursor yet, <kbd>↓</kbd> and <kbd>Enter</kbd> start from the `value`
row, or from the top match while searching. With one item in the list,
<kbd>Enter</kbd> picks it whether or not the cursor has moved.
## Screen readers
DOM focus stays in the search field, so screen readers do not hear the list
change. While searching, a visually hidden `role="status"` message says how
many items match the search and names it, so a new search with the same count
is still read out. It is cleared as soon as the list changes and filled in once
typing pauses for half a second, so it doesn't talk over each typed character
or describe the previous search. When nothing matches, it reads a not
found label that takes the search, or says there are no results for it. Section
titles, placeholder rows, "Add custom item", the additional items and rows that
`isSearchResult` rejects are not counted.
## Implementation
Without `sections`, the component filters the items from `getItems` itself as the
@@ -79,6 +91,7 @@ means supplying one.
| allowCustomValue | Boolean | false | Offers the typed string as an item. |
| customValueLabel | String | - | Label for that custom item. |
| notFoundLabel | String/Function | - | Shown when a search matches nothing. |
| isSearchResult | Function | - | Whether a row from `getItems` counts as a match for screen readers. The quick bar leaves out its "Ask Assist" row. |
| emptyLabel | String | - | Shown when there are no items at all. |
| noSort | Boolean | false | Keeps the order `getItems` returned. |
| clearable | Boolean | false | Adjusts search field padding for a clear affordance. |
@@ -1,7 +1,9 @@
import { ContextProvider } from "@lit/context";
import { IntlMessageFormat } from "intl-messageformat";
import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import type { LocalizeFunc } from "../../../../src/common/translations/localize";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-picker-combo-box";
import type { PickerComboBoxItem } from "../../../../src/components/ha-picker-combo-box";
@@ -19,13 +21,26 @@ import type {
} from "../../../../src/types";
import { THEME_COMPARISON_PANELS } from "../../components/demo-theme-comparison";
// The English strings, plurals included, so searching a demo announces the
// same count and search as the real picker.
const LOCALIZE_KEYS: Record<string, string> = {
"ui.common.search": "Search",
"ui.components.combo-box.no_match": "No matching items found",
"ui.components.combo-box.no_items": "No items available",
"ui.components.combo-box.result_count":
"{count} {count, plural,\n one {result}\n other {results}\n} for {term}",
"ui.components.combo-box.no_results": "No results for {term}",
};
const localize = (key: string) => LOCALIZE_KEYS[key] ?? key;
const localize: LocalizeFunc = (key, values) => {
const message = LOCALIZE_KEYS[key];
if (message === undefined) {
return key;
}
return new IntlMessageFormat(message, "en").format<string>(
values as Record<string, string | number>
) as string;
};
const DEMO_I18N: HomeAssistantInternationalization = {
localize,
+6 -6
View File
@@ -101,7 +101,7 @@
"fullcalendar": "7.1.3",
"fuse.js": "7.5.0",
"hls.js": "1.7.3",
"home-assistant-js-websocket": "9.8.0",
"home-assistant-js-websocket": "9.8.1",
"idb-keyval": "6.3.0",
"intl-messageformat": "12.1.4",
"js-yaml": "5.5.0",
@@ -110,7 +110,7 @@
"lit": "3.3.3",
"lit-html": "3.3.3",
"maplibre-gl": "6.16.0",
"marked": "18.1.1",
"marked": "18.1.2",
"memoize-one": "6.0.0",
"nanoid": "6.0.2",
"node-vibrant": "4.0.4",
@@ -173,7 +173,7 @@
"@vitest/coverage-v8": "5.0.5",
"babel-loader": "10.1.1",
"babel-plugin-polyfill-corejs3": "1.0.0",
"browserslist": "4.29.4",
"browserslist": "4.29.5",
"browserslist-useragent-regexp": "4.1.4",
"core-js-compat": "3.51.0",
"del": "9.0.1",
@@ -214,7 +214,7 @@
"terser-webpack-plugin": "5.6.1",
"ts-lit-plugin": "2.0.2",
"typescript": "6.0.3",
"typescript-eslint": "8.72.0",
"typescript-eslint": "8.72.1",
"vite": "8.3.5",
"vite-tsconfig-paths": "6.1.1",
"vitest": "5.0.5",
@@ -222,8 +222,8 @@
"webpackbar": "7.0.0",
"workbox-build": "7.4.1"
},
"packageManager": "[email protected]5.0",
"packageManager": "[email protected]6.0",
"volta": {
"node": "24.22.1"
"node": "24.22.2"
}
}
+173 -173
View File
@@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.15.0
version: 12.15.0
specifier: 12.16.0
version: 12.16.0
packages:
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-eVCqmscY70LGvgRzu3mi8IBWBFGix+a6mNZoWmXxESsBdeiKEF9xQSuaeel10yn+zI7TXy2jlI63vFLzETk+aQ==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-Air4kk1RA4C8PGkkIH91eme/ERTV6xEmPfJS+OO1UMMzx7ZDeM44WGfcizGPnQbWiqfJ0l7TSttzYzQ0FvRLIA==}
cpu: [arm64]
os: [android]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-Gx/WbkFcdUFUg8g8AShezcS9EiQxfYvdAm2hfKLO8sfJmJvKOCt5fzlgHWDzmGhoclibgW+tUGZ/KF4F4wOTCA==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-MHQdX7J3b12gsBbKSV4phbGZaIAYxhIbXRS2E56XgVXpcL9R+HiQ/Aq9PDkJtNVaOaGfrFR9kzGMLF4PXk6jRw==}
cpu: [x64]
os: [android]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-N0ZykiDWCvqSBJvt71j7cSoBR993FqvYZFtmpCtqvW3pguiI+NAgraLPA5OTwu5mV+/d8hYpVcPktJA3xFDLtA==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-0kMENoYlVTxiCPWf7fXEF3EtYXUySilIC+UAe5Z7kC8keY8+ul8ddhuHjSU0e85qlKDIJfIxDYACse/Lx8It/Q==}
cpu: [arm64]
os: [darwin]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-7Ntr2HlBUxzwWOg7NwDxCbRW/3vXuqKO+hcXm+w3SBi9rrHY53li1wLqWVT7Gjxd+lVEHM1QJZo6WJEGFU8C6A==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-jsS6Q5gTjRD4TOyyFazO/3ctRHfigGtLatfV2ZxCGd0/ZD/m/9RIT8P38oyo+r3ARHhgL7YPT8dXmj5UGd9iSA==}
cpu: [x64]
os: [darwin]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-btOiyL7fL7W4uQGH0aqFai2TNC3IoKcZ7gNHBVbEFZt2dWrnrD0SufL3iGs3fLlmb4OtuU35uf4Zpr9V9Jh9mA==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-Rs8pV7jAhRnAzKdJWU20ECeRB4KUdV5xChUvmXyaHq+60+UfUhYVJjL0btApxX/AIBscrK/+tjP3w6RTb9RDCA==}
cpu: [x64]
os: [freebsd]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-pNC38k+O+90FOjbPNSnw0E7apq1PKTmz3fFe1amn+lys/U5kL++uzir8bRiho8ONcyA3d9EU7wdqcY0DKkj+JA==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-1p8s61byKqiaDi+TatmMoxFVFPXpLwrHBI4CZBJlH2A58hi3EfqfDgWB5w2IutciGrBQ5DxVFE8VsbITnpqfUQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-qML/8aCXslh8td/w4U+HJNqUcZ2+m6EfTbWScIzZRmyPsRFN+xB4LP1U/hymxc1iSJiKtG56HJx2fvUErWFIGw==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-6vwpzcLhopnnz/x537ET62PBFh8QxWsE9miRT8lhHXYuq0WVXxT6L6W0BcjZNVEQyp1WggdHq4qt8LXrdk2RFQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-tjRDUUl7RCJ6T/6eoNPxOqkwU4Mzi5RtTXmSAxujyz37OTwwsMU7Ps087Zk4gHcUvRSeyF40ZFsew3vtE+nmUA==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-58n7z+x7M/gWK+lhoRrABp0X2dYvUGhdjKb1S0UCm1I7v1KwltEvAIlWgYSyq9/jOqTp/uCtZE9dU1sP/fqs3Q==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-bF1uBWEfbwaeryz6lIiaJ8AWD082otZfvzGX5l+6LZRpRwUEpJHUsJrORaYP5/cNMv5ZR2p6TvvcJcQzwtDzmw==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-N81hKRql41PisaxO0EJcmBVvGjQ+aY8vgprPhB6DFaCzohuT0EAfsc5icM/NZkdFLLaglYja8zAk3E59ToNUaA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-zKMhIzsexYMToeLmqV5jPsvV/f9vwsJb2kUH2NFYzalbxImeBrlA4kL7GwF8gD4pagV6jYYcJb8RjbZIdjHptg==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-8UfyGGA9o8IqdoPtCg9c/t7Ht/ArO+T318Yomlp36WvImnBj3etnO0GKh0CPOp7fG+AvNcspxCXIr5vNjYo84w==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-A9J0Jg0Rdx+qQU4qXtwyST5tto9IOu1zi4fDk35nbc6DWahggbcKtFvZPKFwL2qgUXGXOX//hXv6z/sp6emO8A==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-BCp9UPPqS9wyZmfB1CeX8ODpUaY3icHPNQvnlutDUymexM0frPxgd1Qf1iWfC+IhvszevGnVlkUqEITSMowHNw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-yc+QlZR+9+nr29J4fRzhMoapgxTBDndq/+peyoq1XUufnQFnfbXUMFudDIKzHi545linGqmOmXfn0NoufNgcOg==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-NSZrQRBatoGG4C14DBYyQHnUGQA1fejrwoiKRnqyzFFXPFbydK8avYcLu3evb9Nry8iGIQujRnwysdCNmSFBlg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-fPXZxuY1VclkOga9bc53qNCXcegfeoBR8+Ue+AospDwKzuGGSHll0oDxkLfkmpjyRqpe7bH5DyQPYp/xSMaIVA==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-RsPE7hktWUn+oCi5IlnpMGBBMO+Lgf+VUUgiqrBHsdmdpTk3KDOM9TA0xOgK+xwvQdLtvc28JwigR4HmGMNyIQ==}
cpu: [arm64]
os: [win32]
'@pnpm/[email protected]5.0':
resolution: {integrity: sha512-nrd+Tp1We1DGj4DGG5Bea9lZNh9y1PjlLBdEwdInS4D0h0UIDtM7JaGGGDJhNzIwcHASyihWI7HLV+dtG+fhYg==}
'@pnpm/[email protected]6.0':
resolution: {integrity: sha512-mphDCqBvSu7o8j3DqnVDkpCf0HKaKJjEdlN6ymfpxh4VUYw5SW4zQLGmvjdxUUZZb6nuU/R8bTn2rAdJdYCk5w==}
cpu: [x64]
os: [win32]
[email protected]5.0:
resolution: {integrity: sha512-s5UdbW/0qfE9x89WzSjSuOs328ycaOUuHhGRhz6p+CoBcoerULqtxczElPA2xnwOR+sTYIwvo29HcRth6hKOYQ==}
[email protected]6.0:
resolution: {integrity: sha512-6czJGguNUtopfBaEH57gXz3dYxvXV+8mohFofUo43mU8YURTZJKUQ4xYvnTCumLVPmM6ThU3CMASKW0PPuJ7Ng==}
engines: {node: '>=18'}
hasBin: true
snapshots:
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
'@pnpm/[email protected]5.0':
'@pnpm/[email protected]6.0':
optional: true
[email protected]5.0:
[email protected]6.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.15.0
'@pnpm/exe.android-x64': 12.15.0
'@pnpm/exe.darwin-arm64': 12.15.0
'@pnpm/exe.darwin-x64': 12.15.0
'@pnpm/exe.freebsd-x64': 12.15.0
'@pnpm/exe.linux-arm64': 12.15.0
'@pnpm/exe.linux-arm64-musl': 12.15.0
'@pnpm/exe.linux-ppc64': 12.15.0
'@pnpm/exe.linux-riscv64': 12.15.0
'@pnpm/exe.linux-s390x': 12.15.0
'@pnpm/exe.linux-x64': 12.15.0
'@pnpm/exe.linux-x64-musl': 12.15.0
'@pnpm/exe.win32-arm64': 12.15.0
'@pnpm/exe.win32-x64': 12.15.0
'@pnpm/exe.android-arm64': 12.16.0
'@pnpm/exe.android-x64': 12.16.0
'@pnpm/exe.darwin-arm64': 12.16.0
'@pnpm/exe.darwin-x64': 12.16.0
'@pnpm/exe.freebsd-x64': 12.16.0
'@pnpm/exe.linux-arm64': 12.16.0
'@pnpm/exe.linux-arm64-musl': 12.16.0
'@pnpm/exe.linux-ppc64': 12.16.0
'@pnpm/exe.linux-riscv64': 12.16.0
'@pnpm/exe.linux-s390x': 12.16.0
'@pnpm/exe.linux-x64': 12.16.0
'@pnpm/exe.linux-x64-musl': 12.16.0
'@pnpm/exe.win32-arm64': 12.16.0
'@pnpm/exe.win32-x64': 12.16.0
---
lockfileVersion: '9.0'
@@ -364,8 +364,8 @@ importers:
specifier: 1.7.3
version: 1.7.3
home-assistant-js-websocket:
specifier: 9.8.0
version: 9.8.0
specifier: 9.8.1
version: 9.8.1
idb-keyval:
specifier: 6.3.0
version: 6.3.0
@@ -391,8 +391,8 @@ importers:
specifier: 6.16.0
version: 6.16.0
marked:
specifier: 18.1.1
version: 18.1.1
specifier: 18.1.2
version: 18.1.2
memoize-one:
specifier: 6.0.0
version: 6.0.0
@@ -575,11 +575,11 @@ importers:
specifier: 1.0.0
version: 1.0.0(@babel/[email protected])
browserslist:
specifier: 4.29.4
version: 4.29.4
specifier: 4.29.5
version: 4.29.5
browserslist-useragent-regexp:
specifier: 4.1.4
version: 4.1.4([email protected].4)
version: 4.1.4([email protected].5)
core-js-compat:
specifier: 3.51.0
version: 3.51.0
@@ -597,7 +597,7 @@ importers:
version: 0.13.11([email protected])
eslint-plugin-import-x:
specifier: 4.17.1
version: 4.17.1(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])
version: 4.17.1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])
eslint-plugin-lit:
specifier: 2.3.1
version: 2.3.1([email protected]([email protected]))
@@ -606,7 +606,7 @@ importers:
version: 5.1.3([email protected]([email protected]))
eslint-plugin-unused-imports:
specifier: 4.4.1
version: 4.4.1(@typescript-eslint/[email protected].0(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))
version: 4.4.1(@typescript-eslint/[email protected].1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))
eslint-plugin-wc:
specifier: 3.1.0
version: 3.1.0([email protected]([email protected]))
@@ -698,8 +698,8 @@ importers:
specifier: 6.0.3
version: 6.0.3
typescript-eslint:
specifier: 8.72.0
version: 8.72.0([email protected]([email protected]))([email protected])([email protected])
specifier: 8.72.1
version: 8.72.1([email protected]([email protected]))([email protected])([email protected])
vite:
specifier: 8.3.5
version: 8.3.5(@types/[email protected])([email protected])([email protected])
@@ -3372,63 +3372,63 @@ packages:
'@types/[email protected]':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.72.0
'@typescript-eslint/parser': ^8.72.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/[email protected].0':
resolution: {integrity: sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==}
'@typescript-eslint/[email protected].1':
resolution: {integrity: sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript/[email protected]':
@@ -4046,8 +4046,8 @@ packages:
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
engines: {node: ^4.5.0 || >= 5.9}
[email protected].29:
resolution: {integrity: sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==}
[email protected].33:
resolution: {integrity: sha512-yiVKDX+qCMRKBE084lXvq9EcwFUhKZ2nU8TK1EQQuOgPNR/lmbGxXOeDdlGUqC2Zz11Cs9rk1d/8NfgBmlkt0w==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -4113,8 +4113,8 @@ packages:
peerDependencies:
browserslist: '>=4.0.0'
[email protected].4:
resolution: {integrity: sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==}
[email protected].5:
resolution: {integrity: sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@@ -4172,8 +4172,8 @@ packages:
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
engines: {node: '>=14.16'}
[email protected]6:
resolution: {integrity: sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==}
[email protected]9:
resolution: {integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==}
[email protected]:
resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==}
@@ -4605,8 +4605,8 @@ packages:
engines: {node: '>=0.10.0'}
hasBin: true
[email protected]59:
resolution: {integrity: sha512-4SC9HHSFjO/liAhifpdOL5hG9b4BP7/5G3y3UPZVO8trlAXr1CaUEEJL0gWK3kOcYcZrOA5rkwG45Gra/lTIgg==}
[email protected]64:
resolution: {integrity: sha512-nRa65aFge40viPAKxMKULIY8tcXKwKboJWNn4RoKwyWXJ6SP7BnrDGKUTzc64818jIciH0PK7LSF8QzZes19GA==}
[email protected]:
resolution: {integrity: sha512-uB0/Qube3lkwh8SmkTnGIyUgJ9YdqVSzIoHMRCEQjAbD4Y5UzsVbch1tIxjTgUe5k3gy1U0ZMKMJ90A81lqwig==}
@@ -5205,8 +5205,8 @@ packages:
[email protected]:
resolution: {integrity: sha512-MsPlx6yVW4Qv4C7mEVou4/gk/5cN2dVxOTnrNjPSmyH2f0Ln+/UkG10Ax1PE6OvrmTVPKgQYozTtBaPOuCzq7A==}
[email protected].0:
resolution: {integrity: sha512-Nl5asoKQJwJ5a8N0U72jkSAUdO+hsY/T+Upp0VaZMUSt6A2gtdyvKfBIEYbpnYK3NP83Q/YGaC5XYkO5c08Img==}
[email protected].1:
resolution: {integrity: sha512-WvovbrSoYLTKfh6aY5SxSWPPy50/mLWLCihQL8G2N+2e3mTrg3QTQCAp4R32/LwbkRBDGj0XNs6JFj8e2IYEUw==}
[email protected]:
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
@@ -5907,8 +5907,8 @@ packages:
engines: {node: '>= 18'}
hasBin: true
[email protected].1:
resolution: {integrity: sha512-zEGgrtUG8crk+TrMacJSVABU4DYDDkS8uqgU3ohrn/2fW9C2AEfUrO3m6IhpIpQpl7YcVpiXXC9cSMWDANT3dg==}
[email protected].2:
resolution: {integrity: sha512-DfOnvLAQ6WYeWxWO1u4RyFOeqyHup6dks5PPqnoFiooU7ipRvidO/2j8plhAHIiZg574E9ZlzSjUiPMLAzFkgg==}
engines: {node: '>= 20'}
hasBin: true
@@ -6105,8 +6105,8 @@ packages:
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
hasBin: true
[email protected].59:
resolution: {integrity: sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA==}
[email protected].60:
resolution: {integrity: sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==}
engines: {node: '>=18'}
[email protected]:
@@ -7294,8 +7294,8 @@ packages:
resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==}
engines: {node: '>= 0.4'}
[email protected].0:
resolution: {integrity: sha512-r26Fkc8As+mZvqp7SLsMmNFyYUup/6CjK7/eHBHmhOw6GH6n6YFxHVFrG0jN1c5CMpDzelLPxvgeCOzx7BjN+A==}
[email protected].1:
resolution: {integrity: sha512-3Li9FfPmQp45nxEtFD1ZCrYFI3cYK5kpyNtmm1H7lMagm8u9sy6PTjy3ytwQZs7KNukOyKFHAZ6z6GjYFeaeXw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -7963,7 +7963,7 @@ snapshots:
dependencies:
'@babel/compat-data': 7.29.7
'@babel/helper-validator-option': 7.29.7
browserslist: 4.29.4
browserslist: 4.29.5
lru-cache: 5.1.1
semver: 6.3.1
@@ -7971,7 +7971,7 @@ snapshots:
dependencies:
'@babel/compat-data': 8.0.5
'@babel/helper-validator-option': 8.0.0
browserslist: 4.29.4
browserslist: 4.29.5
flru: 1.0.2
verkit: 0.5.0
@@ -11058,14 +11058,14 @@ snapshots:
dependencies:
'@types/node': 24.19.2
'@typescript-eslint/[email protected].0(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])':
'@typescript-eslint/[email protected].1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.72.0([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/scope-manager': 8.72.0
'@typescript-eslint/type-utils': 8.72.0([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/utils': 8.72.0([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/visitor-keys': 8.72.0
'@typescript-eslint/parser': 8.72.1([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/scope-manager': 8.72.1
'@typescript-eslint/type-utils': 8.72.1([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/utils': 8.72.1([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/visitor-keys': 8.72.1
eslint: 10.13.0([email protected])
ignore: 7.0.6
natural-compare: 1.4.0
@@ -11074,41 +11074,41 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected])':
'@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@typescript-eslint/scope-manager': 8.72.0
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/typescript-estree': 8.72.0([email protected])([email protected])
'@typescript-eslint/visitor-keys': 8.72.0
'@typescript-eslint/scope-manager': 8.72.1
'@typescript-eslint/types': 8.72.1
'@typescript-eslint/typescript-estree': 8.72.1([email protected])([email protected])
'@typescript-eslint/visitor-keys': 8.72.1
debug: 4.4.3([email protected])
eslint: 10.13.0([email protected])
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected].0([email protected])([email protected])':
'@typescript-eslint/[email protected].1([email protected])([email protected])':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.72.0([email protected])
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/tsconfig-utils': 8.72.1([email protected])
'@typescript-eslint/types': 8.72.1
debug: 4.4.3([email protected])
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected].0':
'@typescript-eslint/[email protected].1':
dependencies:
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/visitor-keys': 8.72.0
'@typescript-eslint/types': 8.72.1
'@typescript-eslint/visitor-keys': 8.72.1
'@typescript-eslint/[email protected].0([email protected])':
'@typescript-eslint/[email protected].1([email protected])':
dependencies:
typescript: 6.0.3
'@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected])':
'@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/typescript-estree': 8.72.0([email protected])([email protected])
'@typescript-eslint/utils': 8.72.0([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/types': 8.72.1
'@typescript-eslint/typescript-estree': 8.72.1([email protected])([email protected])
'@typescript-eslint/utils': 8.72.1([email protected]([email protected]))([email protected])([email protected])
debug: 4.4.3([email protected])
eslint: 10.13.0([email protected])
ts-api-utils: 2.5.0([email protected])
@@ -11116,14 +11116,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected].0': {}
'@typescript-eslint/[email protected].1': {}
'@typescript-eslint/[email protected].0([email protected])([email protected])':
'@typescript-eslint/[email protected].1([email protected])([email protected])':
dependencies:
'@typescript-eslint/project-service': 8.72.0([email protected])([email protected])
'@typescript-eslint/tsconfig-utils': 8.72.0([email protected])
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/visitor-keys': 8.72.0
'@typescript-eslint/project-service': 8.72.1([email protected])([email protected])
'@typescript-eslint/tsconfig-utils': 8.72.1([email protected])
'@typescript-eslint/types': 8.72.1
'@typescript-eslint/visitor-keys': 8.72.1
debug: 4.4.3([email protected])
minimatch: 10.2.6
semver: 7.8.5
@@ -11133,20 +11133,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected])':
'@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@eslint-community/eslint-utils': 4.10.1([email protected]([email protected]))
'@typescript-eslint/scope-manager': 8.72.0
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/typescript-estree': 8.72.0([email protected])([email protected])
'@typescript-eslint/scope-manager': 8.72.1
'@typescript-eslint/types': 8.72.1
'@typescript-eslint/typescript-estree': 8.72.1([email protected])([email protected])
eslint: 10.13.0([email protected])
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected].0':
'@typescript-eslint/[email protected].1':
dependencies:
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/types': 8.72.1
eslint-visitor-keys: 5.0.1
'@typescript/[email protected]':
@@ -11620,7 +11620,7 @@ snapshots:
[email protected]: {}
[email protected].29: {}
[email protected].33: {}
[email protected]: {}
@@ -11694,24 +11694,24 @@ snapshots:
[email protected]:
dependencies:
browserslist: 4.29.4
browserslist: 4.29.5
[email protected]([email protected].4):
[email protected]([email protected].5):
dependencies:
argue-cli: 2.1.0
browserslist: 4.29.4
browserslist: 4.29.5
easy-table: 1.2.0
picocolors: 1.1.1
regexp-tree: 0.1.27
ua-regexes-lite: 1.2.1
[email protected].4:
[email protected].5:
dependencies:
baseline-browser-mapping: 2.11.29
caniuse-lite: 1.0.30001816
electron-to-chromium: 1.5.459
node-releases: 2.0.59
update-browserslist-db: 1.3.4([email protected].4)
baseline-browser-mapping: 2.11.33
caniuse-lite: 1.0.30001819
electron-to-chromium: 1.5.464
node-releases: 2.0.60
update-browserslist-db: 1.3.4([email protected].5)
[email protected]: {}
@@ -11793,7 +11793,7 @@ snapshots:
[email protected]: {}
[email protected]6: {}
[email protected]9: {}
[email protected]: {}
@@ -11961,7 +11961,7 @@ snapshots:
[email protected]:
dependencies:
browserslist: 4.29.4
browserslist: 4.29.5
[email protected]: {}
@@ -12206,7 +12206,7 @@ snapshots:
dependencies:
jake: 10.9.4
[email protected]59: {}
[email protected]64: {}
[email protected]: {}
@@ -12401,9 +12401,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected](@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected]):
[email protected](@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected]):
dependencies:
'@typescript-eslint/types': 8.72.0
'@typescript-eslint/types': 8.72.1
comment-parser: 1.4.8
debug: 4.4.3([email protected])
eslint: 10.13.0([email protected])
@@ -12414,7 +12414,7 @@ snapshots:
stable-hash-x: 0.2.0
unrs-resolver: 1.12.2
optionalDependencies:
'@typescript-eslint/utils': 8.72.0([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/utils': 8.72.1([email protected]([email protected]))([email protected])([email protected])
transitivePeerDependencies:
- supports-color
@@ -12438,11 +12438,11 @@ snapshots:
parse5: 8.0.1
parse5-htmlparser2-tree-adapter: 8.0.1
[email protected](@typescript-eslint/[email protected].0(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])):
[email protected](@typescript-eslint/[email protected].1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])):
dependencies:
eslint: 10.13.0([email protected])
optionalDependencies:
'@typescript-eslint/eslint-plugin': 8.72.0(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/eslint-plugin': 8.72.1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
[email protected]([email protected]([email protected])):
dependencies:
@@ -12962,7 +12962,7 @@ snapshots:
[email protected]: {}
[email protected].0: {}
[email protected].1: {}
[email protected]:
dependencies:
@@ -13689,7 +13689,7 @@ snapshots:
[email protected]: {}
[email protected].1: {}
[email protected].2: {}
[email protected]: {}
@@ -13868,7 +13868,7 @@ snapshots:
undici: 8.12.1
which: 7.0.0
[email protected].59: {}
[email protected].60: {}
[email protected]:
dependencies:
@@ -15210,12 +15210,12 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
[email protected].0([email protected]([email protected]))([email protected])([email protected]):
[email protected].1([email protected]([email protected]))([email protected])([email protected]):
dependencies:
'@typescript-eslint/eslint-plugin': 8.72.0(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/parser': 8.72.0([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/typescript-estree': 8.72.0([email protected])([email protected])
'@typescript-eslint/utils': 8.72.0([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/eslint-plugin': 8.72.1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/parser': 8.72.1([email protected]([email protected]))([email protected])([email protected])
'@typescript-eslint/typescript-estree': 8.72.1([email protected])([email protected])
'@typescript-eslint/utils': 8.72.1([email protected]([email protected]))([email protected])([email protected])
eslint: 10.13.0([email protected])
typescript: 6.0.3
transitivePeerDependencies:
@@ -15326,9 +15326,9 @@ snapshots:
[email protected]: {}
[email protected]([email protected].4):
[email protected]([email protected].5):
dependencies:
browserslist: 4.29.4
browserslist: 4.29.5
escalade: 3.2.0
picocolors: 1.1.1
+10 -12
View File
@@ -4,8 +4,8 @@ import { consumeLocalize } from "../common/decorators/consume-context-entry";
import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-icon-next";
import "../components/ha-list";
import "../components/ha-list-item";
import "../components/item/ha-list-item-button";
import "../components/list/ha-list-base";
import type { AuthProvider } from "../data/auth";
declare global {
@@ -32,21 +32,19 @@ export class HaPickAuthProvider extends LitElement {
>${this._localize("ui.panel.page-authorize.pick_auth_provider")}</span
>
</h3>
<ha-list>
<ha-list-base>
${this.authProviders.map(
(provider) => html`
<ha-list-item
hasMeta
role="button"
<ha-list-item-button
.auth_provider=${provider}
@click=${this._handlePick}
>
${provider.name}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
<span slot="headline">${provider.name}</span>
<ha-icon-next slot="end"></ha-icon-next>
</ha-list-item-button>
`
)}
</ha-list>
</ha-list-base>
`;
}
@@ -80,9 +78,9 @@ export class HaPickAuthProvider extends LitElement {
background: var(--card-background-color);
padding: 0 15px;
}
ha-list {
ha-list-base {
margin: 16px -16px 0;
--mdc-list-side-padding: 24px;
--ha-row-item-padding-inline: 24px;
}
`;
}
+1 -1
View File
@@ -28,7 +28,7 @@ const RASTER_TILE_URL = `${MAP_TILES_PATH}/raster/{z}/{x}/{y}.png?token={token}`
// policy strips again unless the tiles ask for it back.
const DEMO_RASTER_TILE_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
const OSM_ATTRIBUTION =
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
'<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>';
// Browsers keep about 16 live WebGL contexts and drop the oldest, which a
// dashboard full of map cards hits. A transient loss is restored, hence a grace.
+12 -6
View File
@@ -89,6 +89,8 @@ export const DEFAULT_CHART_WIDTH = 500;
// is short enough for a whole screenful; on a desktop that would cover the page.
const VISIBILITY_ROOT_MARGIN_NARROW = "100%";
const VISIBILITY_ROOT_MARGIN = "300px";
export type ZoomSliderMode = "auto" | "always" | "never";
const DEFERRED_PROPS = [
"options",
"data",
@@ -163,9 +165,10 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
@property({ attribute: "small-controls", type: Boolean })
public smallControls?: boolean;
// For charts zoomed from outside, like the synced History charts
@property({ attribute: "hide-zoom-controls", type: Boolean })
public hideZoomControls = false;
// "auto" shows the zoom slider once zoomed. "never" hides every zoom
// control, for charts zoomed from outside like the synced History charts.
@property({ attribute: "zoom-slider" })
public zoomSlider: ZoomSliderMode = "auto";
// For charts sized to their content, like timelines, which grow to fit the
// zoom slider instead of giving up plot height
@@ -617,7 +620,7 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
>
${
(this._isZoomed || this._isGraphRoamed) &&
!this.hideZoomControls &&
this.zoomSlider !== "never" &&
!showZoomSlider
? html`<ha-icon-button
class="zoom-reset"
@@ -1064,9 +1067,12 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
private _showZoomSlider() {
const [start, end] = this._zoomRange;
return (
!this.hideZoomControls &&
this.zoomSlider !== "never" &&
this._hasZoomableXAxis() &&
(this._zoomSliderHeld || start !== 0 || end !== 100)
(this.zoomSlider === "always" ||
this._zoomSliderHeld ||
start !== 0 ||
end !== 100)
);
}
@@ -24,6 +24,7 @@ import type {
HomeAssistantInternationalization,
} from "../../types";
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
import type { ZoomSliderMode } from "./ha-chart-base";
import { sideTooltipPosition } from "./chart-tooltip-position";
import "./ha-chart-tooltip-marker";
import {
@@ -105,8 +106,8 @@ export class StateHistoryChartLine extends LitElement {
@property({ attribute: "expand-legend", type: Boolean })
public expandLegend?: boolean;
@property({ attribute: "hide-zoom-controls", type: Boolean })
public hideZoomControls = false;
@property({ attribute: "zoom-slider" })
public zoomSlider: ZoomSliderMode = "auto";
@state() private _chartData: LineSeriesOption[] = [];
@@ -159,7 +160,7 @@ export class StateHistoryChartLine extends LitElement {
@dataset-unhidden=${this._datasetUnhidden}
@chart-zoom=${this._handleDataZoom}
.expandLegend=${this.expandLegend}
.hideZoomControls=${this.hideZoomControls}
.zoomSlider=${this.zoomSlider}
.clickLabelForMoreInfo=${this.clickForMoreInfo}
@legend-label-click=${this._handleLegendLabelClick}
></ha-chart-base>
@@ -22,6 +22,7 @@ import type {
HomeAssistantInternationalization,
} from "../../types";
import { DEFAULT_CHART_WIDTH, MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
import type { ZoomSliderMode } from "./ha-chart-base";
import { itemTooltipPosition } from "./chart-tooltip-position";
import "./ha-chart-tooltip-marker";
import type { HaECOption, HaECSeries } from "../../resources/echarts/echarts";
@@ -68,8 +69,8 @@ export class StateHistoryChartTimeline extends LitElement {
@property({ attribute: false }) public chartIndex?;
@property({ attribute: "hide-zoom-controls", type: Boolean })
public hideZoomControls = false;
@property({ attribute: "zoom-slider" })
public zoomSlider: ZoomSliderMode = "auto";
@state() private _chartData: CustomSeriesOption[] = [];
@@ -105,7 +106,7 @@ export class StateHistoryChartTimeline extends LitElement {
grow-for-zoom-slider
@chart-click=${this._handleChartClick}
@chart-zoom=${this._handleDataZoom}
.hideZoomControls=${this.hideZoomControls}
.zoomSlider=${this.zoomSlider}
></ha-chart-base>
`;
}
+2 -2
View File
@@ -218,7 +218,7 @@ export class StateHistoryCharts extends LitElement {
@chart-zoom-with-index=${this._handleTimelineSync}
.height=${this.virtualize ? undefined : this.height}
.expandLegend=${this.expandLegend}
?hide-zoom-controls=${this.syncCharts}
.zoomSlider=${this.syncCharts ? "never" : "auto"}
></state-history-chart-line>
</div> `;
}
@@ -237,7 +237,7 @@ export class StateHistoryCharts extends LitElement {
.clickForMoreInfo=${this.clickForMoreInfo}
@y-width-changed=${this._yWidthChanged}
@chart-zoom-with-index=${this._handleTimelineSync}
?hide-zoom-controls=${this.syncCharts}
.zoomSlider=${this.syncCharts ? "never" : "auto"}
></state-history-chart-timeline>
</div> `;
};
+21 -12
View File
@@ -1,6 +1,5 @@
import { TZDate } from "@date-fns/tz";
import type { ContextType } from "@lit/context";
import type { ActionDetail } from "@material/mwc-list";
import { mdiCalendarToday } from "@mdi/js";
import "cally";
import type { HassConfig } from "home-assistant-js-websocket/dist/types";
@@ -17,6 +16,7 @@ import {
import { transform } from "../../common/decorators/transform";
import { fireEvent } from "../../common/dom/fire_event";
import type {
HASSDomCurrentTargetEvent,
HASSDomEvent,
HASSDomTargetEvent,
} from "../../common/dom/fire_event";
@@ -32,8 +32,8 @@ import type { HaBaseTimeInput } from "../ha-base-time-input";
import "../ha-icon-button";
import "../ha-icon-button-next";
import "../ha-icon-button-prev";
import "../ha-list";
import "../ha-list-item";
import "../item/ha-list-item-button";
import "../list/ha-list-base";
import "../ha-time-input";
import type { HaTimeInput } from "../ha-time-input";
import type { DateRangePickerRanges } from "./ha-date-range-picker";
@@ -166,11 +166,19 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
}
return html`
<ha-list @action=${this._setDateRange} activatable>
${Object.keys(this.ranges!).map(
(name) => html`<ha-list-item>${name}</ha-list-item>`
<ha-list-base>
${Object.entries(this.ranges!).map(
([name, range], index) => html`
<ha-list-item-button
.range=${range}
.index=${index}
@click=${this._setDateRange}
>
<span slot="headline">${name}</span>
</ha-list-item-button>
`
)}
</ha-list>
</ha-list-base>
`;
}
@@ -343,11 +351,12 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
this._saveDateRangePreset(chip.range, chip.index);
}
private _setDateRange(ev: HASSDomEvent<ActionDetail>) {
const dateRange: [Date, Date] = Object.values(this.ranges!)[
ev.detail.index
];
this._saveDateRangePreset(dateRange, ev.detail.index);
private _setDateRange(
ev: HASSDomCurrentTargetEvent<
HTMLElement & { range: [Date, Date]; index: number }
>
) {
this._saveDateRangePreset(ev.currentTarget.range, ev.currentTarget.index);
}
private _saveDateRangePreset(range: [Date, Date], index: number) {
+78 -21
View File
@@ -15,6 +15,7 @@ import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { tinykeys } from "tinykeys";
import { findSectionTitle } from "./list/find-section-title";
import { searchResultsStatus } from "./list/search-results-status";
import { consume } from "../common/decorators/consume";
import {
fireEvent,
@@ -23,6 +24,7 @@ import {
} from "../common/dom/fire_event";
import { ignoreRepeatedActivation } from "../common/keyboard/ignore-repeated-activation";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { internationalizationContext } from "../data/context";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import {
@@ -164,6 +166,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false })
public notFoundLabel?: string | ((search: string) => string);
/** Whether a row from `getItems` counts as a search result for screen readers, so a row offered for any search, like the quick bar's "Ask Assist", can be left out */
@property({ attribute: false })
public isSearchResult?: (item: PickerComboBoxItem) => boolean;
@property({ attribute: "empty-label" })
public emptyLabel?: string;
@@ -224,12 +230,18 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
@state() private _sectionTitle?: string;
@state() private _status?: string;
private _allItems: PickerComboBoxItem[] = [];
// Rows from this index on are added by the combo box itself, like "Add custom
// item", and belong to no section.
private _sectionedItemsCount = 0;
// Rows the combo box offers for any search, like "Add custom item" and the
// additional items, are not search results.
private _actionItems = new WeakSet<PickerComboBoxItem>();
// The virtualized list renders its scroller a moment after it connects, so
// the scroll fades attach once it reports its first visible rows.
private _virtualScrollElement?: HTMLElement;
@@ -251,13 +263,18 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
}
}
public willUpdate() {
public willUpdate(changedProps: PropertyValues) {
if (!this.hasUpdated) {
this._selectedSection = this.selectedSection;
this._allItems = this._getItems();
this._items = this._allItems;
this._updateListMode();
}
// New rows make the last message out of date, so clear it at once. The new
// one is then read out even when its text is the same as before.
if (changedProps.has("_items")) {
this._status = undefined;
}
}
protected updated(changedProps: PropertyValues) {
@@ -267,6 +284,13 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
if (changedProps.has("_items") && this._search) {
this._highlightTopMatch();
}
if (changedProps.has("_items")) {
if (this._search) {
this._updateStatus();
} else {
this._updateStatus.cancel();
}
}
}
// Scroll before the surface animates in, so the list does not jump at the
@@ -303,6 +327,8 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
disconnectedCallback() {
super.disconnectedCallback();
this._removeKeyboardShortcuts?.();
this._updateStatus.cancel();
this._status = undefined;
}
public refreshItems() {
@@ -362,9 +388,28 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
>
${this._plainList ? this._renderPlainList() : this._renderVirtualList()}
${this.renderScrollableFades()}
</div>
<div class="visually-hidden" role="status">
${this._status ?? nothing}
</div>`;
}
// DOM focus stays in the search field, so screen readers hear nothing when
// the list changes. Announce how many items match instead, once typing
// pauses, so the count doesn't compete with each typed character.
private _updateStatus = debounce(() => {
this._status = searchResultsStatus(
this._search,
this._items,
(item) =>
this._isOption(item) &&
!this._actionItems.has(item) &&
(this.isSearchResult?.(item) ?? true),
this.i18n?.localize,
this.notFoundLabel
);
}, 500);
private _renderPlainList() {
return html`
<ha-list-selectable
@@ -454,8 +499,25 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
}
}
private _getAdditionalItems = (searchString?: string) =>
this.getAdditionalItems?.(searchString) || [];
private _getAdditionalItems = (searchString?: string) => {
const items = this.getAdditionalItems?.(searchString) || [];
items.forEach((item) => this._actionItems.add(item));
return items;
};
private _getCustomValueItem(search: string): PickerComboBoxItem {
const item = {
id: search,
primary:
this.customValueLabel ??
this.i18n?.localize?.("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${search}"`,
icon_path: mdiPlus,
};
this._actionItems.add(item);
return item;
}
private _getItems = () => {
let items = [...(this.getItems(this._search, this._selectedSection) || [])];
@@ -497,15 +559,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
items.push(...additionalItems);
if (this.allowCustomValue && this._search) {
items.push({
id: this._search,
primary:
this.customValueLabel ??
this.i18n?.localize?.("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${this._search}"`,
icon_path: mdiPlus,
});
items.push(this._getCustomValueItem(this._search));
}
if (this.mode === "dialog") {
@@ -666,15 +720,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
}
if (this.allowCustomValue && searchString) {
filteredItems.push({
id: searchString,
primary:
this.customValueLabel ??
this.i18n?.localize?.("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${searchString}"`,
icon_path: mdiPlus,
});
filteredItems.push(this._getCustomValueItem(searchString));
}
this._items = filteredItems;
@@ -1018,6 +1064,17 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
align-items: center;
padding: var(--ha-space-3);
}
.visually-hidden {
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px;
width: 1px;
margin: -1px;
padding: 0;
border: 0;
}
`,
];
}
-52
View File
@@ -1,52 +0,0 @@
import { css } from "lit";
import { RadioListItemBase } from "@material/mwc-list/mwc-radio-list-item-base";
import { styles as controlStyles } from "@material/mwc-list/mwc-control-list-item.css";
import { styles } from "@material/mwc-list/mwc-list-item.css";
import { customElement } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
@customElement("ha-radio-list-item")
export class HaRadioListItem extends RadioListItemBase {
async onChange(event) {
super.onChange(event);
fireEvent(this, event.type);
}
static override styles = [
styles,
controlStyles,
css`
:host {
--mdc-theme-secondary: var(--primary-color);
}
:host([graphic="avatar"]) .mdc-deprecated-list-item__graphic,
:host([graphic="medium"]) .mdc-deprecated-list-item__graphic,
:host([graphic="large"]) .mdc-deprecated-list-item__graphic,
:host([graphic="control"]) .mdc-deprecated-list-item__graphic {
margin-inline-end: var(--mdc-list-item-graphic-margin, 16px);
margin-inline-start: 0px;
direction: var(--direction);
}
.mdc-deprecated-list-item__meta {
flex-shrink: 0;
direction: var(--direction);
margin-inline-start: auto;
margin-inline-end: 0;
}
.mdc-deprecated-list-item__graphic {
margin-top: var(--radio-list-item-graphic-margin-top);
}
:host([graphic="icon"]) .mdc-deprecated-list-item__graphic {
margin-inline-start: 0;
margin-inline-end: var(--mdc-list-item-graphic-margin, 32px);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-radio-list-item": HaRadioListItem;
}
}
@@ -0,0 +1,37 @@
import type { LocalizeFunc } from "../../common/translations/localize";
/**
* The screen reader message for a list search: how many items match, or that
* nothing does. A live region is only read out when its text changes, so the
* message names the search.
*
* Returns undefined when there is no search.
* @param search - The search text
* @param items - The list rows, with section titles as strings
* @param isResult - Whether a row is a result, so placeholder rows and rows
* offered for any search, like "Add custom item", are not counted
* @param localize - The localize function
* @param notFoundLabel - The not found label; one that takes the search is
* used when nothing matches, as it names the search already
*/
export const searchResultsStatus = <T>(
search: string,
items: readonly (T | string)[],
isResult: (item: T) => boolean,
localize?: LocalizeFunc,
notFoundLabel?: string | ((search: string) => string)
): string | undefined => {
if (!search) {
return undefined;
}
const count = items.filter(
(item): item is T => typeof item !== "string" && isResult(item)
).length;
const term = `‘${search}’`;
if (!count) {
return typeof notFoundLabel === "function"
? notFoundLabel(search)
: localize?.("ui.components.combo-box.no_results", { term });
}
return localize?.("ui.components.combo-box.result_count", { count, term });
};
+5
View File
@@ -282,6 +282,7 @@ export class QuickBar extends LitElement {
)}
.label=${this.hass.localize("ui.dialogs.quick-bar.title")}
.getItems=${this._getItems}
.isSearchResult=${this._isSearchResult}
.rowRenderer=${this._getRowRenderer(this._runningCommandId)}
mode="dialog"
.sections=${sections}
@@ -446,6 +447,10 @@ export class QuickBar extends LitElement {
// #region data
// "Ask Assist" is offered for any search, so it isn't a search result.
private _isSearchResult = (item: PickerComboBoxItem) =>
!("assistPrompt" in item);
private _getItems = (searchString: string, section: string) => {
this._selectedSection = section as QuickBarSection | undefined;
return this._getItemsMemoized(
+7 -1
View File
@@ -180,6 +180,13 @@ export class HomeAssistantMain extends LitElement {
color: var(--primary-text-color);
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
/* Keep the negative z-index panel backgrounds (the resolver's ::before
and the Lovelace view background) in this host's stacking context.
Isolating the conditionally rendered partial-panel-resolver instead
can intermittently leave the Android WebView (Chromium 109-124)
accessibility tree with an empty panel subtree while the page paints
normally, which breaks UI automation such as the companion app E2E. */
isolation: isolate;
--ha-sidebar-width: calc(56px + var(--safe-area-inset-left, 0px));
--ha-top-app-bar-width: calc(100% - var(--ha-sidebar-width));
--safe-area-content-inset-left: 0px;
@@ -201,7 +208,6 @@ export class HomeAssistantMain extends LitElement {
partial-panel-resolver {
display: block;
height: 100%;
isolation: isolate;
}
partial-panel-resolver::before {
content: "";
@@ -101,6 +101,8 @@ export class DialogYamlIntegration extends LitElement {
ha-dialog {
/* Place above other dialogs */
--dialog-z-index: 104;
--ha-dialog-header-white-space: normal;
--ha-dialog-header-title-height: auto;
}
`;
}
+16 -16
View File
@@ -5,9 +5,10 @@ import { customElement, property, state } from "lit/decorators";
import { stringCompare } from "../../../common/string/compare";
import "../../../components/ha-button";
import "../../../components/ha-card";
import "../../../components/ha-list";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import "../../../components/item/ha-list-item-base";
import "../../../components/item/ha-list-item-button";
import "../../../components/list/ha-list-base";
import "../../../components/user/ha-person-badge";
import type { Person } from "../../../data/person";
import {
@@ -92,23 +93,22 @@ export class HaConfigPerson extends LitElement {
</span>
<ha-card outlined class="storage">
<ha-list>
<ha-list-base>
${this._storageItems.map(
(entry) => html`
<ha-list-item
graphic="avatar"
<ha-list-item-button
@click=${this._openEditEntry}
.entry=${entry}
>
<ha-person-badge
.person=${entry}
slot="graphic"
slot="start"
></ha-person-badge>
<span>${entry.name}</span>
</ha-list-item>
<span slot="headline">${entry.name}</span>
</ha-list-item-button>
`
)}
</ha-list>
</ha-list-base>
${
this._storageItems.length === 0
? html`
@@ -138,19 +138,19 @@ export class HaConfigPerson extends LitElement {
this._configItems.length > 0
? html`
<ha-card outlined header="Configuration.yaml persons">
<ha-list>
<ha-list-base>
${this._configItems.map(
(entry) => html`
<ha-list-item graphic="avatar">
<ha-list-item-base>
<ha-person-badge
.person=${entry}
slot="graphic"
slot="start"
></ha-person-badge>
<span>${entry.name}</span>
</ha-list-item>
<span slot="headline">${entry.name}</span>
</ha-list-item-base>
`
)}
</ha-list>
</ha-list-base>
</ha-card>
`
: nothing
@@ -299,7 +299,7 @@ export class HaConfigPerson extends LitElement {
align-items: center;
justify-content: space-around;
}
ha-list:has(+ .empty) {
ha-list-base:has(+ .empty) {
display: none;
}
`;
@@ -73,6 +73,10 @@ const mountPartitionLabel = ({
return drive ? `${drive} — ${identity}, ${size}` : `${identity}, ${size}`;
};
// Supervisor accepts only ASCII letters, digits and underscores.
const mountNameFromLabel = (label?: string): string =>
(label ?? "").replace(/^\W+|\W+$/g, "").replace(/\W+/g, "_");
const requiredFieldsFilled = (
schema: readonly HaFormSchema[],
data?: MountFormData
@@ -305,6 +309,8 @@ class ViewMountDialog extends DirtyStateProviderMixin<
private _originalType?: SupervisorMountType;
private _suggestedName?: string;
@state() private _diskIdentity?: string;
public connectedCallback(): void {
@@ -529,7 +535,9 @@ class ViewMountDialog extends DirtyStateProviderMixin<
private _valueChanged(ev: CustomEvent) {
this._validationError = {};
this._validationWarning = {};
const previous = this._data;
this._data = ev.detail.value;
this._suggestName(previous);
if (this._data?.name && !/^\w+$/.test(this._data.name)) {
this._validationError.name = "invalid_name";
}
@@ -556,6 +564,32 @@ class ViewMountDialog extends DirtyStateProviderMixin<
);
}
// Name a new disk mount after the partition's volume label, unless the user
// typed a name of their own.
private _suggestName(previous?: MountFormData) {
const data = this._data;
if (data?.name !== previous?.name) {
this._suggestedName = undefined;
}
if (
this._existing ||
data?.type !== SupervisorMountType.DISK ||
(data.device === previous?.device &&
previous?.type === SupervisorMountType.DISK) ||
(data.name && data.name !== this._suggestedName)
) {
return;
}
const name = mountNameFromLabel(
this._partitions?.find((entry) => entry.partition.device === data.device)
?.partition.label
);
if (name !== (data.name ?? "")) {
data.name = name;
}
this._suggestedName = name;
}
private async _connectMount(ev) {
const progressButton = ev.target as HaProgressButton;
this._error = undefined;
@@ -28,7 +28,7 @@ import type { ContextType } from "@lit/context";
import type { HassConfig } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import {
@@ -78,7 +78,6 @@ import type {
HomeAssistantConfig,
HomeAssistantInternationalization,
} from "../../../types";
import { animationStyles } from "../../../resources/theme/animations.globals";
const RANGE_KEYS: DateRange[] = [
"today",
@@ -151,6 +150,10 @@ export class HuiEnergyPeriodSelector extends LitElement {
@state() private _loading = false;
@state() private _loadingLeaving = false;
@query(".loading-indicator") private _loadingIndicator?: HTMLElement;
private _loadingTimer?: ReturnType<typeof setTimeout>;
private _resizeObserver?: ResizeObserver;
@@ -379,10 +382,15 @@ export class HuiEnergyPeriodSelector extends LitElement {
: nothing
}
${
this._loading
this._loading || this._loadingLeaving
? html`<ha-spinner
class="loading-indicator"
class=${classMap({
"loading-indicator": true,
leaving: this._loadingLeaving,
})}
size="small"
@transitionend=${this._loadingTransitionDone}
@transitioncancel=${this._loadingTransitionDone}
></ha-spinner>`
: nothing
}
@@ -727,6 +735,10 @@ export class HuiEnergyPeriodSelector extends LitElement {
private _updateDates(energyData: EnergyData): void {
clearTimeout(this._loadingTimer);
if (this._loading) {
this._loadingLeaving = true;
this._removeLoadingIndicatorWithoutTransition();
}
this._loading = false;
this._compare = energyData.startCompare !== undefined;
this._startDate = energyData.start;
@@ -759,10 +771,30 @@ export class HuiEnergyPeriodSelector extends LitElement {
// Basically to ensure there's no "flash loading" when data is loaded quickly
clearTimeout(this._loadingTimer);
this._loadingTimer = setTimeout(() => {
this._loadingLeaving = false;
this._loading = true;
}, 200);
}
private async _removeLoadingIndicatorWithoutTransition() {
await this.updateComplete;
// No transition starts when the spinner was still at opacity 0
this._removeIdleLoadingIndicator();
}
private _loadingTransitionDone(ev: TransitionEvent) {
if (ev.propertyName === "opacity") {
this._removeIdleLoadingIndicator();
}
}
private _removeIdleLoadingIndicator() {
// Reversing the fade-in cancels a transition while the fade-out runs
if (!this._loadingIndicator?.getAnimations().length) {
this._loadingLeaving = false;
}
}
private _getDatePickerPlacement = memoizeOne(
(
verticalOpeningDirection: VerticalOpeningDirection | undefined,
@@ -778,7 +810,6 @@ export class HuiEnergyPeriodSelector extends LitElement {
);
static styles = css`
${animationStyles}
:host {
display: block;
}
@@ -843,7 +874,15 @@ export class HuiEnergyPeriodSelector extends LitElement {
position: absolute;
right: var(--ha-space-2);
align-items: center;
animation: fade-in var(--ha-animation-duration-normal) ease-in-out;
transition: opacity var(--ha-animation-duration-normal) ease-in-out;
}
@starting-style {
.loading-indicator {
opacity: 0;
}
}
.loading-indicator.leaving {
opacity: 0;
}
.date-actions .overflow {
display: flex;
@@ -0,0 +1,10 @@
// Install the Intl.PluralRules polyfill together with its locale data.
// FormatJS replaces Intl.PluralRules as soon as polyfill-force runs, and a
// data-less polyfill throws from select(). Keeping both imports in one module
// closes that gap for every caller, including the should-polyfill probes
// that other entry bundles run.
import "@formatjs/intl-pluralrules/polyfill-force";
// Locale data for plural rules breaks current JSON conversions as it includes functions,
// so only import English to avoid huge bundles
// TODO: Setup JS imports instead of JSON fetches
import "@formatjs/intl-pluralrules/locale-data/en";
+10 -15
View File
@@ -14,8 +14,6 @@ import {
polyfillTimeZoneData,
} from "./locale-data-polyfill";
let polyfilled = false;
const _polyfillTimeZoneData = polyfillTimeZoneData;
// The DateTimeFormat and DurationFormat polyfills call BigInt() when formatting
@@ -47,10 +45,6 @@ const setDefaultTimeZone = () => {
};
const polyfillIntl = async () => {
if (polyfilled) {
return;
}
polyfilled = true;
const locale = getLocalLanguage();
const polyfills: Promise<unknown>[] = [];
if (shouldPolyfillGetCanonicalLocales()) {
@@ -79,14 +73,10 @@ const polyfillIntl = async () => {
polyfills.push(import("@formatjs/intl-numberformat/polyfill-force"));
}
if (shouldPolyfillPluralRules(locale)) {
polyfills.push(
import("@formatjs/intl-pluralrules/polyfill-force").then(
// Locale data for plural rules breaks current JSON conversions as it includes functions,
// so only import English to avoid huge bundles
// TODO: Setup JS imports instead of JSON fetches
() => import("@formatjs/intl-pluralrules/locale-data/en")
)
);
// The polyfill and its English data are one chunk, so Intl.PluralRules
// is never installed without data. Its should-polyfill probe calls
// select(), which throws on a data-less polyfill.
polyfills.push(import("./intl-pluralrules"));
}
if (shouldPolyfillRelativeTimeFormat(locale)) {
polyfills.push(import("@formatjs/intl-relativetimeformat/polyfill-force"));
@@ -100,4 +90,9 @@ const polyfillIntl = async () => {
);
};
await polyfillIntl();
// Each legacy entry bundle (core, app, authorize, onboarding) carries its own
// copy of this module, and the bundles load in parallel. A module-level flag
// only stops re-runs within one bundle, so a second bundle could probe
// Intl.* while the first was still loading locale data. Share one promise
// per page instead, so every bundle waits for the same run.
await (globalThis.__haIntlPolyfill ??= polyfillIntl());
+4 -1
View File
@@ -17,6 +17,7 @@
"climate": "Climate",
"home": "Overview",
"maintenance": "Maintenance",
"marketplace": "Marketplace",
"notfound": "Page not found"
},
"state": {
@@ -1662,7 +1663,9 @@
"no_items": "No items available",
"unknown_item": "Unknown item",
"search_or_custom": "Search | Add custom item",
"add_custom_item": "Add custom item"
"add_custom_item": "Add custom item",
"result_count": "{count} {count, plural,\n one {result}\n other {results}\n} for {term}",
"no_results": "No results for {term}"
},
"suggest_with_ai": {
"label": "Suggest",
+2
View File
@@ -41,6 +41,8 @@ declare global {
var __HASS_URL__: string;
// Read by intl-polyfill before a polyfill replaces Intl.DateTimeFormat
var __haNativeTimeZone: string | undefined;
// Shared by every legacy entry bundle so the Intl polyfills load once
var __haIntlPolyfill: Promise<void> | undefined;
// Handshake between the app entry and the extra modules in index.html
var __haScopedRegistryReady: boolean | undefined;
var __haLoadExtraModules: (() => void) | undefined;
@@ -0,0 +1,60 @@
import { describe, expect, it } from "vitest";
import { searchResultsStatus } from "../../../src/components/list/search-results-status";
import { mockLocalize } from "../../fixtures/hass";
interface Row {
id: string;
// Like "Add custom item" or "Add new area", offered for any search
action?: boolean;
}
const isResult = (row: Row) => !row.action;
describe("searchResultsStatus", () => {
it("counts only the rows that are results", () => {
const rows: (Row | string)[] = [
"Lights",
{ id: "kitchen_light" },
{ id: "bedroom_light" },
{ id: "add_new", action: true },
];
expect(searchResultsStatus("light", rows, isResult, mockLocalize)).toBe(
'ui.components.combo-box.result_count: {"count":2,"term":"‘light’"}'
);
expect(
searchResultsStatus(
"garage",
["Lights", { id: "add_new", action: true }],
isResult,
mockLocalize
)
).toBe('ui.components.combo-box.no_results: {"term":"‘garage’"}');
});
it("uses a not found label that takes the search", () => {
expect(
searchResultsStatus(
"garage",
[],
isResult,
mockLocalize,
(search) => `No areas found for ‘${search}’`
)
).toBe("No areas found for ‘garage’");
});
// A live region is only read out when its text changes.
it("names the search, so a new search with as many results is read out", () => {
expect(
searchResultsStatus("bedroom", [{ id: "a" }], isResult, mockLocalize)
).not.toBe(
searchResultsStatus("fan", [{ id: "b" }], isResult, mockLocalize)
);
});
it("is undefined without a search", () => {
expect(
searchResultsStatus("", [{ id: "a" }], isResult, mockLocalize)
).toBeUndefined();
});
});