Compare commits

...

161 Commits

Author SHA1 Message Date
Paul Bottein
ebe5207b6e Improve datatable 2024-06-25 17:05:51 +02:00
Paul Bottein
bd1ede4145 Fix grid size picker size (#21161) 2024-06-25 12:24:04 +02:00
Paul Bottein
321a085c0e Resize card editor (#21115) 2024-06-24 22:10:31 +02:00
Bram Kragten
6a3041988a Allow to change username (#21152) 2024-06-24 18:51:43 +02:00
renovate[bot]
23fcdf876c Update dependency @codemirror/view to v6.28.2 (#21154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-24 12:24:01 -04:00
Bram Kragten
00f325e961 Support expandable in initial form data (#21153) 2024-06-24 17:30:43 +02:00
renovate[bot]
d00b3cfc61 Update Yarn to v4.3.1 (#21149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-24 13:33:19 +02:00
karwosts
4cc9e74ea8 Fix 'Move to view' operation (#21142) 2024-06-24 11:56:06 +02:00
dependabot[bot]
a56b9a96ce Bump softprops/action-gh-release from 2.0.5 to 2.0.6 (#21148)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 08:37:48 +02:00
renovate[bot]
d4b5f4bc14 Update dependency @octokit/rest to v21 (#21146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-23 23:22:08 -04:00
renovate[bot]
cf1523ee73 Update dependency @types/chromecast-caf-receiver to v6.0.15 (#21138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-23 12:54:15 +02:00
renovate[bot]
f5d571ca84 Update dependency webpack to v5.92.1 (#21134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 20:54:24 +02:00
karwosts
362e92f313 Add some weather attribute icons and units (#21133) 2024-06-22 15:42:26 +02:00
Jan-Philipp Benecke
5ddf72b973 Add preview to Threshold config & option flow (#19845)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-06-22 13:32:40 +02:00
renovate[bot]
6e78c28f51 Update dependency @codemirror/autocomplete to v6.16.3 (#21130)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 09:10:52 +02:00
renovate[bot]
772f0bb669 Update dependency tar to v7.4.0 (#21129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 08:51:43 +02:00
renovate[bot]
846c2a848f Update dependency glob to v10.4.2 (#21127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 08:51:08 +02:00
renovate[bot]
8495757005 Update dependency @braintree/sanitize-url to v7.0.3 (#21126) 2024-06-21 22:09:25 -04:00
karwosts
9960d38b91 Offer to delete no-longer-recorded statistics (#21119) 2024-06-21 11:18:24 +02:00
Simon Lamon
d3222f8bb0 Various fixes in dialogs (#20935)
* allow escape and scrim action for repair dialogs

* improve delete entity dialogs

* reiterate refresh token dialog wordings (kept refresh token for now)

* improve device delete dialogs

* Improve deletable text and invalidation
2024-06-21 11:15:01 +02:00
Simon Lamon
2e5cce5409 Replace paper-listbox in cast frontend (#19954)
* hc-cast

* Update cast/src/launcher/layout/hc-cast.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Update cast/src/launcher/layout/hc-cast.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Update cast/src/launcher/layout/hc-cast.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Fixes

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-06-21 11:13:13 +02:00
Paul Bottein
f78946447f Show card preview inside section in card editor (#21065)
* Show card inside section in card editor

* Replace edit mode by preview

* Add backward compatibility for custom cards

* Re-order props
2024-06-21 11:12:18 +02:00
Jay Turner
eb0579ddc5 Use EnergyCardBaseConfig where appropriate (#20896)
* Use EnergyCardBaseConfig where appropriate

* Update type key

* Rename class

* Run prettier
2024-06-21 11:11:41 +02:00
Steve Repsher
686424fc70 Add CoreJS polyfills to modern build (#20676) 2024-06-21 11:07:39 +02:00
renovate[bot]
039e9b40bd Update typescript-eslint monorepo to v7.13.1 (#21121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-20 14:41:07 -04:00
karwosts
8272bef890 Sort filter-domains on translated name (#21116) 2024-06-19 17:31:04 +02:00
Kevin Jahrens
62528b2413 Fix back paths (#21112)
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-06-19 07:08:14 +00:00
renovate[bot]
fa24f529e0 Lock file maintenance (#21114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 08:58:38 +02:00
dependabot[bot]
43a54f6cda Bump ws from 7.5.9 to 7.5.10 (#21111) 2024-06-18 20:56:53 -04:00
Paulus Schoutsen
9c153bbd58 Split out service entities (#21076)
* Hide notify entiites from generated dashboard

* Split out service entities on device info page

* Update src/panels/lovelace/common/generate-lovelace-config.ts

* Split service -> notify/assist
2024-06-18 08:58:45 +02:00
Bram Kragten
27afe9ecb7 Wrap code editor for template selector (#21104)
* Wrap code editor for template selector
2024-06-17 19:15:55 +00:00
dependabot[bot]
72f989e2bd Bump actions/checkout from 4.1.6 to 4.1.7 (#21102) 2024-06-17 10:19:48 +02:00
renovate[bot]
a6ef46565f Update dependency @codemirror/view to v6.28.1 (#21099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-16 09:43:11 +02:00
renovate[bot]
a35ac09688 Update dependency lint-staged to v15.2.7 (#21098)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 20:55:19 +02:00
Douwe
27024135ea Update hui-grid-section.ts (#21088)
Add variable to align title
2024-06-15 18:21:42 +02:00
Franck Nijhof
8759ed740a Make the radius of the home zone configurable (#21096) 2024-06-15 14:41:09 +02:00
Franck Nijhof
bb3e8ae33d Update home-assistant-js-websocket to 9.4.0 (#21097) 2024-06-15 08:03:55 -04:00
renovate[bot]
b5b60c9bf0 Update vaadinWebComponents monorepo to v24.4.0 (#21089)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 00:12:37 -04:00
renovate[bot]
3b6a2cf7d8 Update dependency @codemirror/view to v6.28.0 (#21081)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 00:09:09 -04:00
renovate[bot]
7e10e14102 Update dependency lint-staged to v15.2.6 (#21092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 21:21:25 +02:00
renovate[bot]
a580abab4a Update dependency webpack to v5.92.0 (#21090)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 20:21:11 +02:00
renovate[bot]
11523c08c4 Update Yarn to v4.3.0 (#21084) 2024-06-14 12:29:02 -04:00
renovate[bot]
7a8988528b Update dependency prettier to v3.3.2 (#21087)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 08:42:55 +02:00
renovate[bot]
2a6380f083 Update typescript-eslint monorepo to v7.13.0 (#21082)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 08:29:55 +02:00
Paul Bottein
29881c8bb4 Fix selected state for selected config entries (#21079) 2024-06-13 18:26:45 +02:00
Simon Lamon
56254ddf03 Fix diagnostic download not downloading (#21078) 2024-06-13 13:31:30 +02:00
Paulus Schoutsen
007ba70641 Hide notify entities from generated dashboard (#21075)
Hide notify entiites from generated dashboard
2024-06-13 08:04:27 +02:00
Steve Repsher
3e1227b064 Do not inject Intl polyfill into ecma402-abstract package (#21074) 2024-06-12 14:45:57 -04:00
Matthias Alphart
067e179f26 Translation support for device automation extra fields (#20567)
* Translation support for device trigger extra fields

* Prefer component translation over default

* Move device trigger extra_fields translations to backend

* move translations for extra_fields of conditions and actions too
2024-06-12 14:09:50 +02:00
AlCalzone
9a3f7df25e Z-Wave: Prevent closing the Add Device dialog when user input is required (#20999)
* prevent closing the Z-Wave Add node dialog when user input is required

* ask user for confirmation before leaving page during bootstrapping

* fix: no non-null assertion
2024-06-12 13:48:40 +02:00
Paul Bottein
c7b4e8f37c Fix current mode not selected in card feature (#21063) 2024-06-12 13:40:44 +02:00
Paul Bottein
bfa8b886ab Make update actions sticky on more info (#21053) 2024-06-12 13:39:04 +02:00
Paul Bottein
433c00b73a Move card loading logic into hui-card (#21018)
* Move card rebuild to hui-card

* Use hui card in stack card

* add once to event

* Do not use state

* Use hui card in conditional card

* Use editMode instead of lovelace in hui card

* Fix edit mode

* Use hui-card in card dialog and panel todo

* Fix edit mode

* Fix types

* Migrate entity filter card

* Update demo card

* Fix UI view

* Allow edit mode attribute

* Remove unused condition

* Remove unused section preview code

* Remove useless check for config
2024-06-12 13:38:21 +02:00
Stefan Agner
a497f42f73 Move send credentials to phone to main Thread configuration panel (#21066)
Move send credentials to phone to main view

Currently the button is hidden in the more info dialog, and even there
it seems that it is currently not rendered correctly.

This moves the button to the main view, make it more obvious while
still keeping it out of the way if the feature is not applicable.
2024-06-12 13:35:29 +02:00
Stefan Agner
165723cb5b Clarify Thread credentials transfer direction (#21067)
"Import credentials" on a phone can be missunderstood as importing
credentials from Home Assistant to the phone, but this is not what
this command is doing.

Use "Send credentials to Home Assistant" to make it clear what the
direction of the transfer is.
2024-06-12 13:32:51 +02:00
Bruno Pantaleão Gonçalves
42b5fa696a Fix "canImportKeychain" boolean for thread panel (#21062) 2024-06-11 10:04:13 +02:00
renovate[bot]
59062d96a8 Update dependency @rollup/plugin-commonjs to v26 (#21037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-10 21:56:01 +02:00
Paulus Schoutsen
d36bbfe07d Hide hidden updates from sidebar/settings (#21058) 2024-06-10 15:52:24 -04:00
Bram Kragten
0d489213a4 Bumped version to 20240610.0 2024-06-10 19:50:10 +02:00
koostamas
c54acc9369 Fix automation describeCondition for number state type (#21052)
When using a number state condition in an automation, the UI used an incorrect evaluation when trying to describe the condition which made the label default to the default value.
To fix this, I just changed the evaluation to check directly for `undefined` value.
2024-06-10 19:19:26 +02:00
Simon Lamon
562bc084f0 Revert fullcalendar back to v6.1.11 (#21039)
* Drop fullcalendar back to v6.1.11

* Add resolution
2024-06-10 09:23:53 +02:00
karwosts
6fce2f35a5 Add a title to triggered dialog (#21046) 2024-06-09 21:33:58 +02:00
karwosts
f4e24bed2e Fix cancel button in section edit (#21045) 2024-06-09 12:54:38 +00:00
renovate[bot]
09969c0e2d Update babel monorepo to v7.24.7 (#21035)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 19:52:09 +02:00
renovate[bot]
4b0181774b Update CodeMirror (#21022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 19:51:08 +02:00
renovate[bot]
272db5e9e8 Update dependency @rollup/plugin-replace to v5.0.7 (#21036)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 19:50:43 +02:00
renovate[bot]
9ae3a824d9 Update dependency prettier to v3.3.1 (#21033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 12:29:12 +02:00
renovate[bot]
9db55c9391 Update dependency @lit-labs/virtualizer to v2.0.13 (#21029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 09:42:52 +02:00
renovate[bot]
59697127c0 Update dependency @rollup/plugin-replace to v5.0.6 (#21031)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 08:50:00 +02:00
renovate[bot]
565600e945 Update dependency @codemirror/view to v6.26.4 (#21021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-07 16:08:38 +02:00
karwosts
721eebf367 Add floor selector to script fields (#21016) 2024-06-07 13:02:58 +02:00
renovate[bot]
f5ae842167 Update typescript-eslint monorepo to v7.12.0 (#21014) 2024-06-06 20:35:13 -04:00
renovate[bot]
3575734ed0 Update dependency @codemirror/language to v6.10.2 (#21008) 2024-06-06 20:34:16 -04:00
karwosts
4e8de1f64d Todo button menu: add stop propagation (#20996) 2024-06-05 11:43:21 +02:00
Bram Kragten
cd73b8ac29 Bumped version to 20240605.0 2024-06-05 11:41:51 +02:00
Paul Bottein
74eca6b1f5 Increase check update timeout to 15s (#20998) 2024-06-05 10:13:28 +02:00
Paulus Schoutsen
9ef0bd6e46 Fix Assist styling (#20997) 2024-06-05 09:53:40 +02:00
renovate[bot]
53eb7f771f Update dependency prettier to v3.3.0 (#20992)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-04 20:27:20 +02:00
Bram Kragten
cd62f064cb Bumped version to 20240604.0 2024-06-04 16:47:00 +02:00
Paul Bottein
db82b856e0 Fix filter card visibility when show empty is false (#20985) 2024-06-04 16:46:38 +02:00
Paul Bottein
ab340e13e9 Fix max option saving using keyboard for script mode (#20984) 2024-06-04 16:46:08 +02:00
Paul Bottein
22c54b3fea Fix card editor size on mobile (#20976) 2024-06-04 16:45:54 +02:00
Alex van den Hoogen
2dd7e598d5 Add missing hui-root callback, fixes #20854 (#20975)
* Add missing hui-root callback, fixes #20854

* Set scroll event listener on hui-root to passive
2024-06-04 14:03:04 +02:00
Raman Gupta
d1ce06e368 Fix parameter name in zwave_js WS API (#20981) 2024-06-04 11:21:52 +02:00
Bram Kragten
9717304b68 Bumped version to 20240603.0 2024-06-03 18:43:00 +02:00
Bram Kragten
c646f3c39a Save search filter in session storage (#20973)
* Save search filter in session storage

* Apply suggestions from code review

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-06-03 18:42:34 +02:00
Paul Bottein
0297ec5a7b Add visibility editor to card editor (#20926)
* Add visibility editor to card editor

* Add translations

* AI suggestion
2024-06-03 18:23:47 +02:00
Paul Bottein
e48286c2a0 Fix conditional card visiblity inside section (#20966)
* Fix conditional card visiblity inside section

* Remove _ from protected method
2024-06-03 18:23:30 +02:00
Brynley McDonald
f2b2da9877 Fix filters for entity/device from integrations dashboard (#20953) 2024-06-03 17:50:40 +02:00
Simon Lamon
250f87cfd8 Improve error messages in config entries (#20934)
* Improve error messages and test completely

* Add css back

* Revert ha-alert changes

* Update src/panels/config/integrations/ha-config-integration-page.ts

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-06-03 16:29:47 +02:00
Paul Bottein
9f6afb162a Make lovelace optional in card and section (#20970) 2024-06-03 16:05:41 +02:00
Paul Bottein
0add65feff Use event for section visibility instead of mutation observer (#20967) 2024-06-03 15:54:40 +02:00
Paul Bottein
c08d9a9166 Fix card display in view panel (#20972) 2024-06-03 15:49:31 +02:00
ildar170975
d9a9038cec Update ha-entity-marker.ts: add CSS variable for border-radius (#20914)
* Update ha-entity-marker.ts

* fix whitespace
2024-06-03 15:40:46 +02:00
Paul Bottein
6bee3ef45c Fix fallback icon color in dark mode (#20969) 2024-06-03 14:16:18 +02:00
Paul Bottein
3a855f95ad Fix broken config switch in demo (#20971) 2024-06-03 13:37:32 +02:00
Paulus Schoutsen
e7f3393ec6 Tweak styles Assist dialog (#20960) 2024-06-03 11:42:43 +02:00
karwosts
d7cb4cb537 Revert "Filter unrecorded entities from history panel (#19621)" (#20941)
This reverts commit d88670034a.
2024-06-03 10:03:30 +02:00
renovate[bot]
c55720c933 Update dependency @codemirror/autocomplete to v6.16.2 (#20965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-03 07:49:47 +00:00
dependabot[bot]
f78e757485 Bump relative-ci/agent-action from 2.1.10 to 2.1.11 (#20964)
Bumps [relative-ci/agent-action](https://github.com/relative-ci/agent-action) from 2.1.10 to 2.1.11.
- [Release notes](https://github.com/relative-ci/agent-action/releases)
- [Commits](https://github.com/relative-ci/agent-action/compare/v2.1.10...v2.1.11)

---
updated-dependencies:
- dependency-name: relative-ci/agent-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 09:39:01 +02:00
renovate[bot]
fa03c58a93 Update dependency tar to v7.2.0 (#20952)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-02 15:21:42 +02:00
renovate[bot]
49f1ad633f Update dependency @codemirror/autocomplete to v6.16.1 (#20948) 2024-06-01 22:39:12 -04:00
renovate[bot]
d449e10120 Update dependency eslint-plugin-lit to v1.14.0 (#20950) 2024-06-01 22:37:39 -04:00
renovate[bot]
474c8c243e Update dependency ua-parser-js to v1.0.38 (#20939)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 14:49:31 -04:00
renovate[bot]
e9e53e9451 Update dependency workbox-build to v7.1.1 (#20937)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 14:29:02 +02:00
renovate[bot]
cfa84f30be Update typescript-eslint monorepo to v7.11.0 (#20932)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 00:41:51 -04:00
karwosts
7416ae7dfd Fix double unsubscribe in todo list card (#20928) 2024-05-30 20:21:39 +02:00
Bram Kragten
6f1fa139e7 Bumped version to 20240530.0 2024-05-30 16:08:31 +02:00
Paulus Schoutsen
b38a348957 Disable beta menu instead of hiding (#20906) 2024-05-30 15:29:28 +02:00
Simon Lamon
f97971faf6 Improve refresh token dialogs (#20917)
* Improve refresh token dialogs

* Remove this
2024-05-30 10:39:15 +02:00
Steve Repsher
c5ae9e8497 Remove eslint-plugin-disable (#20902) 2024-05-30 09:49:40 +02:00
Paul Bottein
c00287c401 Fix config entry menu (#20908) 2024-05-29 23:49:20 +02:00
Bram Kragten
c0e048023d format webpack.cjs 2024-05-29 18:04:24 +02:00
Bram Kragten
431f4937c1 Update webpack.cjs 2024-05-29 17:55:40 +02:00
Bram Kragten
0a55220837 Merge branch 'master' into dev 2024-05-29 17:53:04 +02:00
Paul Bottein
13f01492b4 Add visibility option to dashboard cards (#20840)
* Create hui card

* Add compatiblity with helpers

* Improve layout options

* Fix conditional card

* Add missing import

* Add visibility option in config

* Fix conditions

* Fix case with multiple conditions

* Remove useless set hass
2024-05-29 17:50:16 +02:00
Bram Kragten
ce5bcf61f9 Bumped version to 20240529.0 2024-05-29 17:49:29 +02:00
Bram Kragten
d31a777135 use image selector for view background (#20898)
* use image selector for view background

* make config future proof

* improvements
2024-05-29 17:29:09 +02:00
Paul Bottein
5cc08cfe0b Fix area card editor when an entity have an unknown device (#20900) 2024-05-29 14:29:04 +00:00
Steve Repsher
3eea7dc6cd Use valid locale for translation test (#20899) 2024-05-29 15:44:56 +02:00
karwosts
a629f01300 Collapsible blueprint input sections (#19946)
* Deduplicate blueprint editor code

* Collapsible blueprint sections

* add description

* renamed collapsed

* unused import

* unused import

* Don't allow collapsing sections with required

* Update to new schema
2024-05-29 15:44:11 +02:00
renovate[bot]
f1345af526 Update dependency @bundle-stats/plugin-webpack-filter to v4.13.2 (#20897)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-29 12:44:04 +00:00
karwosts
064c51f487 Add a picture uploader to picture-card-editor (#18695)
* Add a picture uploader to picture-card-editor

* add imageSelector

* lint

* Add delete button to picture-upload

* updates from feedback

* fix lint

* Update en.json

* Update selector.ts

* remove delete
2024-05-29 14:32:53 +02:00
karwosts
d88670034a Filter unrecorded entities from history panel (#19621)
* Filter unrecorded entities from history panel

* cache result

* Cache excluded entities instead of recorded entities
2024-05-29 14:32:22 +02:00
Georgi Stanojevski
5fab1969a8 Add Macedonian (Македонски) to the frontend. (#20701)
Following: https://developers.home-assistant.io/docs/translations/#maintainer-steps-to-add-a-new-language

Steps 1 and 2.

- It has the "mk" code in the IANA subtag registry.
- Adding it in src/translations/translationMetadata.json
2024-05-29 14:13:45 +02:00
Simon Lamon
b3e14d449e Show detailed config entry error inline (#20764)
* Put config entry error inline

* Fixes (show configure button and don't make them interactive)
2024-05-29 14:07:52 +02:00
Steve Repsher
97206ee8fe Inject element polyfills where used using Babel (#20689) 2024-05-29 14:02:40 +02:00
Steve Repsher
7748315fc3 Inject Intl polyfills where used (#20798)
* Inject Intl polyfills where used

* Replace Intl polyfill in localize method with loading intl-messageformat asynchronously

* Remove spurious feature tests for Intl
2024-05-29 14:01:21 +02:00
Paul Bottein
e059ca146b Script change icon (#20885)
* Add icon to rename dialog

* Check in entity registry

* Only use icon for script
2024-05-29 13:57:13 +02:00
Jay Turner
56cabeb497 Fix type value on Interface for the energy-usage-graph (#20895) 2024-05-29 10:33:12 +00:00
Paul Bottein
7a7bd87f50 Unify usage of dashboard title (#20853) 2024-05-29 12:29:52 +02:00
Adam Kapos
ff9c794659 Add UI for setting view background (#20708)
* Add UI for setting view background

* Update eslint-plugin-unused-imports to fix parse failure

* Changes from review
2024-05-29 12:23:54 +02:00
Bram Kragten
2921161336 Save data table filters in session storage (#20894)
* Save data table filters in session storage

* typing fix

* remove url logic, rename storage key
2024-05-29 12:20:30 +02:00
G Johansson
91e5fcacd5 Add default code to alarm_control_panel (#20062)
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-05-29 11:05:46 +02:00
Raman Gupta
febbf34de6 Change Z-Wave JS API model to match zwave-js (#20793)
* Change Z-Wave JS API model to match zwave-js

* fix qrprovisioninginformation

* remove additional properties from QRProvisioningInformation
2024-05-29 09:25:09 +02:00
Bram Kragten
5a2977f4d4 Add collapse & expand all groups (#20891)
* Add collapse & expand all groups

* review suggestion
2024-05-29 09:16:26 +02:00
Paul Bottein
7a7a355765 Allowing toggle of expiration date for refresh token (#20846)
* Allowing removal of expiration date for refresh token

* Adjust wording and add icon

* Update current

* Update src/translations/en.json

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* Update wording

* Allow enable and disable

* Better type

* Better handle errors

* Use relative date

* Update src/panels/profile/ha-refresh-tokens-card.ts

* Update API

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-05-29 09:12:08 +02:00
Paul Bottein
ccebae84a7 Use list for change mode dialog (#20890)
* Use list for change mode dialog

* Add listbox role

* Remove unused import
2024-05-28 18:23:12 +02:00
renovate[bot]
f96f38ee82 Update dependency lint-staged to v15.2.5 (#20892)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-28 10:46:59 -04:00
renovate[bot]
d4056e6a32 Update dependency @bundle-stats/plugin-webpack-filter to v4.13.1 (#20889)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-28 09:31:17 +02:00
renovate[bot]
389a7a6ed9 Update dependency eslint-plugin-unused-imports to v4 (#20884)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 14:04:45 -04:00
renovate[bot]
05aecaaaf1 Update babel monorepo to v7.24.6 (#20883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 15:47:29 +02:00
karwosts
085131d546 Rename energy 'Today' button to 'Now' (#20871) 2024-05-27 14:13:55 +02:00
renovate[bot]
c2737d5cec Update dependency @bundle-stats/plugin-webpack-filter to v4.13.0 (#20877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 14:11:34 +02:00
renovate[bot]
29ae46d775 Update dependency @material/web to v1.5.0 (#20878)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 14:09:55 +02:00
Yosi Levy
dfee3ba089 RTL fixes (#20880) 2024-05-27 14:09:29 +02:00
Bram Kragten
b2af21ba5c Bumped version to 20240501.1 2024-05-06 17:50:01 +02:00
Paul Bottein
12a61a0021 Remove alarm modes list when adding a alarm modes card feature (#20688) 2024-05-06 17:49:30 +02:00
Simon Lamon
649917cdde Always save custom display name in energy dashboard when hitting Enter (#20702)
Change to Input event
2024-05-06 17:49:14 +02:00
karwosts
3ed27ee853 Add spacer for FAB under the zone list (#20706) 2024-05-06 17:48:58 +02:00
karwosts
c1d3a76917 Energy CSV download should not require admin (#20704) 2024-05-06 17:48:38 +02:00
Paul Bottein
571ed6b9e9 Revert usage of babel runtime for legacy bundle (#20741)
Revert usage of babel runtine for legacy bundle
2024-05-06 17:48:21 +02:00
Paulus Schoutsen
a347315fa7 Fix showing options button on conversation agent picker (#20736) 2024-05-06 17:47:59 +02:00
Simon Lamon
57d1405115 Show ungrouped group when there are results (#20716) 2024-05-06 17:47:41 +02:00
Yosi Levy
e5ff6bd2f5 Font updates in new filters (#20482)
* Style changes

* Fixes
2024-05-06 17:47:21 +02:00
Bram Kragten
e2266aa671 Merge branch 'dev' 2024-05-01 12:04:10 +02:00
Bram Kragten
ef4f11fdf8 20240430.0 (#20681) 2024-04-30 23:58:58 +02:00
Bram Kragten
e7c1ac94af 20240429.0 (#20665) 2024-04-29 17:44:33 +02:00
Paul Bottein
1acbcccd62 20240426.0 (#20636) 2024-04-26 11:42:26 +02:00
Bram Kragten
64f54d9aaa 20240424.1 (#20609) 2024-04-24 14:45:22 +02:00
Bram Kragten
8712adbf8d 20240424.0 (#20602) 2024-04-24 11:21:24 +02:00
220 changed files with 6878 additions and 4470 deletions

View File

@@ -126,6 +126,5 @@
"lit-a11y/anchor-is-valid": "warn",
"lit-a11y/role-has-required-aria-attrs": "warn"
},
"plugins": ["disable", "unused-imports"],
"processor": "disable/disable"
"plugins": ["unused-imports"]
}

View File

@@ -21,7 +21,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
with:
ref: dev
@@ -57,7 +57,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
with:
ref: master

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Setup Node
uses: actions/setup-node@v4.0.2
with:
@@ -58,7 +58,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Setup Node
uses: actions/setup-node@v4.0.2
with:
@@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Setup Node
uses: actions/setup-node@v4.0.2
with:
@@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Setup Node
uses: actions/setup-node@v4.0.2
with:

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.

View File

@@ -22,7 +22,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
with:
ref: dev
@@ -58,7 +58,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
with:
ref: master

View File

@@ -16,7 +16,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Setup Node
uses: actions/setup-node@v4.0.2

View File

@@ -21,7 +21,7 @@ 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@v4.1.6
uses: actions/checkout@v4.1.7
- name: Setup Node
uses: actions/setup-node@v4.0.2

View File

@@ -20,7 +20,7 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5

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@v2.1.10
uses: relative-ci/agent-action@v2.1.11
with:
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
token: ${{ github.token }}

View File

@@ -23,7 +23,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
@@ -55,7 +55,7 @@ jobs:
script/release
- name: Upload release assets
uses: softprops/action-gh-release@v2.0.5
uses: softprops/action-gh-release@v2.0.6
with:
files: |
dist/*.whl

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Upload Translations
run: |

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.2.2.cjs
yarnPath: .yarn/releases/yarn-4.3.1.cjs

View File

@@ -18,6 +18,39 @@ const PolyfillSupport = {
safari: 17.4,
samsung: 15.0,
},
"element-append": {
android: 54,
chrome: 54,
edge: 17,
firefox: 49,
ios: 10.0,
opera: 41,
opera_mobile: 41,
safari: 10.0,
samsung: 6.0,
},
"element-getattributenames": {
android: 61,
chrome: 61,
edge: 18,
firefox: 45,
ios: 10.3,
opera: 48,
opera_mobile: 45,
safari: 10.1,
samsung: 8.0,
},
"element-toggleattribute": {
android: 69,
chrome: 69,
edge: 18,
firefox: 63,
ios: 12.0,
opera: 56,
opera_mobile: 48,
safari: 12.0,
samsung: 10.0,
},
fetch: {
android: 42,
chrome: 42,
@@ -29,6 +62,31 @@ const PolyfillSupport = {
safari: 10.1,
samsung: 4.0,
},
"intl-getcanonicallocales": {
android: 54,
chrome: 54,
edge: 16,
firefox: 48,
ios: 10.3,
opera: 41,
opera_mobile: 41,
safari: 10.1,
samsung: 6.0,
},
"intl-locale": {
android: 74,
chrome: 74,
edge: 79,
firefox: 75,
ios: 14.0,
opera: 62,
opera_mobile: 53,
safari: 14.0,
samsung: 11.0,
},
"intl-other": {
// Not specified (i.e. always try polyfill) since compatibility depends on supported locales
},
proxy: {
android: 49,
chrome: 49,
@@ -69,8 +127,38 @@ const polyfillMap = {
key: "element-internals",
module: "element-internals-polyfill",
},
...Object.fromEntries(
["append", "getAttributeNames", "toggleAttribute"].map((prop) => {
const key = `element-${prop.toLowerCase()}`;
return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }];
})
),
},
static: {
Intl: {
getCanonicalLocales: {
key: "intl-getcanonicallocales",
module: join(POLYFILL_DIR, "intl-polyfill.ts"),
},
Locale: {
key: "intl-locale",
module: join(POLYFILL_DIR, "intl-polyfill.ts"),
},
...Object.fromEntries(
[
"DateTimeFormat",
"DisplayNames",
"ListFormat",
"NumberFormat",
"PluralRules",
"RelativeTimeFormat",
].map((obj) => [
obj,
{ key: "intl-other", module: join(POLYFILL_DIR, "intl-polyfill.ts") },
])
),
},
},
static: {},
};
// Create plugin using the same factory as for CoreJS
@@ -78,7 +166,7 @@ export default defineProvider(
({ createMetaResolver, debug, shouldInjectPolyfill }) => {
const resolvePolyfill = createMetaResolver(polyfillMap);
return {
name: "HA Custom",
name: "custom-polyfill",
polyfills: PolyfillSupport,
usageGlobal(meta, utils) {
const polyfill = resolvePolyfill(meta);

View File

@@ -92,8 +92,8 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
[
"@babel/preset-env",
{
useBuiltIns: latestBuild ? false : "usage",
corejs: latestBuild ? false : dependencies["core-js"],
useBuiltIns: "usage",
corejs: dependencies["core-js"],
bugfixes: true,
shippedProposals: true,
},
@@ -157,6 +157,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
exclude: [
path.join(paths.polymer_dir, "src/resources/polyfills"),
...[
"@formatjs/(?:ecma402-abstract|intl-\\w+)",
"@lit-labs/virtualizer/polyfills",
"@webcomponents/scoped-custom-element-registry",
"element-internals-polyfill",

View File

@@ -19,6 +19,7 @@ const inBackendDir = "translations/backend";
const workDir = "build/translations";
const outDir = join(workDir, "output");
const EN_SRC = join(paths.translations_src, "en.json");
const TEST_LOCALE = "en-x-test";
let mergeBackend = false;
@@ -150,7 +151,7 @@ const createTestTranslation = () =>
: gulp
.src(EN_SRC)
.pipe(new CustomJSON(null, testReviver))
.pipe(rename("test.json"))
.pipe(rename(`${TEST_LOCALE}.json`))
.pipe(gulp.dest(workDir));
/**
@@ -192,7 +193,7 @@ const createTranslations = async () => {
// each locale, then fragmentizes and flattens the data for final output.
const translationFiles = await glob([
`${inFrontendDir}/!(en).json`,
...(env.isProdBuild() ? [] : [`${workDir}/test.json`]),
...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
]);
const hashStream = new Transform({
objectMode: true,
@@ -254,8 +255,8 @@ const createTranslations = async () => {
const mergeFiles = [];
for (let i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join("-");
if (lang === "test") {
mergeFiles.push(`${workDir}/test.json`);
if (lang === TEST_LOCALE) {
mergeFiles.push(`${workDir}/${TEST_LOCALE}.json`);
} else if (lang !== "en") {
mergeFiles.push(`${inFrontendDir}/${lang}.json`);
if (mergeBackend) {
@@ -284,7 +285,7 @@ const writeTranslationMetaData = () =>
new CustomJSON((meta) => {
// Add the test translation in development.
if (!env.isProdBuild()) {
meta.test = { nativeName: "Test" };
meta[TEST_LOCALE] = { nativeName: "Translation Test" };
}
// Filter out locales without a native name, and add the hashes.
for (const locale of Object.keys(meta)) {

View File

@@ -1,7 +1,6 @@
import "@material/mwc-button/mwc-button";
import { mdiCast, mdiCastConnected } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox";
import { ActionDetail } from "@material/mwc-list/mwc-list";
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
import { Auth, Connection } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -28,6 +27,7 @@ import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout";
import "../../../../src/components/ha-list-item";
@customElement("hc-cast")
class HcCast extends LitElement {
@@ -83,34 +83,37 @@ class HcCast extends LitElement {
`
: html`
<div class="section-header">PICK A VIEW</div>
<paper-listbox
attr-for-selected="data-path"
.selected=${this.castManager.status.lovelacePath || ""}
>
<mwc-list @action=${this._handlePickView} activatable>
${(
this.lovelaceViews ?? [
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
]
).map(
(view, idx) => html`
<paper-icon-item
@click=${this._handlePickView}
data-path=${view.path || idx}
(view, idx) =>
html`<ha-list-item
graphic="avatar"
.activated=${this.castManager.status?.lovelacePath ===
(view.path ?? idx)}
.selected=${this.castManager.status?.lovelacePath ===
(view.path ?? idx)}
>
${view.title || view.path || "Unnamed view"}
${view.icon
? html`
<ha-icon
.icon=${view.icon}
slot="item-icon"
slot="graphic"
></ha-icon>
`
: ""}
${view.title || view.path}
</paper-icon-item>
`
)}
</paper-listbox>
: html`<ha-svg-icon
slot="item-icon"
.path=${mdiViewDashboard}
></ha-svg-icon>`}</ha-list-item
> `
)}</mwc-list
>
`}
<div class="card-actions">
${this.castManager.status
? html`
@@ -182,8 +185,8 @@ class HcCast extends LitElement {
this.castManager.requestSession();
}
private async _handlePickView(ev: Event) {
const path = (ev.currentTarget as any).getAttribute("data-path");
private async _handlePickView(ev: CustomEvent<ActionDetail>) {
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);
}
@@ -246,25 +249,14 @@ class HcCast extends LitElement {
height: 18px;
}
paper-listbox {
padding-top: 0;
}
paper-listbox ha-icon {
ha-list-item ha-icon,
ha-list-item ha-svg-icon {
padding: 12px;
color: var(--secondary-text-color);
}
paper-icon-item {
cursor: pointer;
}
paper-icon-item[disabled] {
cursor: initial;
}
:host([hide-icons]) paper-icon-item {
--paper-item-icon-width: 0px;
:host([hide-icons]) ha-icon {
display: none;
}
.spacer {

View File

@@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-view";
import { HomeAssistant } from "../../../../src/types";
@@ -61,7 +62,12 @@ class HcLovelace extends LitElement {
const index = this._viewIndex;
if (index !== undefined) {
const dashboardTitle = this.lovelaceConfig.title || this.urlPath;
const title = getPanelTitleFromUrlPath(
this.hass,
this.urlPath || "lovelace"
);
const dashboardTitle = title || this.urlPath;
const viewTitle =
this.lovelaceConfig.views[index].title ||
@@ -80,10 +86,17 @@ class HcLovelace extends LitElement {
this.lovelaceConfig.views[index].background ||
this.lovelaceConfig.background;
if (configBackground) {
const backgroundStyle =
typeof configBackground === "string"
? configBackground
: configBackground?.image
? `center / cover no-repeat url('${configBackground.image}')`
: undefined;
if (backgroundStyle) {
this._huiView!.style.setProperty(
"--lovelace-background",
configBackground
backgroundStyle
);
} else {
this._huiView!.style.removeProperty("--lovelace-background");

View File

@@ -35,6 +35,7 @@ import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/lo
import { HassElement } from "../../../../src/state/hass-element";
import { castContext } from "../cast_context";
import "./hc-launch-screen";
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
strategy: {
@@ -359,7 +360,11 @@ export class HcMain extends HassElement {
}
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
castContext.setApplicationState(lovelaceConfig.title || "");
const title = getPanelTitleFromUrlPath(
this.hass!,
this._urlPath || "lovelace"
);
castContext.setApplicationState(title || "");
this._lovelaceConfig = lovelaceConfig;
}

View File

@@ -1,8 +1,9 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { until } from "lit/directives/until";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-card";
import "../../../src/components/ha-button";
import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
@@ -11,7 +12,6 @@ import {
demoConfigs,
selectedDemoConfig,
selectedDemoConfigIndex,
setDemoConfig,
} from "../configs/demo-configs";
@customElement("ha-demo-card")
@@ -64,9 +64,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
)}
</div>
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
<ha-button @click=${this._nextConfig} .disabled=${this._switching}>
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
</mwc-button>
</ha-button>
</div>
<div class="content">
<p class="small-hidden">
@@ -87,9 +87,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
</div>
<div class="actions small-hidden">
<a href="https://www.home-assistant.io" target="_blank">
<mwc-button>
<ha-button>
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
</mwc-button>
</ha-button>
</a>
</div>
</ha-card>
@@ -113,13 +113,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
private async _updateConfig(index: number) {
this._switching = true;
try {
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) {
alert("Failed to switch config :-(");
} finally {
this._switching = false;
}
fireEvent(this, "set-demo-config" as any, { index });
}
static get styles(): CSSResultGroup {
@@ -149,7 +143,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
height: 60px;
}
.picker mwc-button {
.picker ha-button {
margin-right: 8px;
}

View File

@@ -1,9 +1,12 @@
import type { LocalizeFunc } from "../../../src/common/translations/localize";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { selectedDemoConfig } from "../configs/demo-configs";
import {
selectedDemoConfig,
selectedDemoConfigIndex,
setDemoConfig,
} from "../configs/demo-configs";
import "../custom-cards/cast-demo-row";
import "../custom-cards/ha-demo-card";
import type { HADemoCard } from "../custom-cards/ha-demo-card";
export const mockLovelace = (
hass: MockHomeAssistant,
@@ -19,17 +22,22 @@ export const mockLovelace = (
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
};
customElements.whenDefined("hui-view").then(() => {
customElements.whenDefined("hui-root").then(() => {
// eslint-disable-next-line
const HUIView = customElements.get("hui-view");
// Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView!.prototype.createCardElement;
const HUIRoot = customElements.get("hui-root")!;
HUIView!.prototype.createCardElement = function (config) {
const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") {
(el as HADemoCard).lovelace = this.lovelace;
}
return el;
const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
HUIRoot.prototype.firstUpdated = function (changedProperties) {
oldFirstUpdated.call(this, changedProperties);
this.addEventListener("set-demo-config", async (ev) => {
const index = (ev as CustomEvent).detail.index;
try {
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) {
setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
alert("Failed to switch config :-(");
}
});
};
});

View File

@@ -1,7 +1,9 @@
import { load } from "js-yaml";
import { html, css, LitElement, PropertyValues } from "lit";
import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
import memoizeOne from "memoize-one";
import "../../../src/panels/lovelace/cards/hui-card";
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
import { HomeAssistant } from "../../../src/types";
export interface DemoCardConfig {
@@ -19,7 +21,12 @@ class DemoCard extends LitElement {
@state() private _size?: number;
@query("#card") private _card!: HTMLElement;
@query("hui-card", false) private _card?: HuiCard;
private _config = memoizeOne((config: string) => {
const c = (load(config) as any)[0];
return c;
});
render() {
return html`
@@ -30,63 +37,32 @@ class DemoCard extends LitElement {
: ""}
</h2>
<div class="root">
<div id="card"></div>
${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""}
<hui-card
.config=${this._config(this.config.config)}
.hass=${this.hass}
@card-updated=${this._cardUpdated}
></hui-card>
${this.showConfig
? html`<pre>${this.config.config.trim()}</pre>`
: nothing}
</div>
`;
}
updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("config")) {
const card = this._card;
while (card.lastChild) {
card.removeChild(card.lastChild);
}
const el = this._createCardElement((load(this.config.config) as any)[0]);
card.appendChild(el);
this._getSize(el);
}
if (changedProps.has("hass")) {
const card = this._card.lastChild;
if (card) {
(card as any).hass = this.hass;
}
}
private async _cardUpdated(ev) {
ev.stopPropagation();
this._updateSize();
}
async _getSize(el) {
await customElements.whenDefined(el.localName);
if (!("getCardSize" in el)) {
this._size = undefined;
return;
}
this._size = await el.getCardSize();
private async _updateSize() {
this._size = await this._card?.getCardSize();
}
_createCardElement(cardConfig) {
const element = createCardElement(cardConfig);
if (this.hass) {
element.hass = this.hass;
}
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
},
{ once: true }
);
return element;
}
_rebuildCard(cardElToReplace, config) {
const newCardEl = this._createCardElement(config);
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
protected update(
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
): void {
super.update(_changedProperties);
this._updateSize();
}
static styles = css`
@@ -101,7 +77,7 @@ class DemoCard extends LitElement {
font-size: 0.5em;
color: var(--primary-text-color);
}
#card {
hui-card {
max-width: 400px;
width: 100vw;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeNumeric extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeSeconds extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShortYear extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShort extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeDateTime extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -35,59 +35,57 @@ export class DemoDateTimeDate extends LitElement {
<div class="center">Month-Day-Year</div>
<div class="center">Year-Month-Day</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.DMY,
},
demoConfig
)}
</div>
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.MDY,
},
demoConfig
)}
</div>
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.YMD,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.DMY,
},
demoConfig
)}
</div>
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.MDY,
},
demoConfig
)}
</div>
<div class="center">
${formatDateNumeric(
date,
{
...defaultLocale,
language: key,
date_format: DateFormat.YMD,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeTimeSeconds extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeTimeWeekday extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -56,48 +56,46 @@ export class DemoDateTimeTime extends LitElement {
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
${Object.entries(translationMetadata.translations).map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
`
)}
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}

View File

@@ -25,15 +25,15 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.24.5",
"@braintree/sanitize-url": "7.0.2",
"@codemirror/autocomplete": "6.16.0",
"@codemirror/commands": "6.5.0",
"@codemirror/language": "6.10.1",
"@babel/runtime": "7.24.7",
"@braintree/sanitize-url": "7.0.3",
"@codemirror/autocomplete": "6.16.3",
"@codemirror/commands": "6.6.0",
"@codemirror/language": "6.10.2",
"@codemirror/legacy-modes": "6.4.0",
"@codemirror/search": "6.5.6",
"@codemirror/state": "6.4.1",
"@codemirror/view": "6.26.3",
"@codemirror/view": "6.28.2",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.12.5",
"@formatjs/intl-displaynames": "6.6.8",
@@ -43,17 +43,17 @@
"@formatjs/intl-numberformat": "8.10.3",
"@formatjs/intl-pluralrules": "5.2.14",
"@formatjs/intl-relativetimeformat": "11.2.14",
"@fullcalendar/core": "6.1.13",
"@fullcalendar/daygrid": "6.1.13",
"@fullcalendar/interaction": "6.1.13",
"@fullcalendar/list": "6.1.13",
"@fullcalendar/luxon3": "6.1.13",
"@fullcalendar/timegrid": "6.1.13",
"@fullcalendar/core": "6.1.11",
"@fullcalendar/daygrid": "6.1.11",
"@fullcalendar/interaction": "6.1.11",
"@fullcalendar/list": "6.1.11",
"@fullcalendar/luxon3": "6.1.11",
"@fullcalendar/timegrid": "6.1.11",
"@lezer/highlight": "1.2.0",
"@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.7",
"@lit-labs/observers": "2.0.2",
"@lit-labs/virtualizer": "2.0.12",
"@lit-labs/virtualizer": "2.0.13",
"@lrnwebcomponents/simple-tooltip": "8.0.2",
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
@@ -80,7 +80,7 @@
"@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "1.4.1",
"@material/web": "1.5.0",
"@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47",
"@polymer/paper-item": "3.0.1",
@@ -88,8 +88,8 @@
"@polymer/paper-tabs": "3.1.0",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.3.13",
"@vaadin/vaadin-themable-mixin": "24.3.13",
"@vaadin/combo-box": "24.4.0",
"@vaadin/vaadin-themable-mixin": "24.4.0",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -110,7 +110,7 @@
"fuse.js": "7.0.0",
"google-timezones-json": "1.2.0",
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
"home-assistant-js-websocket": "9.3.0",
"home-assistant-js-websocket": "9.4.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.14",
"js-yaml": "4.1.0",
@@ -133,7 +133,7 @@
"tinykeys": "2.1.0",
"tsparticles-engine": "2.12.0",
"tsparticles-preset-links": "2.12.0",
"ua-parser-js": "1.0.37",
"ua-parser-js": "1.0.38",
"unfetch": "5.0.0",
"vis-data": "7.1.9",
"vis-network": "9.1.9",
@@ -149,26 +149,26 @@
"xss": "1.0.15"
},
"devDependencies": {
"@babel/core": "7.24.5",
"@babel/core": "7.24.7",
"@babel/helper-define-polyfill-provider": "0.6.2",
"@babel/plugin-proposal-decorators": "7.24.1",
"@babel/plugin-transform-runtime": "7.24.3",
"@babel/preset-env": "7.24.5",
"@babel/preset-typescript": "7.24.1",
"@bundle-stats/plugin-webpack-filter": "4.12.2",
"@babel/plugin-proposal-decorators": "7.24.7",
"@babel/plugin-transform-runtime": "7.24.7",
"@babel/preset-env": "7.24.7",
"@babel/preset-typescript": "7.24.7",
"@bundle-stats/plugin-webpack-filter": "4.13.2",
"@koa/cors": "5.0.0",
"@lokalise/node-api": "12.5.0",
"@octokit/auth-oauth-device": "7.1.1",
"@octokit/plugin-retry": "7.1.1",
"@octokit/rest": "20.1.1",
"@octokit/rest": "21.0.0",
"@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "25.0.8",
"@rollup/plugin-commonjs": "26.0.1",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-replace": "5.0.5",
"@rollup/plugin-replace": "5.0.7",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.14",
"@types/chromecast-caf-receiver": "6.0.15",
"@types/chromecast-caf-sender": "1.0.10",
"@types/color-name": "1.1.4",
"@types/glob": "8.1.0",
@@ -185,8 +185,8 @@
"@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "7.10.0",
"@typescript-eslint/parser": "7.10.0",
"@typescript-eslint/eslint-plugin": "7.13.1",
"@typescript-eslint/parser": "7.13.1",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
@@ -198,15 +198,14 @@
"eslint-config-airbnb-typescript": "18.0.0",
"eslint-config-prettier": "9.1.0",
"eslint-import-resolver-webpack": "0.13.8",
"eslint-plugin-disable": "2.0.3",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-lit": "1.13.0",
"eslint-plugin-lit": "1.14.0",
"eslint-plugin-lit-a11y": "4.1.2",
"eslint-plugin-unused-imports": "3.2.0",
"eslint-plugin-unused-imports": "4.0.0",
"eslint-plugin-wc": "2.1.0",
"fancy-log": "2.0.0",
"fs-extra": "11.2.0",
"glob": "10.4.1",
"glob": "10.4.2",
"gulp": "5.0.0",
"gulp-json-transform": "0.5.0",
"gulp-rename": "2.0.0",
@@ -215,7 +214,7 @@
"husky": "9.0.11",
"instant-mocha": "1.5.2",
"jszip": "3.10.1",
"lint-staged": "15.2.4",
"lint-staged": "15.2.7",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.5.0",
@@ -225,7 +224,7 @@
"object-hash": "3.0.0",
"open": "10.1.0",
"pinst": "3.0.0",
"prettier": "3.2.5",
"prettier": "3.3.2",
"rollup": "2.79.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
@@ -234,18 +233,18 @@
"sinon": "18.0.0",
"source-map-url": "0.4.1",
"systemjs": "6.15.1",
"tar": "7.1.0",
"tar": "7.4.0",
"terser-webpack-plugin": "5.3.10",
"transform-async-modules-webpack-plugin": "1.1.1",
"ts-lit-plugin": "2.0.2",
"typescript": "5.4.5",
"webpack": "5.91.0",
"webpack": "5.92.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.4",
"webpack-manifest-plugin": "5.0.0",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "6.0.1",
"workbox-build": "7.1.0"
"workbox-build": "7.1.1"
},
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": {
@@ -254,8 +253,9 @@
"lit": "2.8.0",
"clean-css": "5.3.3",
"@lit/reactive-element": "1.6.3",
"@fullcalendar/daygrid": "6.1.11",
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
},
"packageManager": "yarn@4.2.2"
"packageManager": "yarn@4.3.1"
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20240501.0"
version = "20240610.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@@ -31,6 +31,7 @@ import {
mdiFormatListBulleted,
mdiFormatListCheckbox,
mdiFormTextbox,
mdiForumOutline,
mdiGauge,
mdiGoogleAssistant,
mdiGoogleCirclesCommunities,
@@ -98,7 +99,7 @@ export const FIXED_DOMAIN_ICONS = {
calendar: mdiCalendar,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiMicrophoneMessage,
conversation: mdiForumOutline,
counter: mdiCounter,
date: mdiCalendar,
datetime: mdiCalendarClock,
@@ -235,6 +236,8 @@ export const SENSOR_ENTITIES = [
"weather",
];
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
/** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements

View File

@@ -1,8 +1,6 @@
import { getWeekStartByLocale } from "weekstart";
import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
import "../../resources/intl-polyfill";
export const weekdays = [
"sunday",
"monday",

View File

@@ -1,7 +1,6 @@
import { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { DateFormat, FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { resolveTimeZone } from "./resolve-time-zone";
// Tuesday, August 10

View File

@@ -1,7 +1,6 @@
import { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { formatDateNumeric } from "./format_date";
import { formatTime } from "./format_time";
import { resolveTimeZone } from "./resolve-time-zone";

View File

@@ -1,6 +1,5 @@
import { HaDurationData } from "../../components/ha-duration-input";
import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);

View File

@@ -1,7 +1,6 @@
import { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { resolveTimeZone } from "./resolve-time-zone";
import { useAmPm } from "./use_am_pm";

View File

@@ -1,5 +1,4 @@
import memoizeOne from "memoize-one";
import "../../resources/intl-polyfill";
export const localizeWeekdays = memoizeOne(
(language: string, short: boolean): string[] => {

View File

@@ -1,6 +1,5 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { selectUnit } from "../util/select-unit";
const formatRelTimeMem = memoizeOne(

View File

@@ -108,6 +108,8 @@ export const storage =
subscribe?: boolean;
state?: boolean;
stateOptions?: InternalPropertyDeclaration;
serializer?: (value: any) => any;
deserializer?: (value: any) => any;
}): any =>
(clsElement: ClassElement) => {
const storageName = options.storage || "localStorage";
@@ -141,7 +143,9 @@ export const storage =
const getValue = (): any =>
storageInstance.hasKey(storageKey!)
? storageInstance.getValue(storageKey!)
? options.deserializer
? options.deserializer(storageInstance.getValue(storageKey!))
: storageInstance.getValue(storageKey!)
: initVal;
const setValue = (el: ReactiveElement, value: any) => {
@@ -149,7 +153,10 @@ export const storage =
if (options.state) {
oldValue = getValue();
}
storageInstance.setValue(storageKey!, value);
storageInstance.setValue(
storageKey!,
options.serializer ? options.serializer(value) : value
);
if (options.state) {
el.requestUpdate(clsElement.key, oldValue);
}

View File

@@ -0,0 +1 @@
export const preventDefault = (ev) => ev.preventDefault();

View File

@@ -12,11 +12,10 @@ export const formatLanguageCode = (
}
};
const formatLanguageCodeMem = memoizeOne((locale: FrontendLocaleData) =>
Intl && "DisplayNames" in Intl
? new Intl.DisplayNames(locale.language, {
type: "language",
fallback: "code",
})
: undefined
const formatLanguageCodeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DisplayNames(locale.language, {
type: "language",
fallback: "code",
})
);

View File

@@ -11,6 +11,7 @@ declare global {
export interface NavigateOptions {
replace?: boolean;
data?: any;
}
export const navigate = (path: string, options?: NavigateOptions) => {
@@ -24,7 +25,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
if (__DEMO__) {
if (replace) {
mainWindow.history.replaceState(
mainWindow.history.state?.root ? { root: true } : null,
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
"",
`${mainWindow.location.pathname}#${path}`
);
@@ -33,12 +34,12 @@ export const navigate = (path: string, options?: NavigateOptions) => {
}
} else if (replace) {
mainWindow.history.replaceState(
mainWindow.history.state?.root ? { root: true } : null,
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
"",
path
);
} else {
mainWindow.history.pushState(null, "", path);
mainWindow.history.pushState(options?.data ?? null, "", path);
}
fireEvent(mainWindow, "location-changed", {
replace,

View File

@@ -63,30 +63,18 @@ export const formatNumber = (
if (
localeOptions?.number_format !== NumberFormat.none &&
!Number.isNaN(Number(num)) &&
Intl
!Number.isNaN(Number(num))
) {
try {
return new Intl.NumberFormat(
locale,
getDefaultFormatOptions(num, options)
).format(Number(num));
} catch (err: any) {
// Don't fail when using "TEST" language
// eslint-disable-next-line no-console
console.error(err);
return new Intl.NumberFormat(
undefined,
getDefaultFormatOptions(num, options)
).format(Number(num));
}
return new Intl.NumberFormat(
locale,
getDefaultFormatOptions(num, options)
).format(Number(num));
}
if (
!Number.isNaN(Number(num)) &&
num !== "" &&
localeOptions?.number_format === NumberFormat.none &&
Intl
localeOptions?.number_format === NumberFormat.none
) {
// If NumberFormat is none, use en-US format without grouping.
return new Intl.NumberFormat(

View File

@@ -1,5 +1,4 @@
import memoizeOne from "memoize-one";
import "../../resources/intl-polyfill";
import { FrontendLocaleData } from "../../data/translation";
export const formatListWithAnds = (

View File

@@ -1,6 +1,6 @@
import IntlMessageFormat from "intl-messageformat";
import type { IntlMessageFormat } from "intl-messageformat";
import type { HTMLTemplateResult } from "lit";
import { polyfillLocaleData } from "../../resources/locale-data-polyfill";
import { polyfillLocaleData } from "../../resources/polyfills/locale-data-polyfill";
import { Resources, TranslationDict } from "../../types";
import { fireEvent } from "../dom/fire_event";
@@ -89,9 +89,8 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
resources: Resources,
formats?: FormatsType
): Promise<LocalizeFunc<Keys>> => {
await import("../../resources/intl-polyfill").then(() =>
polyfillLocaleData(language)
);
const { IntlMessageFormat } = await import("intl-messageformat");
await polyfillLocaleData(language);
// Every time any of the parameters change, invalidate the strings cache.
cache._localizationCache = {};

View File

@@ -730,6 +730,28 @@ export class HaDataTable extends LitElement {
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
};
public expandAllGroups() {
this._collapsedGroups = [];
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
}
public collapseAllGroups() {
if (
!this.groupColumn ||
!this.data.some((item) => item[this.groupColumn!])
) {
return;
}
const grouped = groupBy(this.data, (item) => item[this.groupColumn!]);
if (grouped.undefined) {
// undefined is a reserved group name
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
delete grouped.undefined;
}
this._collapsedGroups = Object.keys(grouped);
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
}
static get styles(): CSSResultGroup {
return [
haStyleScrollbar,

View File

@@ -47,6 +47,8 @@ export class HaCodeEditor extends ReactiveElement {
@property({ type: Boolean }) public readOnly = false;
@property({ type: Boolean }) public linewrap = false;
@property({ type: Boolean, attribute: "autocomplete-entities" })
public autocompleteEntities = false;
@@ -134,6 +136,13 @@ export class HaCodeEditor extends ReactiveElement {
),
});
}
if (changedProps.has("linewrap")) {
transactions.push({
effects: this._loadedCodeMirror!.linewrapCompartment!.reconfigure(
this.linewrap ? this._loadedCodeMirror!.EditorView.lineWrapping : []
),
});
}
if (changedProps.has("_value") && this._value !== this.value) {
transactions.push({
changes: {
@@ -181,6 +190,9 @@ export class HaCodeEditor extends ReactiveElement {
this._loadedCodeMirror.readonlyCompartment.of(
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
),
this._loadedCodeMirror.linewrapCompartment.of(
this.linewrap ? this._loadedCodeMirror.EditorView.lineWrapping : []
),
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
];

View File

@@ -4,7 +4,6 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import "../resources/intl-polyfill";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
@@ -282,14 +281,10 @@ export class HaCountryPicker extends LitElement {
private _getOptions = memoizeOne(
(language?: string, countries?: string[]) => {
let options: { label: string; value: string }[] = [];
const countryDisplayNames =
Intl && "DisplayNames" in Intl
? new Intl.DisplayNames(language, {
type: "region",
fallback: "code",
})
: undefined;
const countryDisplayNames = new Intl.DisplayNames(language, {
type: "region",
fallback: "code",
});
if (countries) {
options = countries.map((country) => ({
value: country,

View File

@@ -4,7 +4,6 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import "../resources/intl-polyfill";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
@@ -170,12 +169,9 @@ const CURRENCIES = [
];
const curSymbol = (currency: string, locale?: string) =>
Intl && "NumberFormat" in Intl
? new Intl.NumberFormat(locale, { style: "currency", currency })
.formatToParts(1)
.find((x) => x.type === "currency")?.value
: currency;
new Intl.NumberFormat(locale, { style: "currency", currency })
.formatToParts(1)
.find((x) => x.type === "currency")?.value;
@customElement("ha-currency-picker")
export class HaCurrencyPicker extends LitElement {
@property() public language = "en";
@@ -189,13 +185,10 @@ export class HaCurrencyPicker extends LitElement {
@property({ type: Boolean, reflect: true }) public disabled = false;
private _getOptions = memoizeOne((language?: string) => {
const currencyDisplayNames =
Intl && "DisplayNames" in Intl
? new Intl.DisplayNames(language, {
type: "currency",
fallback: "code",
})
: undefined;
const currencyDisplayNames = new Intl.DisplayNames(language, {
type: "currency",
fallback: "code",
});
const options = CURRENCIES.map((currency) => ({
value: currency,
label: `${

View File

@@ -21,6 +21,8 @@ export class HaExpansionPanel extends LitElement {
@property({ type: Boolean, reflect: true }) leftChevron = false;
@property({ type: Boolean, reflect: true }) noCollapse = false;
@property() header?: string;
@property() secondary?: string;
@@ -34,16 +36,17 @@ export class HaExpansionPanel extends LitElement {
<div class="top ${classMap({ expanded: this.expanded })}">
<div
id="summary"
class=${classMap({ noCollapse: this.noCollapse })}
@click=${this._toggleContainer}
@keydown=${this._toggleContainer}
@focus=${this._focusChanged}
@blur=${this._focusChanged}
role="button"
tabindex="0"
tabindex=${this.noCollapse ? -1 : 0}
aria-expanded=${this.expanded}
aria-controls="sect1"
>
${this.leftChevron
${this.leftChevron && !this.noCollapse
? html`
<ha-svg-icon
.path=${mdiChevronDown}
@@ -57,7 +60,7 @@ export class HaExpansionPanel extends LitElement {
<slot class="secondary" name="secondary">${this.secondary}</slot>
</div>
</slot>
${!this.leftChevron
${!this.leftChevron && !this.noCollapse
? html`
<ha-svg-icon
.path=${mdiChevronDown}
@@ -106,6 +109,9 @@ export class HaExpansionPanel extends LitElement {
return;
}
ev.preventDefault();
if (this.noCollapse) {
return;
}
const newExpanded = !this.expanded;
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
this._container.style.overflow = "hidden";
@@ -130,6 +136,9 @@ export class HaExpansionPanel extends LitElement {
}
private _focusChanged(ev) {
if (this.noCollapse) {
return;
}
this.shadowRoot!.querySelector(".top")!.classList.toggle(
"focused",
ev.type === "focus"
@@ -191,6 +200,9 @@ export class HaExpansionPanel extends LitElement {
font-weight: 500;
outline: none;
}
#summary.noCollapse {
cursor: default;
}
.summary-icon.expanded {
transform: rotate(180deg);

View File

@@ -1,7 +1,14 @@
import { SelectedDetail } from "@material/mwc-list";
import "@material/mwc-menu/mwc-menu-surface";
import { mdiFilterVariantRemove } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { Blueprints, fetchBlueprints } from "../data/blueprint";
@@ -25,6 +32,16 @@ export class HaFilterBlueprints extends LitElement {
@state() private _blueprints?: Blueprints;
public willUpdate(properties: PropertyValues) {
super.willUpdate(properties);
if (!this.hasUpdated) {
if (this.value?.length) {
this._findRelated();
}
}
}
protected render() {
return html`
<ha-expansion-panel
@@ -96,7 +113,6 @@ export class HaFilterBlueprints extends LitElement {
ev: CustomEvent<SelectedDetail<Set<number>>>
) {
const blueprints = this._blueprints!;
const relatedPromises: Promise<RelatedResult>[] = [];
if (!ev.detail.index.size) {
fireEvent(this, "data-table-filter-changed", {
@@ -112,13 +128,33 @@ export class HaFilterBlueprints extends LitElement {
for (const index of ev.detail.index) {
const blueprintId = Object.keys(blueprints)[index];
value.push(blueprintId);
}
this.value = value;
this._findRelated();
}
private async _findRelated() {
if (!this.value?.length) {
fireEvent(this, "data-table-filter-changed", {
value: [],
items: undefined,
});
this.value = [];
return;
}
const relatedPromises: Promise<RelatedResult>[] = [];
for (const blueprintId of this.value) {
if (this.type) {
relatedPromises.push(
findRelated(this.hass, `${this.type}_blueprint`, blueprintId)
);
}
}
this.value = value;
const results = await Promise.all(relatedPromises);
const items: Set<string> = new Set();
for (const result of results) {
@@ -128,7 +164,7 @@ export class HaFilterBlueprints extends LitElement {
}
fireEvent(this, "data-table-filter-changed", {
value,
value: this.value,
items: this.type ? items : undefined,
});
}

View File

@@ -41,6 +41,9 @@ export class HaFilterDevices extends LitElement {
if (!this.hasUpdated) {
loadVirtualizer();
if (this.value?.length) {
this._findRelated();
}
}
}

View File

@@ -89,13 +89,18 @@ export class HaFilterDomains extends LitElement {
});
return Array.from(domains.values())
.map((domain) => ({
domain,
name: domainToName(this.hass.localize, domain),
}))
.filter(
(entry) =>
!filter ||
entry.toLowerCase().includes(filter) ||
domainToName(this.hass.localize, entry).toLowerCase().includes(filter)
entry.domain.toLowerCase().includes(filter) ||
entry.name.toLowerCase().includes(filter)
)
.sort((a, b) => stringCompare(a, b, this.hass.locale.language));
.sort((a, b) => stringCompare(a.name, b.name, this.hass.locale.language))
.map((entry) => entry.domain);
});
protected updated(changed) {

View File

@@ -42,6 +42,9 @@ export class HaFilterEntities extends LitElement {
if (!this.hasUpdated) {
loadVirtualizer();
if (this.value?.length) {
this._findRelated();
}
}
}
@@ -186,15 +189,12 @@ export class HaFilterEntities extends LitElement {
return;
}
const value: string[] = [];
for (const entityId of this.value) {
value.push(entityId);
if (this.type) {
relatedPromises.push(findRelated(this.hass, "entity", entityId));
}
}
this.value = value;
const results = await Promise.all(relatedPromises);
const items: Set<string> = new Set();
for (const result of results) {
@@ -204,7 +204,7 @@ export class HaFilterEntities extends LitElement {
}
fireEvent(this, "data-table-filter-changed", {
value,
value: this.value,
items: this.type ? items : undefined,
});
}

View File

@@ -1,7 +1,14 @@
import "@material/mwc-menu/mwc-menu-surface";
import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
@@ -42,6 +49,16 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
@state() private _floors?: FloorRegistryEntry[];
public willUpdate(properties: PropertyValues) {
super.willUpdate(properties);
if (!this.hasUpdated) {
if (this.value?.floors?.length || this.value?.areas?.length) {
this._findRelated();
}
}
}
protected render() {
const areas = this._areas(this.hass.areas, this._floors);
@@ -190,6 +207,10 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
}
}
protected firstUpdated() {
this._findRelated();
}
private _expandedWillChange(ev) {
this._shouldRender = ev.detail.expanded;
}

View File

@@ -2,7 +2,7 @@ import type { Selector } from "../../data/selector";
import type { HaFormSchema } from "./types";
export const computeInitialHaFormData = (
schema: HaFormSchema[]
schema: HaFormSchema[] | readonly HaFormSchema[]
): Record<string, any> => {
const data = {};
schema.forEach((field) => {
@@ -36,6 +36,8 @@ export const computeInitialHaFormData = (
minutes: 0,
seconds: 0,
};
} else if (field.type === "expandable") {
data[field.name] = computeInitialHaFormData(field.schema);
} else if ("selector" in field) {
const selector: Selector = field.selector;

View File

@@ -0,0 +1,233 @@
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "./ha-icon-button";
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
import { mdiRestore } from "@mdi/js";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import { HomeAssistant } from "../types";
type GridSizeValue = {
rows?: number;
columns?: number;
};
@customElement("ha-grid-size-picker")
export class HaGridSizeEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public value?: GridSizeValue;
@property({ attribute: false }) public rows = 6;
@property({ attribute: false }) public columns = 4;
@property({ attribute: false }) public rowMin?: number;
@property({ attribute: false }) public rowMax?: number;
@property({ attribute: false }) public columnMin?: number;
@property({ attribute: false }) public columnMax?: number;
@property({ attribute: false }) public isDefault?: boolean;
@state() public _localValue?: GridSizeValue = undefined;
protected willUpdate(changedProperties) {
if (changedProperties.has("value")) {
this._localValue = this.value;
}
}
protected render() {
return html`
<div class="grid">
<ha-grid-layout-slider
aria-label=${this.hass.localize(
"ui.components.grid-size-picker.columns"
)}
id="columns"
.min=${this.columnMin ?? 1}
.max=${this.columnMax ?? this.columns}
.range=${this.columns}
.value=${this.value?.columns}
@value-changed=${this._valueChanged}
@slider-moved=${this._sliderMoved}
></ha-grid-layout-slider>
<ha-grid-layout-slider
aria-label=${this.hass.localize(
"ui.components.grid-size-picker.rows"
)}
id="rows"
.min=${this.rowMin ?? 1}
.max=${this.rowMax ?? this.rows}
.range=${this.rows}
vertical
.value=${this.value?.rows}
@value-changed=${this._valueChanged}
@slider-moved=${this._sliderMoved}
></ha-grid-layout-slider>
${!this.isDefault
? html`
<ha-icon-button
@click=${this._reset}
class="reset"
.path=${mdiRestore}
label=${this.hass.localize(
"ui.components.grid-size-picker.reset_default"
)}
title=${this.hass.localize(
"ui.components.grid-size-picker.reset_default"
)}
>
</ha-icon-button>
`
: nothing}
<div
class="preview"
style=${styleMap({
"--total-rows": this.rows,
"--total-columns": this.columns,
"--rows": this._localValue?.rows,
"--columns": this._localValue?.columns,
})}
>
<div>
${Array(this.rows * this.columns)
.fill(0)
.map((_, index) => {
const row = Math.floor(index / this.columns) + 1;
const column = (index % this.columns) + 1;
const disabled =
(this.rowMin !== undefined && row < this.rowMin) ||
(this.rowMax !== undefined && row > this.rowMax) ||
(this.columnMin !== undefined && column < this.columnMin) ||
(this.columnMax !== undefined && column > this.columnMax);
return html`
<div
class="cell"
data-row=${row}
data-column=${column}
?disabled=${disabled}
@click=${this._cellClick}
></div>
`;
})}
</div>
<div class="selected">
<div class="cell"></div>
</div>
</div>
</div>
`;
}
_cellClick(ev) {
const cell = ev.currentTarget as HTMLElement;
if (cell.getAttribute("disabled") !== null) return;
const rows = Number(cell.getAttribute("data-row"));
const columns = Number(cell.getAttribute("data-column"));
fireEvent(this, "value-changed", {
value: { rows, columns },
});
}
private _valueChanged(ev) {
ev.stopPropagation();
const key = ev.currentTarget.id;
const newValue = {
...this.value,
[key]: ev.detail.value,
};
fireEvent(this, "value-changed", { value: newValue });
}
private _reset(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: {
rows: undefined,
columns: undefined,
},
});
}
private _sliderMoved(ev) {
ev.stopPropagation();
const key = ev.currentTarget.id;
const value = ev.detail.value;
if (value === undefined) return;
this._localValue = {
...this.value,
[key]: ev.detail.value,
};
}
static styles = [
css`
.grid {
display: grid;
grid-template-areas:
"reset column-slider"
"row-slider preview";
grid-template-rows: auto 1fr;
grid-template-columns: auto 1fr;
gap: 8px;
}
#columns {
grid-area: column-slider;
}
#rows {
grid-area: row-slider;
}
.reset {
grid-area: reset;
}
.preview {
position: relative;
grid-area: preview;
aspect-ratio: 1 / 1;
}
.preview > div {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: grid;
grid-template-columns: repeat(var(--total-columns), 1fr);
grid-template-rows: repeat(var(--total-rows), 1fr);
gap: 4px;
}
.preview .cell {
background-color: var(--disabled-color);
grid-column: span 1;
grid-row: span 1;
border-radius: 4px;
opacity: 0.2;
cursor: pointer;
}
.preview .cell[disabled] {
opacity: 0.05;
cursor: initial;
}
.selected {
pointer-events: none;
}
.selected .cell {
background-color: var(--primary-color);
grid-column: 1 / span var(--columns, 0);
grid-row: 1 / span var(--rows, 0);
opacity: 0.5;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-grid-size-picker": HaGridSizeEditor;
}
}

View File

@@ -1,4 +1,3 @@
import "@material/mwc-list/mwc-list-item";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import {
ComboBoxDataProviderCallback,
@@ -11,6 +10,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { customIcons } from "../data/custom_icons";
import { HomeAssistant, ValueChangedEvent } from "../types";
import "./ha-combo-box";
import "./ha-list-item";
import "./ha-icon";
type IconItem = {
@@ -67,10 +67,10 @@ const loadCustomIconItems = async (iconsetPrefix: string) => {
};
const rowRenderer: ComboBoxLitRenderer<IconItem | RankedIcon> = (item) =>
html`<mwc-list-item graphic="avatar">
html`<ha-list-item graphic="avatar">
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
${item.icon}
</mwc-list-item>`;
</ha-list-item>`;
@customElement("ha-icon-picker")
export class HaIconPicker extends LitElement {
@@ -198,8 +198,7 @@ export class HaIconPicker extends LitElement {
static get styles() {
return css`
ha-icon,
ha-svg-icon {
*[slot="icon"] {
color: var(--primary-text-color);
position: relative;
bottom: 2px;

View File

@@ -6,7 +6,6 @@ import { stopPropagation } from "../common/dom/stop_propagation";
import { formatLanguageCode } from "../common/language/format_language";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { FrontendLocaleData } from "../data/translation";
import "../resources/intl-polyfill";
import { translationMetadata } from "../resources/translations-metadata";
import { HomeAssistant } from "../types";
import "./ha-list-item";

View File

@@ -100,6 +100,7 @@ export class HaListItem extends ListItemBase {
span.material-icons:first-of-type,
span.material-icons:last-of-type {
direction: rtl !important;
--direction: rtl;
}
`
: css``,

View File

@@ -2,6 +2,7 @@ import { mdiImagePlus } from "@mdi/js";
import { LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { haStyle } from "../resources/styles";
import { createImage, generateImageThumbnailUrl } from "../data/image_upload";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import {
@@ -31,6 +32,8 @@ export class HaPictureUpload extends LitElement {
@property({ attribute: false }) public cropOptions?: CropOptions;
@property({ type: Boolean }) public original = false;
@property({ type: Number }) public size = 512;
@state() private _uploading = false;
@@ -60,13 +63,15 @@ export class HaPictureUpload extends LitElement {
alt=${this.currentImageAltText ||
this.hass.localize("ui.components.picture-upload.current_image_alt")}
/>
<ha-button
@click=${this._handleChangeClick}
.label=${this.hass.localize(
"ui.components.picture-upload.change_picture"
)}
>
</ha-button>
<div>
<ha-button
@click=${this._handleChangeClick}
.label=${this.hass.localize(
"ui.components.picture-upload.change_picture"
)}
>
</ha-button>
</div>
</div>
</div>`;
}
@@ -122,7 +127,11 @@ export class HaPictureUpload extends LitElement {
this._uploading = true;
try {
const media = await createImage(this.hass, file);
this.value = generateImageThumbnailUrl(media.id, this.size);
this.value = generateImageThumbnailUrl(
media.id,
this.size,
this.original
);
fireEvent(this, "change");
} catch (err: any) {
showAlertDialog(this, {
@@ -134,32 +143,35 @@ export class HaPictureUpload extends LitElement {
}
static get styles() {
return css`
:host {
display: block;
height: 240px;
}
ha-file-upload {
height: 100%;
}
.center-vertical {
display: flex;
align-items: center;
height: 100%;
}
.value {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
img {
max-width: 100%;
max-height: 200px;
margin-bottom: 4px;
border-radius: var(--file-upload-image-border-radius);
}
`;
return [
haStyle,
css`
:host {
display: block;
height: 240px;
}
ha-file-upload {
height: 100%;
}
.center-vertical {
display: flex;
align-items: center;
height: 100%;
}
.value {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
img {
max-width: 100%;
max-height: 200px;
margin-bottom: 4px;
border-radius: var(--file-upload-image-border-radius);
}
`,
];
}
}

View File

@@ -0,0 +1,145 @@
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { ImageSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "../ha-textarea";
import "../ha-textfield";
import "../ha-picture-upload";
import "../ha-radio";
import type { HaPictureUpload } from "../ha-picture-upload";
import { URL_PREFIX } from "../../data/image_upload";
@customElement("ha-selector-image")
export class HaImageSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public value?: any;
@property() public name?: string;
@property() public label?: string;
@property() public placeholder?: string;
@property() public helper?: string;
@property({ attribute: false }) public selector!: ImageSelector;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
@state() private showUpload = false;
protected firstUpdated(changedProps): void {
super.firstUpdated(changedProps);
if (!this.value || this.value.startsWith(URL_PREFIX)) {
this.showUpload = true;
}
}
protected render() {
return html`
<div>
<label>
${this.hass.localize("ui.components.selectors.image.select_image")}
<ha-formfield
.label=${this.hass.localize("ui.components.selectors.image.upload")}
>
<ha-radio
name="mode"
value="upload"
.checked=${this.showUpload}
@change=${this._radioGroupPicked}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass.localize("ui.components.selectors.image.url")}
>
<ha-radio
name="mode"
value="url"
.checked=${!this.showUpload}
@change=${this._radioGroupPicked}
></ha-radio>
</ha-formfield>
</label>
${!this.showUpload
? html`
<ha-textfield
.name=${this.name}
.value=${this.value || ""}
.placeholder=${this.placeholder || ""}
.helper=${this.helper}
helperPersistent
.disabled=${this.disabled}
@input=${this._handleChange}
.label=${this.label || ""}
.required=${this.required}
></ha-textfield>
`
: html`
<ha-picture-upload
.hass=${this.hass}
.value=${this.value?.startsWith(URL_PREFIX) ? this.value : null}
.original=${this.selector.image?.original}
.cropOptions=${this.selector.image?.crop}
@change=${this._pictureChanged}
></ha-picture-upload>
`}
</div>
`;
}
private _radioGroupPicked(ev): void {
this.showUpload = ev.target.value === "upload";
}
private _pictureChanged(ev) {
const value = (ev.target as HaPictureUpload).value;
fireEvent(this, "value-changed", { value: value ?? undefined });
}
private _handleChange(ev) {
let value = ev.target.value;
if (this.value === value) {
return;
}
if (value === "" && !this.required) {
value = undefined;
}
fireEvent(this, "value-changed", { value });
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
position: relative;
}
div {
display: flex;
flex-direction: column;
}
label {
display: flex;
flex-direction: column;
}
ha-textarea,
ha-textfield {
width: 100%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-image": HaImageSelector;
}
}

View File

@@ -64,6 +64,12 @@ const SELECTOR_SCHEMAS = {
selector: { boolean: {} },
},
] as const,
floor: [
{
name: "multiple",
selector: { boolean: {} },
},
] as const,
icon: [] as const,
location: [] as const,
media: [] as const,

View File

@@ -32,6 +32,7 @@ export class HaTemplateSelector extends LitElement {
autocomplete-icons
@value-changed=${this._handleChange}
dir="ltr"
linewrap
></ha-code-editor>
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`

View File

@@ -32,6 +32,7 @@ const LOAD_ELEMENTS = {
file: () => import("./ha-selector-file"),
floor: () => import("./ha-selector-floor"),
label: () => import("./ha-selector-label"),
image: () => import("./ha-selector-image"),
language: () => import("./ha-selector-language"),
navigation: () => import("./ha-selector-navigation"),
number: () => import("./ha-selector-number"),

View File

@@ -327,6 +327,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
for (const entityId of Object.keys(this.hass.states)) {
if (
entityId.startsWith("update.") &&
!this.hass.entities[entityId]?.hidden &&
updateCanInstall(this.hass.states[entityId] as UpdateEntity)
) {
updateCount++;

View File

@@ -206,6 +206,7 @@ export class HaTextField extends TextFieldBase {
.mdc-floating-label,
.mdc-text-field__input[type="number"] {
direction: rtl;
--direction: rtl;
}
`
: css``,

View File

@@ -48,7 +48,7 @@ class HaEntityMarker extends LitElement {
width: 48px;
height: 48px;
font-size: var(--ha-marker-font-size, 1.5em);
border-radius: 50%;
border-radius: var(--ha-marker-border-radius, 50%);
border: 1px solid var(--ha-marker-color, var(--primary-color));
color: var(--primary-text-color);
background-color: var(--card-background-color);

View File

@@ -10,8 +10,10 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import { supportsFeature } from "../common/entity/supports-feature";
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
import { HomeAssistant } from "../types";
import { getExtendedEntityRegistryEntry } from "./entity_registry";
export const FORMAT_TEXT = "text";
export const FORMAT_NUMBER = "number";
@@ -103,3 +105,50 @@ export const supportedAlarmModes = (stateObj: AlarmControlPanelEntity) =>
const feature = ALARM_MODES[mode].feature;
return !feature || supportsFeature(stateObj, feature);
});
export const setProtectedAlarmControlPanelMode = async (
element: HTMLElement,
hass: HomeAssistant,
stateObj: AlarmControlPanelEntity,
mode: AlarmMode
) => {
const { service } = ALARM_MODES[mode];
let code: string | undefined;
if (
(mode !== "disarmed" &&
stateObj.attributes.code_arm_required &&
stateObj.attributes.code_format) ||
(mode === "disarmed" && stateObj.attributes.code_format)
) {
const entry = await getExtendedEntityRegistryEntry(
hass,
stateObj.entity_id
).catch(() => undefined);
const defaultCode = entry?.options?.alarm_control_panel?.default_code;
if (!defaultCode) {
const disarm = mode === "disarmed";
const response = await showEnterCodeDialog(element, {
codeFormat: stateObj.attributes.code_format,
title: hass.localize(
`ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}`
),
submitText: hass.localize(
`ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}`
),
});
if (response == null) {
throw new Error("Code dialog closed");
}
code = response;
}
}
await hass.callService("alarm_control_panel", service, {
entity_id: stateObj.entity_id,
code,
});
};

View File

@@ -138,6 +138,17 @@ export const adminChangePassword = (
password,
});
export const adminChangeUsername = (
hass: HomeAssistant,
userId: string,
username: string
) =>
hass.callWS<void>({
type: "config/auth_provider/homeassistant/admin_change_username",
user_id: userId,
username,
});
export const deleteAllRefreshTokens = (
hass: HomeAssistant,
token_type?: RefreshTokenType,

View File

@@ -8,7 +8,6 @@ import {
import secondsToDuration from "../common/datetime/seconds_to_duration";
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
import { computeStateName } from "../common/entity/compute_state_name";
import "../resources/intl-polyfill";
import type { HomeAssistant } from "../types";
import { Condition, ForDict, Trigger } from "./automation";
import {
@@ -902,7 +901,7 @@ const tryDescribeCondition = (
)
: undefined;
if (condition.above && condition.below) {
if (condition.above !== undefined && condition.below !== undefined) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.above-below`,
{
@@ -913,7 +912,7 @@ const tryDescribeCondition = (
}
);
}
if (condition.above) {
if (condition.above !== undefined) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.above`,
{
@@ -923,7 +922,7 @@ const tryDescribeCondition = (
}
);
}
if (condition.below) {
if (condition.below !== undefined) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.below`,
{

View File

@@ -13,7 +13,7 @@ export interface Blueprint {
export interface BlueprintMetaData {
domain: BlueprintDomain;
name: string;
input?: Record<string, BlueprintInput | null>;
input?: Record<string, BlueprintInput | BlueprintInputSection | null>;
description?: string;
source_url?: string;
author?: string;
@@ -26,6 +26,14 @@ export interface BlueprintInput {
default?: any;
}
export interface BlueprintInputSection {
name?: string;
icon?: string;
description?: string;
collapsed?: boolean;
input: Record<string, BlueprintInput | null>;
}
export interface BlueprintImportResult {
suggested_filename: string;
raw_data: string;

View File

@@ -6,6 +6,7 @@ export interface ConfigUpdateValues {
latitude: number;
longitude: number;
elevation: number;
radius: number;
unit_system: "metric" | "us_customary";
time_zone: string;
external_url?: string | null;

View File

@@ -0,0 +1,28 @@
export interface DataTableFilters {
[key: string]: {
value: string[] | { key: string[] } | undefined;
items: Set<string> | undefined;
};
}
export const serializeFilters = (value: DataTableFilters) => {
const serializedValue = {};
Object.entries(value).forEach(([key, val]) => {
serializedValue[key] = {
value: val.value,
items: val.items instanceof Set ? Array.from(val.items) : val.items,
};
});
return serializedValue;
};
export const deserializeFilters = (value: DataTableFilters) => {
const deserializedValue = {};
Object.entries(value).forEach(([key, val]) => {
deserializedValue[key] = {
value: val.value,
items: Array.isArray(val.items) ? new Set(val.items) : val.items,
};
});
return deserializedValue;
};

View File

@@ -249,6 +249,22 @@ export const localizeDeviceAutomationTrigger = (
) ||
(trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!);
export const localizeExtraFieldsComputeLabelCallback =
(hass: HomeAssistant, deviceAutomation: DeviceAutomation) =>
// Returns a callback for ha-form to calculate labels per schema object
(schema): string =>
hass.localize(
`component.${deviceAutomation.domain}.device_automation.extra_fields.${schema.name}`
) || schema.name;
export const localizeExtraFieldsComputeHelperCallback =
(hass: HomeAssistant, deviceAutomation: DeviceAutomation) =>
// Returns a callback for ha-form to calculate helper texts per schema object
(schema): string | undefined =>
hass.localize(
`component.${deviceAutomation.domain}.device_automation.extra_fields_descriptions.${schema.name}`
);
export const sortDeviceAutomations = (
automationA: DeviceAutomation,
automationB: DeviceAutomation

View File

@@ -96,6 +96,10 @@ export interface LockEntityOptions {
default_code?: string | null;
}
export interface AlarmControlPanelEntityOptions {
default_code?: string | null;
}
export interface WeatherEntityOptions {
precipitation_unit?: string | null;
pressure_unit?: string | null;
@@ -112,6 +116,7 @@ export interface SwitchAsXEntityOptions {
export interface EntityRegistryOptions {
number?: NumberEntityOptions;
sensor?: SensorEntityOptions;
alarm_control_panel?: AlarmControlPanelEntityOptions;
lock?: LockEntityOptions;
weather?: WeatherEntityOptions;
light?: LightEntityOptions;
@@ -134,6 +139,7 @@ export interface EntityRegistryEntryUpdateParams {
| SensorEntityOptions
| NumberEntityOptions
| LockEntityOptions
| AlarmControlPanelEntityOptions
| WeatherEntityOptions
| LightEntityOptions;
aliases?: string[];

View File

@@ -8,12 +8,37 @@ interface Image {
id: string;
}
export const URL_PREFIX = "/api/image/serve/";
export interface ImageMutableParams {
name: string;
}
export const generateImageThumbnailUrl = (mediaId: string, size: number) =>
`/api/image/serve/${mediaId}/${size}x${size}`;
export const getIdFromUrl = (url: string): string | undefined => {
let id;
if (url.startsWith(URL_PREFIX)) {
id = url.substring(URL_PREFIX.length);
const idx = id.indexOf("/");
if (idx >= 0) {
id = id.substring(0, idx);
}
}
return id;
};
export const generateImageThumbnailUrl = (
mediaId: string,
size?: number,
original: boolean = false
) => {
if (!original && !size) {
throw new Error("Size must be provided if original is false");
}
return original
? `/api/image/serve/${mediaId}/original`
: `/api/image/serve/${mediaId}/${size}x${size}`;
};
export const fetchImages = (hass: HomeAssistant) =>
hass.callWS<Image[]>({ type: "image/list" });
@@ -50,5 +75,5 @@ export const updateImage = (
export const deleteImage = (hass: HomeAssistant, id: string) =>
hass.callWS({
type: "image/delete",
media_id: id,
image_id: id,
});

View File

@@ -3,17 +3,13 @@ import {
getCollection,
HassEventBase,
} from "home-assistant-js-websocket";
import { HuiErrorCard } from "../panels/lovelace/cards/hui-error-card";
import {
Lovelace,
LovelaceBadge,
LovelaceCard,
} from "../panels/lovelace/types";
import type { HuiCard } from "../panels/lovelace/cards/hui-card";
import type { HuiSection } from "../panels/lovelace/sections/hui-section";
import { Lovelace, LovelaceBadge } from "../panels/lovelace/types";
import { HomeAssistant } from "../types";
import { LovelaceSectionConfig } from "./lovelace/config/section";
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
import { LovelaceViewConfig } from "./lovelace/config/view";
import { HuiSection } from "../panels/lovelace/sections/hui-section";
export interface LovelacePanelConfig {
mode: "yaml" | "storage";
@@ -24,7 +20,7 @@ export interface LovelaceViewElement extends HTMLElement {
lovelace?: Lovelace;
narrow?: boolean;
index?: number;
cards?: Array<LovelaceCard | HuiErrorCard>;
cards?: HuiCard[];
badges?: LovelaceBadge[];
sections?: HuiSection[];
isStrategy: boolean;
@@ -34,9 +30,10 @@ export interface LovelaceViewElement extends HTMLElement {
export interface LovelaceSectionElement extends HTMLElement {
hass?: HomeAssistant;
lovelace?: Lovelace;
preview?: boolean;
viewIndex?: number;
index?: number;
cards?: Array<LovelaceCard | HuiErrorCard>;
cards?: HuiCard[];
isStrategy: boolean;
setConfig(config: LovelaceSectionConfig): void;
}

View File

@@ -1,4 +1,5 @@
import { LovelaceLayoutOptions } from "../../../panels/lovelace/types";
import type { Condition } from "../../../panels/lovelace/common/validate-condition";
import type { LovelaceLayoutOptions } from "../../../panels/lovelace/types";
export interface LovelaceCardConfig {
index?: number;
@@ -7,4 +8,5 @@ export interface LovelaceCardConfig {
layout_options?: LovelaceLayoutOptions;
type: string;
[key: string]: any;
visibility?: Condition[];
}

View File

@@ -7,7 +7,6 @@ import type { LovelaceViewRawConfig } from "./view";
export interface LovelaceDashboardBaseConfig {}
export interface LovelaceConfig extends LovelaceDashboardBaseConfig {
title?: string;
background?: string;
views: LovelaceViewRawConfig[];
}

View File

@@ -7,6 +7,10 @@ export interface ShowViewConfig {
user?: string;
}
interface LovelaceViewBackgroundConfig {
image?: string;
}
export interface LovelaceBaseViewConfig {
index?: number;
title?: string;
@@ -14,7 +18,7 @@ export interface LovelaceBaseViewConfig {
icon?: string;
theme?: string;
panel?: boolean;
background?: string;
background?: string | LovelaceViewBackgroundConfig;
visible?: boolean | ShowViewConfig[];
subview?: boolean;
back_path?: string;

View File

@@ -2,6 +2,7 @@ import { HomeAssistant } from "../types";
export interface OTBRInfo {
active_dataset_tlvs: string;
border_agent_id: string;
channel: number;
extended_address: string;
url: string;

View File

@@ -33,22 +33,32 @@ export const getPanelNameTranslationKey = (panel: PanelInfo) => {
return `panel.${panel.title}` as const;
};
export const getPanelTitle = (hass: HomeAssistant): string | undefined => {
export const getPanelTitle = (
hass: HomeAssistant,
panel: PanelInfo
): string | undefined => {
const translationKey = getPanelNameTranslationKey(panel);
return hass.localize(translationKey) || panel.title || undefined;
};
export const getPanelTitleFromUrlPath = (
hass: HomeAssistant,
urlPath: string
): string | undefined => {
if (!hass.panels) {
return undefined;
}
const panel = Object.values(hass.panels).find(
(p: PanelInfo): boolean => p.url_path === hass.panelUrl
(p: PanelInfo): boolean => p.url_path === urlPath
);
if (!panel) {
return undefined;
}
const translationKey = getPanelNameTranslationKey(panel);
return hass.localize(translationKey) || panel.title || undefined;
return getPanelTitle(hass, panel);
};
export const getPanelIcon = (panel: PanelInfo): string | null => {

View File

@@ -11,6 +11,7 @@ export interface RefreshToken {
client_id: string;
client_name?: string;
created_at: string;
expire_at?: string;
id: string;
is_current: boolean;
last_used_at?: string;

View File

@@ -14,6 +14,7 @@ import {
} from "./entity_registry";
import { EntitySources } from "./entity_sources";
import { isHelperDomain } from "../panels/config/helpers/const";
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
export type Selector =
| ActionSelector
@@ -40,6 +41,7 @@ export type Selector =
| FileSelector
| IconSelector
| LabelSelector
| ImageSelector
| LanguageSelector
| LocationSelector
| MediaSelector
@@ -256,6 +258,11 @@ export interface IconSelector {
} | null;
}
export interface ImageSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
image: { original?: boolean; crop?: CropOptions } | null;
}
export interface LabelSelector {
label: {
multiple?: boolean;

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

@@ -0,0 +1,21 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
export interface ThresholdPreview {
state: string;
attributes: Record<string, any>;
}
export const subscribePreviewThreshold = (
hass: HomeAssistant,
flow_id: string,
flow_type: "config_flow" | "options_flow",
user_input: Record<string, any>,
callback: (preview: ThresholdPreview) => void
): Promise<UnsubscribeFunc> =>
hass.connection.subscribeMessage(callback, {
type: "threshold/start_preview",
flow_id,
flow_type,
user_input,
});

View File

@@ -144,7 +144,7 @@ export const checkForEntityUpdates = async (
// there is no reliable way to know if all the updates are done updating, so we just wait a bit for now...
await new Promise((r) => {
setTimeout(r, 10000);
setTimeout(r, 15000);
});
unsubscribeEvents();

View File

@@ -1,6 +1,9 @@
import {
mdiAlertCircleOutline,
mdiGauge,
mdiThermometer,
mdiThermometerWater,
mdiSunWireless,
mdiWaterPercent,
mdiWeatherCloudy,
mdiWeatherFog,
@@ -114,10 +117,15 @@ export const weatherIcons = {
};
export const weatherAttrIcons = {
apparent_temperature: mdiThermometer,
cloud_coverage: mdiWeatherCloudy,
dew_point: mdiThermometerWater,
humidity: mdiWaterPercent,
wind_bearing: mdiWeatherWindy,
wind_speed: mdiWeatherWindy,
pressure: mdiGauge,
temperature: mdiThermometer,
uv_index: mdiSunWireless,
visibility: mdiWeatherFog,
precipitation: mdiWeatherRainy,
};
@@ -221,6 +229,8 @@ export const getWeatherUnit = (
stateObj.attributes.pressure_unit ||
(lengthUnit === "km" ? "hPa" : "inHg")
);
case "apparent_temperature":
case "dew_point":
case "temperature":
case "templow":
return (
@@ -228,6 +238,7 @@ export const getWeatherUnit = (
);
case "wind_speed":
return stateObj.attributes.wind_speed_unit || `${lengthUnit}/h`;
case "cloud_coverage":
case "humidity":
case "precipitation_probability":
return "%";

View File

@@ -14,6 +14,7 @@ export interface Zone {
export interface HomeZoneMutableParams {
latitude: number;
longitude: number;
radius: number;
}
export interface ZoneMutableParams {

View File

@@ -156,7 +156,7 @@ export interface QRProvisioningInformation {
export interface PlannedProvisioningEntry {
/** The device specific key (DSK) in the form aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222 */
dsk: string;
security_classes: SecurityClass[];
securityClasses: SecurityClass[];
}
export const MINIMUM_QR_STRING_LENGTH = 52;
@@ -388,11 +388,9 @@ export const enum NodeStatus {
export interface ZwaveJSProvisioningEntry {
/** The device specific key (DSK) in the form aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222 */
dsk: string;
security_classes: SecurityClass[];
additional_properties: {
nodeId?: number;
[prop: string]: any;
};
securityClasses: SecurityClass[];
nodeId?: number;
[prop: string]: any;
}
export interface RequestedGrant {
@@ -489,14 +487,14 @@ export const stopZwaveExclusion = (hass: HomeAssistant, entry_id: string) =>
export const zwaveGrantSecurityClasses = (
hass: HomeAssistant,
entry_id: string,
security_classes: SecurityClass[],
client_side_auth?: boolean
securityClasses: SecurityClass[],
clientSideAuth?: boolean
) =>
hass.callWS({
type: "zwave_js/grant_security_classes",
entry_id,
security_classes,
client_side_auth,
securityClasses,
clientSideAuth,
});
export const zwaveTryParseDskFromQrCode = (

View File

@@ -0,0 +1,108 @@
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { FlowType } from "../../../data/data_entry_flow";
import {
ThresholdPreview,
subscribePreviewThreshold,
} from "../../../data/threshold";
import { HomeAssistant } from "../../../types";
import "./entity-preview-row";
import { debounce } from "../../../common/util/debounce";
import { fireEvent } from "../../../common/dom/fire_event";
@customElement("flow-preview-threshold")
class FlowPreviewThreshold extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public flowType!: FlowType;
public handler!: string;
@property() public stepId!: string;
@property() public flowId!: string;
@property() public stepData!: Record<string, any>;
@state() private _preview?: HassEntity;
@state() private _error?: string;
private _unsub?: Promise<UnsubscribeFunc>;
disconnectedCallback(): void {
super.disconnectedCallback();
if (this._unsub) {
this._unsub.then((unsub) => unsub());
this._unsub = undefined;
}
}
willUpdate(changedProps) {
if (changedProps.has("stepData")) {
this._debouncedSubscribePreview();
}
}
protected render() {
if (this._error) {
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
}
return html`<entity-preview-row
.hass=${this.hass}
.stateObj=${this._preview}
></entity-preview-row>`;
}
private _setPreview = (preview: ThresholdPreview) => {
const now = new Date().toISOString();
this._preview = {
entity_id: `${this.stepId}.___flow_preview___`,
last_changed: now,
last_updated: now,
context: { id: "", parent_id: null, user_id: null },
...preview,
};
};
private _debouncedSubscribePreview = debounce(() => {
this._subscribePreview();
}, 250);
private async _subscribePreview() {
if (this._unsub) {
(await this._unsub)();
this._unsub = undefined;
}
if (this.flowType === "repair_flow") {
return;
}
try {
this._unsub = subscribePreviewThreshold(
this.hass,
this.flowId,
this.flowType,
this.stepData,
this._setPreview
);
await this._unsub;
fireEvent(this, "set-flow-errors", { errors: {} });
} catch (err: any) {
if (typeof err.message === "string") {
this._error = err.message;
} else {
this._error = undefined;
fireEvent(this, "set-flow-errors", err.message);
}
this._unsub = undefined;
this._preview = undefined;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"flow-preview-threshold": FlowPreviewThreshold;
}
}

View File

@@ -4,10 +4,12 @@ import { styleMap } from "lit/directives/style-map";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-control-button";
import "../../../components/ha-state-icon";
import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel";
import {
AlarmControlPanelEntity,
setProtectedAlarmControlPanelMode,
} from "../../../data/alarm_control_panel";
import "../../../state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes";
import type { HomeAssistant } from "../../../types";
import { showEnterCodeDialog } from "../../enter-code/show-enter-code-dialog";
import "../components/ha-more-info-state-header";
import { moreInfoControlStyle } from "../components/more-info-control-style";
@@ -18,24 +20,12 @@ class MoreInfoAlarmControlPanel extends LitElement {
@property({ attribute: false }) public stateObj?: AlarmControlPanelEntity;
private async _disarm() {
let code: string | undefined;
if (this.stateObj!.attributes.code_format) {
const response = await showEnterCodeDialog(this, {
codeFormat: this.stateObj!.attributes.code_format,
title: this.hass.localize("ui.card.alarm_control_panel.disarm"),
submitText: this.hass.localize("ui.card.alarm_control_panel.disarm"),
});
if (response == null) {
return;
}
code = response;
}
this.hass.callService("alarm_control_panel", "alarm_disarm", {
entity_id: this.stateObj!.entity_id,
code,
});
setProtectedAlarmControlPanelMode(
this,
this.hass,
this.stateObj!,
"disarmed"
);
}
protected render() {

View File

@@ -126,7 +126,6 @@ class MoreInfoUpdate extends LitElement {
></ha-checkbox>
</ha-formfield> `
: ""}
<hr />
<div class="actions">
${this.stateObj.attributes.auto_update
? ""
@@ -240,10 +239,20 @@ class MoreInfoUpdate extends LitElement {
justify-content: space-between;
}
.actions {
border-top: 1px solid var(--divider-color);
background: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
margin: 8px 0 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
position: sticky;
bottom: 0;
padding: 12px 0;
margin-bottom: -24px;
z-index: 1;
}
.actions mwc-button {

View File

@@ -215,10 +215,10 @@ export class HaVoiceCommandDialog extends LitElement {
<div class="messages">
<div class="messages-container" id="scroll-container">
${this._conversation!.map(
// New lines matter for messages
// prettier-ignore
(message) => html`
<div class=${this._computeMessageClasses(message)}>
${message.text}
</div>
<div class=${this._computeMessageClasses(message)}>${message.text}</div>
`
)}
</div>
@@ -355,7 +355,7 @@ export class HaVoiceCommandDialog extends LitElement {
private _handleSendMessage() {
if (this._messageInput.value) {
this._processText(this._messageInput.value);
this._processText(this._messageInput.value.trim());
this._messageInput.value = "";
this._showSendButton = false;
}
@@ -427,34 +427,28 @@ export class HaVoiceCommandDialog extends LitElement {
private async _showNotSupportedMessage() {
this._addMessage({
who: "hass",
text: html`
<p>
${this.hass.localize(
"ui.dialogs.voice_command.not_supported_microphone_browser"
)}
</p>
<p>
${this.hass.localize(
"ui.dialogs.voice_command.not_supported_microphone_documentation",
{
documentation_link: html`
<a
target="_blank"
rel="noopener noreferrer"
href=${documentationUrl(
this.hass,
"/docs/configuration/securing/#remote-access"
)}
>
${this.hass.localize(
"ui.dialogs.voice_command.not_supported_microphone_documentation_link"
)}
</a>
`,
}
)}
</p>
`,
text:
// New lines matter for messages
// prettier-ignore
html`${this.hass.localize(
"ui.dialogs.voice_command.not_supported_microphone_browser"
)}
${this.hass.localize(
"ui.dialogs.voice_command.not_supported_microphone_documentation",
{
documentation_link: html`<a
target="_blank"
rel="noopener noreferrer"
href=${documentationUrl(
this.hass,
"/docs/configuration/securing/#remote-access"
)}
>${this.hass.localize(
"ui.dialogs.voice_command.not_supported_microphone_documentation_link"
)}</a>`,
}
)}`,
});
}
@@ -756,6 +750,7 @@ export class HaVoiceCommandDialog extends LitElement {
max-height: 100%;
}
.message {
white-space: pre-line;
font-size: 18px;
clear: both;
margin: 8px 0;
@@ -792,10 +787,14 @@ export class HaVoiceCommandDialog extends LitElement {
direction: var(--direction);
}
.message a {
.message.user a {
color: var(--text-primary-color);
}
.message.hass a {
color: var(--primary-text-color);
}
.message img {
width: 100%;
border-radius: 10px;

View File

@@ -2,7 +2,6 @@
import "../resources/compatibility";
import "../auth/ha-authorize";
import "../resources/safari-14-attachshadow-patch";
import "../resources/array.flat.polyfill";
import("../resources/ha-style");
import("@polymer/polymer/lib/utils/settings").then(

View File

@@ -25,7 +25,6 @@ import { subscribePanels } from "../data/ws-panels";
import { subscribeThemes } from "../data/ws-themes";
import { subscribeUser } from "../data/ws-user";
import type { ExternalAuth } from "../external_app/external_auth";
import "../resources/array.flat.polyfill";
import "../resources/safari-14-attachshadow-patch";
window.name = MAIN_WINDOW_NAME;

View File

@@ -2,7 +2,6 @@
import "../resources/compatibility";
import "../onboarding/ha-onboarding";
import "../resources/safari-14-attachshadow-patch";
import "../resources/array.flat.polyfill";
import("../resources/ha-style");
import("@polymer/polymer/lib/utils/settings").then(

Some files were not shown because too many files have changed in this diff Show More