Compare commits

...

379 Commits

Author SHA1 Message Date
Paul Bottein
f43319a3ae Bumped version to 20240610.1 2024-06-13 20:49:43 +02:00
Paul Bottein
e8eefaf1d3 Fix current mode not selected in card feature (#21063) 2024-06-13 20:46:50 +02:00
Steve Repsher
06d82a4925 Do not inject Intl polyfill into ecma402-abstract package (#21074) 2024-06-13 20:45:50 +02:00
Paulus Schoutsen
4991d52fcc Hide notify entities from generated dashboard (#21075)
Hide notify entiites from generated dashboard
2024-06-13 20:45:35 +02:00
Simon Lamon
0b391eafcf Fix diagnostic download not downloading (#21078) 2024-06-13 20:45:12 +02:00
Paul Bottein
0bb34830f8 Fix selected state for selected config entries (#21079) 2024-06-13 20:45:02 +02:00
Bram Kragten
4a8bb5034d Merge branch 'dev' 2024-06-10 19:50:24 +02: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
a8366c6416 Merge branch 'dev' 2024-06-05 11:42:10 +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
8ff8c01bba Merge branch 'dev' 2024-06-04 16:47:15 +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
52f3ff3306 20240603.0 (#20974) 2024-06-03 18:56:01 +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
10a5c4dfb4 20240530.0 (#20925) 2024-05-30 16:27: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
1a2daf8a7a 20240529.0 (#20901) 2024-05-29 18:27:40 +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
renovate[bot]
1dbb70b964 Update dependency glob to v10.4.1 (#20881)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:39:34 +02:00
renovate[bot]
1eecc5c0e2 Update dependency glob to v10.4.0 (#20879)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:22:11 +02:00
Franck Nijhof
81c0bcff0b Add sequence action building block (#20874)
* Add sequence action building block

* This is a non-conditional action

* Render sequence in automation traces graph

* Render trace timeline

* Process review comment
2024-05-26 14:54:05 -04:00
karwosts
6ccbeb8a75 Fix entity picker delete button (#20875) 2024-05-26 15:29:57 +02:00
renovate[bot]
f59ed0a72b Update dependency @rollup/plugin-commonjs to v25.0.8 (#20873) 2024-05-25 21:45:51 -04:00
renovate[bot]
a9f00ded0f Update fullcalendar monorepo to v6.1.13 (#20869)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-25 16:10:53 +00:00
renovate[bot]
74730ba201 Update dependency lint-staged to v15.2.4 (#20868)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 22:12:56 +02:00
renovate[bot]
95b2f7d821 Update dependency glob to v10.3.16 (#20867)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 22:07:19 +02:00
renovate[bot]
661b14da54 Update formatjs monorepo (#20847)
* Update formatjs monorepo

* Update dependency @formatjs/intl-locale to v4

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 18:49:48 +02:00
Steve Repsher
41e34c0d61 Inject ResizeObserver polyfill where needed (#20754) 2024-05-24 12:21:46 -04:00
Simon Lamon
5d044a06eb Remove empty electricity grid state (#20794)
* Remove empty grid state

* Apply feedback

* AI review
2024-05-24 16:40:47 +02:00
karwosts
f617426808 Bake numeric device classes into formatEntityState (#19878)
* Bake numeric device classes into formatEntityState

* Apply suggestions from code review

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

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-05-24 16:17:23 +02:00
karwosts
3c3d54243c Fix pick-theme-row and behavior of default theme mode (#20783) 2024-05-24 16:06:22 +02:00
Paul Bottein
afc624bf4b Remove strict connection (#20861) 2024-05-24 15:50:45 +02:00
AlCalzone
0991628843 Z-Wave JS: Title-Case "S2 Access Control" (#20850)
* Z-Wave JS: Title-Case "S2 Access Control"

* Apply sentence case to example description
2024-05-24 13:23:17 +00:00
Paul Bottein
34b9c7b9d1 Fix long title for section in add card dialog (#20863) 2024-05-24 15:10:17 +02:00
karwosts
d52641b495 Device trigger - sync value with initialHaFormData (#20810)
Device trigger - sync yaml with initialHaFormData
2024-05-24 15:06:40 +02:00
renovate[bot]
80c7fd2bf2 Update typescript-eslint monorepo to v7.10.0 (#20858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-23 21:36:30 +02:00
Steve Repsher
e0062cf190 Inject ElementInternals polyfill where needed (#20818) 2024-05-23 14:25:58 -04:00
karwosts
7d2cee650d Sort yaml reloads on translated domain (#20857) 2024-05-23 16:26:03 +02:00
c0ffeeca7
66560b1f1c Apply sentence-style capitalization to headings (#20855) 2024-05-23 15:22:06 +02:00
NP v/d Spek
a500b582e3 Modify the way the update shows available updates (#20773)
* Modify the way the update shows available updates

* Update src/data/update.ts

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>

* Update src/data/update.ts

* Update src/translations/en.json

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-05-22 16:15:17 +02:00
Adam Kapos
19f94ff8cc Fix app-theme-color not following header color (#20758)
* Fix app-theme-color not following header color

* Revert incorrect fix for #20671 that only applies when a primary color is set by the user
2024-05-22 15:26:13 +02:00
AlCalzone
0b6994d402 Z-Wave JS: Sort security classes from highest to lowest (#20851) 2024-05-22 14:18:09 +02:00
karwosts
9fe8f507ec Add configurable actions to Gauge Card (#20833)
* Add actions to gauge card

* struct support

* tap_action in UI

* Apply suggestions from code review

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>

* typo

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-05-22 14:10:22 +02:00
Simon Lamon
2113cf5280 Trim search inputs (#20825) 2024-05-22 10:39:32 +02:00
Paul Bottein
ae9e1b724f Fix voice assistant expose datatable storage key (#20843) 2024-05-21 19:20:29 +02:00
karwosts
9b28c7cf69 Script editor updates (match automation editor) (#20791)
* Show descriptions in script editor

* Script editor updates (match automation editor)

* review feedback
2024-05-21 19:01:51 +02:00
renovate[bot]
d9bac06806 Update formatjs monorepo (#20838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 12:29:21 -04:00
karwosts
b1e37cb1e1 Missing checkbox in zwave dialog (#20841) 2024-05-21 11:56:55 -04:00
Paul Bottein
a2a89502d8 Add visibility option to sections (conditional section) (#20805)
* Add first version of section visibility option

* Move visibility logic into view

* Simplify section view structure

* Don't add hidden section to dom

* Move visilibity logic to hui-section

* Setup section editor

* Add visibility view

* Add basic settings editor

* Improve visibility editor

* Update conditional base

* Feedbacks

* Better typings
2024-05-21 10:43:23 +02:00
Paul Bottein
4cc5d2d04b Improve open and opening state for lock (#20808)
* Add open and opening color

* Split isAvailable into multiple function

* Use done wording
2024-05-21 09:19:36 +02:00
renovate[bot]
79abcca3b3 Update dependency @braintree/sanitize-url to v7.0.2 (#20836)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 19:54:15 +02:00
renovate[bot]
043f383a35 Update dependency chart.js to v4.4.3 (#20835)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 13:25:08 -04:00
dependabot[bot]
d4dd767941 Bump actions/checkout from 4.1.5 to 4.1.6 (#20829) 2024-05-20 08:29:31 +02:00
renovate[bot]
174f1991b1 Lock file maintenance (#20823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-19 03:04:28 +00:00
renovate[bot]
e15495a626 Update dependency eslint-plugin-lit to v1.13.0 (#20775)
* Update dependency eslint-plugin-lit to v1.13.0

* Set attribute-names rule to warning

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
2024-05-19 02:49:44 +00:00
renovate[bot]
a8a9a797cb Update dependency sinon to v18 (#20822)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-18 21:57:01 -04:00
karwosts
914dbc1e28 Reset select-selector to undefined when cleared (#20821) 2024-05-18 17:28:59 +02:00
Jan-Philipp Benecke
111816f08a Use domain translation in filter for domain search (#20763)
* Use domain translation in filter for domain search
2024-05-18 10:15:29 +02:00
renovate[bot]
1b4534890c Update dependency @lokalise/node-api to v12.5.0 (#20819)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-18 10:02:44 +02:00
renovate[bot]
ed6542469d Update dependency core-js to v3.37.1 (#20815) 2024-05-17 08:30:47 -04:00
renovate[bot]
3774a3d6ba Update typescript-eslint monorepo to v7.9.0 (#20812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 16:32:31 -04:00
Bram Kragten
bfa293ae3a Allow storing thread credentials in phone keychain (#20743)
* Allow storing thread credentials in phone keychain

* Update dialog-thread-dataset.ts

* use preferred of dataset if available
2024-05-16 10:01:40 +02:00
renovate[bot]
9264adb799 Update vaadinWebComponents monorepo to v24.3.13 (#20802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 09:19:36 +02:00
renovate[bot]
829ea4a9e4 Update dependency @types/chromecast-caf-sender to v1.0.10 (#20801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 09:04:29 +02:00
Paul Bottein
be26f8bc24 Add grouping by area and domain in voice assistant expose data table (#20797) 2024-05-15 11:41:09 +02:00
renovate[bot]
c864b34a9a Update dependency glob to v10.3.15 (#20795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 08:55:31 +02:00
Cody C
099ea61a94 Add checks to energy dashboard for when preferences are empty (#19765)
* Forbid completing energy dashboard setup unless at least one statistic is selected
* If energy setup was completed but there are no sources available, start setup wizard again
2024-05-13 20:57:37 +02:00
karwosts
3ebe6027be Minor fixes to energy sources behavior (#20785) 2024-05-13 18:41:51 +02:00
karwosts
f5f2a5ad5b Show descriptions in script editor (#20765)
* Show descriptions in script editor

* descriptions in blueprints also
2024-05-13 14:19:47 +02:00
Paulus Schoutsen
d046700d06 Show tag ID if no name, sort by last updated on mobile (#20788) 2024-05-13 09:56:23 +02:00
dependabot[bot]
2ad84b2832 Bump softprops/action-gh-release from 2.0.4 to 2.0.5 (#20790)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 09:13:29 +02:00
dependabot[bot]
f9ccb9fc72 Bump actions/checkout from 4.1.4 to 4.1.5 (#20789)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 09:12:12 +02:00
renovate[bot]
6d3940db1e Update dependency glob to v10.3.14 (#20784)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-12 19:08:52 +02:00
renovate[bot]
20d174431d Update dependency chai to v5.1.1 (#20781)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-12 13:06:07 +02:00
renovate[bot]
1900710e06 Update Yarn to v4.2.2 (#20778) 2024-05-11 15:41:07 -04:00
renovate[bot]
ed86a48e1c Update dependency sinon to v17.0.2 (#20772)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-10 15:08:14 -04:00
renovate[bot]
d2bdb52926 Update vaadinWebComponents monorepo to v24.3.12 (#20761)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-09 14:21:35 +02:00
G Johansson
9c57c9f151 Support open / opening state in LockEntity (#19944) 2024-05-08 21:01:57 +02:00
karwosts
9e9cb15a42 Minor improvements to service call descriptions. (#20733)
* Minor improvements to service call descriptions.
2024-05-08 18:04:38 +02:00
renovate[bot]
6421a9443d Update dependency intl-messageformat to v10.5.12 (#20755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-08 12:50:05 +02:00
Paulus Schoutsen
f2b43ddad8 Allow adding card from history panel (#19582)
* Allow adding card from history panel

* Better empty entities check
2024-05-07 17:11:27 +02:00
Yosi Levy
e55b59d9b7 Logical property style fixes (#20752)
logical prop fixes
2024-05-07 15:35:34 +02:00
Paul Bottein
4a77359a06 Use Material 3 ripple (#20751)
* Use material web ripple component

* Improve button style

* Use css animation instead of ripple for action

* Use ha ripple in all components

* Remove unused label
2024-05-07 15:30:45 +02:00
renovate[bot]
505d7b6ddb Update dependency tar to v7.1.0 (#20748) 2024-05-07 08:23:16 +02:00
Steve Repsher
79cdc43699 Enhance webpack transform async plugin to use babel runtime (with fix) (#20745) 2024-05-06 18:06:21 -04:00
renovate[bot]
8ff9823cd7 Update dependency @octokit/rest to v20.1.1 (#20746)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-06 20:42:37 +02:00
Paul Bottein
3488c60818 Fix tile card margin on old devices (#20742) 2024-05-06 19:49:52 +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
Yosi Levy
43a422cdca Font updates in new filters (#20482)
* Style changes

* Fixes
2024-05-06 15:39:36 +02:00
Douwe
11f2bef05c Add header text align theme variable to stack cards (#20563)
* Update hui-stack-card.ts

Added variable

* Update hui-stack-card.ts

Updated the variable, so that it would not be in line with the rest of the variables. In this way, the variable only works for hui-stack titles.

* Update hui-stack-card.ts

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

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-05-06 13:35:36 +00:00
karwosts
ff9f331287 Expand createDomains to more selectors (#20714)
Expand createDomains to more pickers
2024-05-06 15:26:13 +02:00
Steve Repsher
cdf64ccdaa Refactor translation merges to use native transform stream (#20666) 2024-05-06 15:17:01 +02:00
Simon Lamon
8b220acca2 Show ungrouped group when there are results (#20716) 2024-05-06 15:07:22 +02:00
Paul Bottein
8fdb7fa1d5 Update newsletter link (#20740)
* Update newsletter link

* Update src/panels/config/dashboard/ha-config-dashboard.ts

* Update src/onboarding/dialogs/community-dialog.ts
2024-05-06 14:57:51 +02:00
Paulus Schoutsen
008c842431 Fix showing options button on conversation agent picker (#20736) 2024-05-06 12:24:22 +02:00
Paul Bottein
bc41de0d9c Revert usage of babel runtime for legacy bundle (#20741)
Revert usage of babel runtine for legacy bundle
2024-05-06 12:12:19 +02:00
renovate[bot]
7310c9cf6d Update Yarn to v4.2.1 (#20735) 2024-05-05 21:49:14 -04:00
Steve Repsher
84b436c08e Fix self-injection for custom polyfills (#20718)
* Fix self-injection for custom polyfills

* Update build-scripts/bundle.cjs

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-05-03 16:37:40 -04:00
renovate[bot]
1925a47bdc Update dependency eslint-plugin-unused-imports to v3.2.0 (#20715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-03 16:05:13 +00:00
renovate[bot]
438a426458 Update babel monorepo to v7.24.5 (#20707) 2024-05-02 21:25:29 -04:00
karwosts
f923deb71d Energy CSV download should not require admin (#20704) 2024-05-02 21:08:54 +02:00
renovate[bot]
e79bc71ab7 Update typescript-eslint monorepo to v7.8.0 (#20703)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-02 21:04:03 +02:00
karwosts
11b0990d2b Add spacer for FAB under the zone list (#20706) 2024-05-02 21:02:57 +02:00
Simon Lamon
870cb0c65f Always save custom display name in energy dashboard when hitting Enter (#20702)
Change to Input event
2024-05-02 20:03:36 +02:00
Paul Bottein
deda2009f8 Remove alarm modes list when adding a alarm modes card feature (#20688) 2024-05-02 19:22:43 +02:00
renovate[bot]
b2797ab8da Update dependency gulp to v5 (#20601)
* Update dependency gulp to v5

* Fix premature cloasing of hash stream

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
2024-05-01 15:13:28 +02:00
renovate[bot]
644dcb0381 Update dependency systemjs to v6.15.1 (#20682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-01 13:12:18 +02:00
Bram Kragten
c65f4f7a6e Revert "Remove strict connections" (#20685)
Revert "Remove strict connections (#20662)"

This reverts commit 1df92fa863.
2024-05-01 12:53:01 +02:00
Bram Kragten
e2266aa671 Merge branch 'dev' 2024-05-01 12:04:10 +02:00
Bram Kragten
68a79490dc Bumped version to 20240501.0 2024-05-01 12:03:26 +02:00
Paul Bottein
6febe8552e Allow to reorder alarm modes in card feature (#20684) 2024-05-01 11:55:06 +02:00
Bram Kragten
f611f23f6f Make sure lovelace theme background is set on it's container (#20683) 2024-05-01 11:24:40 +02:00
Bram Kragten
ef4f11fdf8 20240430.0 (#20681) 2024-04-30 23:58:58 +02:00
Bram Kragten
627e06663b Bumped version to 20240430.0 2024-04-30 23:44:32 +02:00
Paul Bottein
ab01633069 Fix ha settings row display in more info settings (#20680) 2024-04-30 21:12:53 +00:00
Bram Kragten
17dcc90638 Update entity status filter and grouping (#20679) 2024-04-30 23:04:48 +02:00
Paul Bottein
d0df029ff1 Update check update icon and add toast when checking update (#20677)
* Update check update icon

* Add toast when checking for update
2024-04-30 19:21:30 +00:00
Paul Bottein
86a7e69812 Allow to reorder and filter options in select options card feature (#20675) 2024-04-30 21:14:49 +02:00
Adam Kapos
af9417f2a6 Add theme support for dialog surface background (#20653)
* Add theme support for dialog surface background

* Change from review

* Change from review

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

* Run prettier

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-04-30 21:12:36 +02:00
Paul Bottein
7120ad99b9 Add customize mode option to card features with modes (#20670)
* Add customize mode options to card features with modes

* Better type

* Fix water heater and humidifier

* Clean schema
2024-04-30 18:38:51 +02:00
Adam Kapos
334c245b65 Fix visual differences between regular and energy dashboards (#20654)
* Fix visual differences between regular and energy dashboards

* Order padding properties the same way between energy and lovelace

* Change from code review
2024-04-30 15:07:54 +02:00
Nicooow
bcb72d83b8 Fix an inconsistency in dark mode (#20671)
* add app-theme-color var

* Fix Prettier format

* Fix regression on default dark theme

* prevent duplicate calculation
2024-04-30 12:03:19 +00:00
karwosts
c99e0e846b More config/entities status filters (#20638) 2024-04-30 12:32:32 +02:00
J. Nick Koston
ec3f63e8a3 Fallback to raw config entry reason if localize returns an empty string (#20668)
Show config entry reason if localize returns an empty string
2024-04-30 12:25:45 +02:00
karwosts
1bc33a30ec Display version info for custom integrations (#20652)
* Display version info for custom integrations

* no width
2024-04-30 12:23:20 +02:00
krazos
8cca233b7c Update unlock icon for tile card lock features (#20667)
Update unlock icon for tile card lock features so it's easier to see the difference between lock and unlock buttons
2024-04-29 20:53:33 +02:00
karwosts
a78608bfb4 Reorderable card-feature modes (#20647)
* Reorderable card-feature modes

* unused var in getStubConfig
2024-04-29 17:48:01 +02:00
Bram Kragten
e7c1ac94af 20240429.0 (#20665) 2024-04-29 17:44:33 +02:00
Bram Kragten
1a797b3415 Bumped version to 20240429.0 2024-04-29 17:36:46 +02:00
Bram Kragten
2b27a4da2b Show abort reason when no translation (#20664) 2024-04-29 17:35:30 +02:00
Bram Kragten
1df92fa863 Remove strict connections (#20662)
* Remove strict connections

* Update cloud-remote-pref.ts
2024-04-29 16:42:23 +02:00
Bram Kragten
cdde85315a fix list items cloud account (#20663) 2024-04-29 14:26:14 +00:00
Paul Bottein
dc67f9faf4 Fix cloud page design on mobile (#20661) 2024-04-29 16:03:02 +02:00
dependabot[bot]
3ad1be50a2 Bump actions/upload-artifact from 4.3.2 to 4.3.3 (#20658)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 12:38:02 +02:00
dependabot[bot]
8aadfe7d28 Bump actions/checkout from 4.1.3 to 4.1.4 (#20659)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 12:33:17 +02:00
renovate[bot]
cff54b73a4 Update dependency @lokalise/node-api to v12.4.1 (#20643)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-27 20:01:32 +02:00
Philip Allgaier
b54cfeb0c0 Hide "Browse Media" button for unavailable media players (#20629)
* Hide "Browse Media" button for unavailable media players
2024-04-27 14:36:42 +02:00
renovate[bot]
cefe612b11 Update dependency @octokit/plugin-retry to v7.1.1 (#20641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-27 11:16:55 +02:00
renovate[bot]
4bc874b497 Update workbox monorepo to v7.1.0 (#20642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-27 11:16:43 +02:00
Philip Allgaier
f3abaa8e02 Align lawn-mower and vacuum more-info layouts (#20632) 2024-04-26 14:07:38 +02:00
Philip Allgaier
21a563fe98 Add details for offset format to sun trigger (#20625)
Add details for offset to sun trigger
2024-04-26 14:05:04 +02:00
Paul Bottein
1acbcccd62 20240426.0 (#20636) 2024-04-26 11:42:26 +02:00
Paul Bottein
35d6c638ab Bumped version to 20240426.0 2024-04-26 11:40:38 +02:00
Bram Kragten
68f8239708 Update cloud remote settings (#20619)
* Update cloud remote settings

* Change again

* Update cloud-remote-pref.ts

* Update UI

* Add missing translations

* use hr and simplify condition

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-04-26 11:36:03 +02:00
renovate[bot]
0db64cca0b Update dependency @babel/helper-define-polyfill-provider to v0.6.2 (#20627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-25 23:37:11 -04:00
renovate[bot]
accfda5f4b Update typescript-eslint monorepo to v7.7.1 (#20628) 2024-04-25 20:51:55 -04:00
Philip Allgaier
c97c20f57d Add mock area registry to demo to fix card picker (#20626) 2024-04-25 18:50:16 +00:00
Philip Allgaier
2725d0191d Disable counter more-info dec/inc buttons when min/max reached (#20624) 2024-04-25 20:49:20 +02:00
renovate[bot]
852cc62398 Update dependency @types/leaflet to v1.9.12 (#20623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-25 17:56:27 +02:00
David F. Mulcahey
654e3ce437 Fix ZHA UI issues (#20622) 2024-04-25 16:16:00 +02:00
Bram Kragten
20a3a00aec add inital data for language selector (#20620)
* add inital data for language selector

* Update compute-initial-ha-form-data.ts
2024-04-25 15:25:18 +02:00
Bram Kragten
22b927d666 make sure we always have trigger and action (#20621)
* make sure we always have trigger and action

* script too
2024-04-25 15:24:33 +02:00
Philip Allgaier
709d6be2e3 Fix wrong chevron icon direction for groups in data tables (#20617)
Fix chevron icon for groups in data table
2024-04-25 11:28:36 +02:00
Bram Kragten
64f54d9aaa 20240424.1 (#20609) 2024-04-24 14:45:22 +02:00
Bram Kragten
fbda9ca418 Bumped version to 20240424.1 2024-04-24 14:30:36 +02:00
Paul Bottein
4e97e3763e Use theme mode property for ha-map (#20606)
* Use theme mode property for ha-map

* Use theme mode in ha-location-editor

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-04-24 14:28:33 +02:00
Bram Kragten
4c9c52d27d Add filter for domains to entity settings (#20605) 2024-04-24 13:53:14 +02:00
Bram Kragten
87bcd3e471 Add multiselect area to automation/scene/script (#20604) 2024-04-24 10:10:43 +00:00
Paul Bottein
7e9b01b56d Fix text alignment for open lock card feature (#20603)
Fix text aligment for open lock card feature
2024-04-24 09:36:17 +00:00
karwosts
713763fc21 Fix visual map issues (#20541)
* Fix visual map issues (#15587)

- hover background color of zoom control in dark mode
- map light mode when dark theme is used
- background color of zoom control with map dark mode when light theme is used

* Fix breaking change (#15587)

* Change theme mode selection to use dropdown (#15587)

- Additionally fixed unpleasant horizontal scrollbar in map editor

* Add yaml migration, fix force light/dark options

---------

Co-authored-by: Christoph Wen <wen.christoph@gmail.com>
2024-04-24 11:35:37 +02:00
renovate[bot]
5b7ab1bfcb Update dependency cropperjs to v1.6.2 (#20600)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-24 11:28:58 +02:00
Bram Kragten
8712adbf8d 20240424.0 (#20602) 2024-04-24 11:21:24 +02:00
Bram Kragten
4b0d19b615 Fix duplicate imports from merge 2024-04-24 11:14:55 +02:00
Bram Kragten
90e5d259af Merge branch 'master' into dev 2024-04-24 11:13:34 +02:00
Bram Kragten
af3a331f57 Bumped version to 20240424.0 2024-04-24 11:09:48 +02:00
Bram Kragten
67c60a4aa8 Add strict connection cloud settings (#20585)
* Add strict connection cloud settings

* Change static page to guard page

* saving files helps...

* Add create link button

* use correct service
2024-04-24 11:06:15 +02:00
Bram Kragten
62de16bb8e Implement storing sorting and grouping for all tables (#20594) 2024-04-24 11:06:00 +02:00
Marc Geurts
d9b71e754d Add lock features for tile card (#20539)
* Added lock features for tile card

* Change success label
2024-04-24 10:30:34 +02:00
Matthias Alphart
5fc950f09f Fix ha-filter-states clear filter behaviour (#20599)
Fix ha-filter-states clear filter behaviour
2024-04-24 10:28:17 +02:00
J. Nick Koston
0725c7b160 Restore getHassTranslationsPre109 (#20597)
* Revert "Remove legacy state translations (#20536)"

This reverts commit e376efc579.

* keep
2024-04-24 10:26:24 +02:00
Steve Repsher
469dbbcccc Rollback gulp to version 4 (#20598) 2024-04-24 10:25:52 +02:00
Bram Kragten
ffdd661b1f Make fallback translations work (#20596) 2024-04-24 07:31:29 +02:00
Bram Kragten
81922f5a3e Save and restore collapsed groups (#20591) 2024-04-23 21:00:17 +02:00
renovate[bot]
7e25366897 Update dependency @types/chromecast-caf-receiver to v6.0.14 (#20589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 18:04:44 +02:00
Bram Kragten
8ab61b5468 Add title when text in datatable doesnt fit (#20590) 2024-04-23 13:50:44 +02:00
Bram Kragten
8239f6dd60 Update hass-tabs-subpage-data-table.ts 2024-04-22 20:28:21 +02:00
Bram Kragten
45dce18e4d typo 2024-04-22 20:15:52 +02:00
Bram Kragten
a428ad0655 Add bulk area assignment to device dashboard (#20581)
* Add bulk area assignment to device dashboard

* Update ha-config-devices-dashboard.ts
2024-04-22 18:35:58 +02:00
Bram Kragten
1b54d51e4a Add option for custom group order to data table (#20582) 2024-04-22 18:28:50 +02:00
Bram Kragten
eb1354d229 Allow groups in data table to be collapsed (#20579) 2024-04-22 18:27:46 +02:00
Bram Kragten
4d21f9e80c Allow to group entities by domain (#20580)
Allow group entities by domain
2024-04-22 18:27:13 +02:00
Paul Bottein
62f46baacf Fix more info slider in iOS (#20586) 2024-04-22 18:26:50 +02:00
Bram Kragten
a3090796d2 Store grouping and sorting for device table (#20583) 2024-04-22 18:13:44 +02:00
renovate[bot]
c34c5d64f9 Update dependency @codemirror/commands to v6.5.0 (#20587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-22 18:09:46 +02:00
Bram Kragten
66228f5858 Add new timestamp state domains (#20584) 2024-04-22 16:33:32 +02:00
Bram Kragten
ac378cfe6d Update external barcode scanning API (#20470) 2024-04-22 12:52:59 +02:00
Nicooow
7ecf8b755e Add css var for meta theme-color attribute (#20558)
* add app-theme-color var

* Fix Prettier format
2024-04-22 09:51:21 +02:00
Matthias Alphart
141107f1f3 Apply initial values to forms of device trigger extra fields (#20555)
* Apply default values of device trigger extra fields

* use computeInitialHaFormData
2024-04-22 09:48:50 +02:00
karwosts
b5277dee53 When munging statistics to history, assume always numeric (#20544) 2024-04-22 09:41:12 +02:00
Steve Repsher
4b593c1c96 Enhance webpack transform async plugin to use babel runtime (#20543) 2024-04-22 09:40:20 +02:00
dependabot[bot]
50ce1b94c8 Bump actions/upload-artifact from 4.3.1 to 4.3.2 (#20575)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-22 09:22:38 +02:00
dependabot[bot]
8bf27a83ec Bump actions/checkout from 4.1.2 to 4.1.3 (#20576)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-22 09:21:14 +02:00
renovate[bot]
389f0d3d23 Update dependency marked to v12.0.2 (#20577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-22 09:19:52 +02:00
karwosts
b966601e6a Hide beta toggle when unsupervised (#20573) 2024-04-21 09:50:14 +02:00
renovate[bot]
f2a0881821 Update dependency @types/tar to v6.1.13 (#20572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-21 09:36:09 +02:00
renovate[bot]
50a49eae43 Update dependency date-fns-tz to v3.1.3 (#20552)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-20 14:23:57 -04:00
renovate[bot]
1c04561004 Update dependency @codemirror/commands to v6.4.0 (#20569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-20 19:39:54 +02:00
renovate[bot]
b80d94d260 Update dependency magic-string to v0.30.10 (#20568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-20 19:39:42 +02:00
karwosts
87012e23e7 Wrap unbreakable area names in ha-card header (#20566) 2024-04-20 19:39:24 +02:00
Simon Lamon
f39758b103 Only create sortable when not disabled (#20565) 2024-04-20 14:32:40 +02:00
G Johansson
697bbf428e Add translation to integration setup failures (#19128) 2024-04-20 09:16:36 +02:00
renovate[bot]
c7444a2605 Update dependency @octokit/auth-oauth-device to v7.1.1 (#20560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-20 09:03:01 +02:00
karwosts
3a5f4d33d2 Fix stopped_unknown_reason localization message (#20557)
* Fix stopped_unknown_reason localization message
2024-04-20 08:45:05 +02:00
renovate[bot]
c3dc62523b Update dependency core-js to v3.37.0 (#20559) 2024-04-19 20:45:00 -04:00
renovate[bot]
424622061a Lock file maintenance (#20553) 2024-04-19 00:47:42 -04:00
renovate[bot]
a3b021b11d Update dependency @material/web to v1.4.1 (#20551) 2024-04-19 00:12:18 -04:00
renovate[bot]
b60ad8b143 Update typescript-eslint monorepo to v7.7.0 (#20549) 2024-04-18 18:55:17 -04:00
J. Nick Koston
e376efc579 Remove legacy state translations (#20536)
* Remove legacy state translations

https://github.com/home-assistant/core/pull/112023
2024-04-18 06:33:07 +02:00
renovate[bot]
382035a1d4 Update dependency @types/color-name to v1.1.4 (#20546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-17 18:55:59 -04:00
renovate[bot]
542e22fe0e Update dependency tar to v7.0.1 (#20547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-17 22:54:57 +00:00
karwosts
af37d57779 Fix entity picker delete entity (#20542) 2024-04-17 19:48:25 +02:00
renovate[bot]
fbef0b0186 Update dependency gulp to v5 (#20305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-17 12:31:10 -04:00
renovate[bot]
9e67d6add8 Update dependency @types/leaflet to v1.9.11 (#20538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-17 18:21:59 +02:00
Simon Lamon
25c702ad2b Don't display keyboard shortcut hints in quickbar if keyboard shortcuts are disabled (#20527)
Fix forgotten hint shortcut tip
2024-04-15 20:20:45 +02:00
Bram Kragten
6516597c93 Fix zones on mobile, align mobile and non mobile view (#20525)
* Fix zones on mobile, align mobile and non mobile view

* Fix CI

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-04-15 20:20:14 +02:00
renovate[bot]
1df9c38a8c Update dependency tar to v7 (#20513)
* Update dependency tar to v7

* Update fetch-nightly-translations.js

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-04-15 13:40:38 +00:00
renovate[bot]
bd7217145a Update dependency element-internals-polyfill to v1.3.11 (#20512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 15:07:09 +02:00
renovate[bot]
569fef38a4 Update vaadinWebComponents monorepo to v24.3.11 (#20523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 15:03:46 +02:00
Thomas Steiner
f21c89cf1a Make allow attribute configurable in iframe panel (#19087)
* Make allow attribute configurable in iframe panel

* Delete .vscode/settings.json

* Update ha-panel-iframe.ts

* Don't quote

* Update src/panels/iframe/ha-panel-iframe.ts

Co-authored-by: Quentame <polletquentin74@me.com>

* Make `allow` configurable for `hui-iframe-card`

* Update src/panels/iframe/ha-panel-iframe.ts

Co-authored-by: Quentame <polletquentin74@me.com>

* Update src/panels/lovelace/cards/hui-iframe-card.ts

Co-authored-by: Quentame <polletquentin74@me.com>

* Update src/panels/iframe/ha-panel-iframe.ts

Co-authored-by: Quentame <polletquentin74@me.com>

* Update src/panels/lovelace/cards/hui-iframe-card.ts

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

* Delete src/panels/iframe/ha-panel-iframe.ts

* Restore dev

* Update ha-panel-iframe.ts

* Prettier

---------

Co-authored-by: Quentame <polletquentin74@me.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-04-15 14:48:57 +02:00
renovate[bot]
02cc418969 Update CodeMirror (#20520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 09:21:07 +00:00
Cougar
4faba159c0 ZHA (Zigbee) visualization enhancement (#20511) 2024-04-15 11:09:16 +02:00
Adam Kapos
29816e6c5e Fix malformed CSS in dialog surface (#20519) 2024-04-15 11:06:07 +02:00
karwosts
5317a11c39 Sort custom cards in card picker (#20517) 2024-04-14 22:32:35 +02:00
Simon Lamon
27c53b3241 Replace paper-listbox in zone area (#19955)
* ha-config-zone

* Fixes

* add selected event back

* Fixes

* remove leftover paper-item css rule

* Fixup merge conflict
2024-04-14 13:46:38 +02:00
renovate[bot]
919befa961 Update dependency typescript to v5.4.5 (#20510) 2024-04-13 20:43:27 -04:00
renovate[bot]
f9c02ed099 Update dependency date-fns-tz to v3.0.1 (#20509) 2024-04-13 20:38:12 -04:00
karwosts
b35c325f43 Rebuild stack card when a child card rebuilds (#19861) 2024-04-12 20:53:04 +02:00
karwosts
b82f1128fe Location/zone editor updates (#19994) 2024-04-12 20:51:23 +02:00
karwosts
178feb7330 Create helpers from automation editor (#19287)
* Create helpers from automation editor

* support multiple createDomains

* localization

* fix lint

* Move multi domain to entity picker

* Update dialog-helper-detail.ts

* Update ha-config-helpers.ts

* optimize a little

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-04-12 18:38:39 +00:00
karwosts
0118a5bf4c Allow customizing display name for energy device (#20033)
* Allow customizing display name for energy device

* use display name in device settings list
2024-04-12 20:31:59 +02:00
renovate[bot]
e0087bd142 Update dependency @material/web to v1.4.0 (#20177)
* Update dependency @material/web to v1.4.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-04-12 15:15:33 +00:00
renovate[bot]
c2d3e7900e Update date-fns to v3 (major) (#20504)
* Update date-fns to v3

* update imports

* breaking changes

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-04-12 17:03:30 +02:00
Charles Garwood
fb8312110b Add support for setting label description (#20421)
* Add support for setting label description
2024-04-12 15:49:07 +02:00
renovate[bot]
16de57342e Update dependency eslint-plugin-wc to v2.1.0 (#20500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-12 08:23:48 -04:00
renovate[bot]
ad6e041c04 Update dependency @codemirror/view to v6.26.2 (#20505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-12 13:10:15 +02:00
Steve Repsher
e22e3e88a0 Speed up and simplify translations build (#19988)
* Speed up and simplify translations build

- Remove use of gulp-flatmap for merges (wasted input) and just loop over translation files.
- Parse and buffer master only once for all merges.
- Remove lokalise key reference transform from non-English files. This is already done by Lokalise when they are downloaded.
- Remove tabs from merged output to minimize buffer sizes.
- Pipe merges to a hashing stream, removing extra tasks and intermediate file I/O.
- Pipe hashed files to a single custom asynchronous transform stream to fragmentize the files. It expands the stream to push a new file for each fragment.
- Incorporate flattening into fragmentization.
- Delete entire ui.panel key for base translation (instead of leaving an empty object).
- Optimize flatten method to stop copying output over and over.
- Convert empty and test filters to JSON.parse() revivers for simplicity and better performance.
- Incorporate supervisor builds into main tasks using a simple toggle (i.e. remove duplicate code).
- Funcify local tasks and simplify exported tasks.
- Incorporate test metadata task into a simplified metadata task.

* Fix Lokalise key reference link

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

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-04-12 12:49:18 +02:00
Steve Repsher
dc8a50965c Group date-fns packages (#20499) 2024-04-12 12:22:18 +02:00
Bram Kragten
1914de7ddf Bumped version to 20240404.2 2024-04-12 11:21:05 +02:00
Simon Lamon
2e505cfb1f Add spacing between icon and name in entity button bar (#20492)
* Fix width between icon and name

* Remove no-text
2024-04-12 11:20:15 +02:00
Bram Kragten
ab49aca815 Handle errors in multi select (#20494) 2024-04-12 11:19:57 +02:00
Bram Kragten
c96968e476 Fix issues with application credentials (#20495) 2024-04-12 11:19:31 +02:00
Simon Lamon
8f050516ec Fix missing argument in voice assistant expose search label (#20491) 2024-04-12 11:18:47 +02:00
Simon Lamon
27d2b244a4 Add spacing between icon and name in entity button bar (#20492)
* Fix width between icon and name

* Remove no-text
2024-04-12 11:17:51 +02:00
Bram Kragten
be2f2c6271 Handle errors in multi select (#20494) 2024-04-12 11:15:15 +02:00
Bram Kragten
8dc2797b16 Fix issues with application credentials (#20495) 2024-04-12 11:10:23 +02:00
renovate[bot]
7ca8dabc44 Update typescript-eslint monorepo to v7.6.0 (#20497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-11 16:32:52 -04:00
Bram Kragten
baeb55e217 Merge branch 'master' into dev 2024-04-11 11:16:42 +02:00
Simon Lamon
a8502fcc11 Fix missing argument in voice assistant expose search label (#20491) 2024-04-11 11:10:02 +02:00
Paulus Schoutsen
9f5bc5b196 Match on correct entity ID of HA conversation agent (#20484) 2024-04-10 11:41:12 +02:00
Bruno Pantaleão Gonçalves
7556ab9506 Add css var for header webkit backdrop filter (#20473)
* Add CSS var for header -webkit-backdrop-filter

* Add backdrop to energy and developer tools
2024-04-10 11:40:44 +02:00
Adam Kapos
bf176ac314 Make it possible for themes to blur backgrounds (#20447)
* Make it possible for themes to blur card backgrounds

* Make it possible for themes to blur dialog backgrounds

* Add ha prefix

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

* Rename dialog-backdrop-filter to ha-dialog-scrim-backdrop-filter

With backwards compatibility

* Run prettier

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-04-10 11:37:35 +02:00
Adam Kapos
9903e22eaa Change tile to display "last triggered" in relative time (#20463) 2024-04-09 14:11:58 +02:00
Simon Lamon
1e0f7d9629 Don't display keyboard shortcut hints if keyboard shortcuts are disabled (#20456)
* Shortcut hints

* Prettier
2024-04-09 14:10:59 +02:00
Charles Garwood
e8a140af44 Don't duplicate the label as the description in Z-Wave Config (#20454)
Don'tduplicate the label as the description
2024-04-09 14:09:21 +02:00
Bram Kragten
b091d4f298 Write log on translation error (#20430)
* Write log on translation error

* Update ha-config-devices-dashboard.ts

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-04-09 13:25:27 +02:00
renovate[bot]
35cf3063cb Update dependency @lokalise/node-api to v12.4.0 (#20252) 2024-04-08 20:49:48 -04:00
renovate[bot]
7141ef17be Update dependency @codemirror/legacy-modes to v6.4.0 (#20475) 2024-04-08 20:36:26 -04:00
renovate[bot]
be2c68c0bb Update octokit monorepo (#20453) 2024-04-08 20:32:57 -04:00
renovate[bot]
7c944d3767 Update dependency mocha to v10.4.0 (#20279) 2024-04-08 20:29:48 -04:00
renovate[bot]
1d4f02df2e Update dependency glob to v10.3.12 (#20298) 2024-04-08 20:28:30 -04:00
renovate[bot]
2007a74a20 Update babel monorepo to v7.24.4 (#20451) 2024-04-08 20:23:53 -04:00
renovate[bot]
8c0839ad57 Update dependency @types/tar to v6.1.12 (#20457) 2024-04-08 20:22:31 -04:00
renovate[bot]
516b9a54c4 Update dependency typescript to v5.4.4 (#20468) 2024-04-08 20:21:33 -04:00
renovate[bot]
0d3e730c9c Update dependency magic-string to v0.30.9 (#20465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-08 09:33:26 +02:00
renovate[bot]
c7a87d02b2 Update dependency @types/leaflet to v1.9.9 (#20452) 2024-04-07 20:37:42 +02:00
Bram Kragten
dd082c204b Remove unused type (#20429) 2024-04-05 12:22:47 +02:00
renovate[bot]
c4af3d1579 Update typescript-eslint monorepo to v7.5.0 (#20426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 11:36:28 +02:00
Bram Kragten
10eadbcbbb Merge branch 'dev' 2024-04-04 21:31:51 +02:00
Bram Kragten
17141824f7 Bumped version to 20240404.1 2024-04-04 21:31:30 +02:00
Bram Kragten
4cfd6c010f Fix overflow on submenus (#20415) 2024-04-04 19:54:36 +02:00
Bram Kragten
daa9024bff Make pages full height 2024-04-04 17:16:36 +02:00
Bram Kragten
e96aca90fe 20240404.0 (#20414) 2024-04-04 16:23:24 +02:00
Bram Kragten
0580a31961 Bumped version to 20240404.0 2024-04-04 16:16:30 +02:00
Bram Kragten
5c42c5130c Add count of items (#20410)
* Add count of items

* Adjust layout, correct filter count
2024-04-04 16:15:13 +02:00
Bram Kragten
72d1e37a23 Fix integration filter search (#20408) 2024-04-04 13:26:26 +02:00
Bram Kragten
61c9072a08 Fix icons in entity settings (#20405) 2024-04-04 13:00:14 +02:00
Bram Kragten
08b25f9c2a Add floor and label support to describe action (#20403) 2024-04-04 13:00:05 +02:00
Samuel Schultze
1a03b49700 Fix calendar range selection (#20394)
fix: calendar range selector
2024-04-04 12:59:54 +02:00
Paul Bottein
2d4a8e2e45 Fix search input outlined background color and margin (#20407) 2024-04-04 12:53:03 +02:00
Bram Kragten
8486377604 Fix z-index create category dialog (#20406) 2024-04-04 10:51:04 +00:00
Bram Kragten
4326519a3f 20240403.1 (#20380) 2024-04-03 16:58:47 +02:00
Bram Kragten
962b30adb9 20240403.0 (#20370) 2024-04-03 14:50:16 +02:00
Bram Kragten
29eb73176a 20240402.2 (#20348) 2024-04-02 23:34:17 +02:00
Bram Kragten
4f1cf1110f 20240402.1 (#20326) 2024-04-02 16:41:12 +02:00
Bram Kragten
d3bf0da289 20240402.0 (#20314) 2024-04-02 11:44:05 +02:00
Paul Bottein
fd06d434f2 20240329.1 (#20280) 2024-03-29 21:25:10 +01:00
Paul Bottein
d24d29e42f 20240329.0 (#20277) 2024-03-29 19:10:34 +01:00
Paul Bottein
e02a47a16a 20240328.0 (#20250) 2024-03-28 16:49:01 +01:00
Bram Kragten
795c16a941 20240327.0 (#20210) 2024-03-27 17:52:08 +01:00
394 changed files with 12925 additions and 9125 deletions

View File

@@ -115,6 +115,7 @@
}
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-names": "warn",
"lit/attribute-value-entities": "off",
"lit/no-template-map": "off",
"lit/no-native-attributes": "warn",
@@ -125,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.2
uses: actions/checkout@v4.1.6
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.2
uses: actions/checkout@v4.1.6
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.2
uses: actions/checkout@v4.1.6
- 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.2
uses: actions/checkout@v4.1.6
- 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.2
uses: actions/checkout@v4.1.6
- name: Setup Node
uses: actions/setup-node@v4.0.2
with:
@@ -89,7 +89,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4.3.3
with:
name: frontend-bundle-stats
path: build/stats/*.json
@@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.6
- name: Setup Node
uses: actions/setup-node@v4.0.2
with:
@@ -113,7 +113,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4.3.3
with:
name: supervisor-bundle-stats
path: build/stats/*.json

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.6
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.2
uses: actions/checkout@v4.1.6
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.2
uses: actions/checkout@v4.1.6
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.2
uses: actions/checkout@v4.1.6
- 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.2
uses: actions/checkout@v4.1.6
- 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.2
uses: actions/checkout@v4.1.6
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
@@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4.3.3
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4.3.3
with:
name: translations
path: translations.tar.gz

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send bundle stats and build information to RelativeCI
uses: relative-ci/agent-action@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.2
uses: actions/checkout@v4.1.6
- 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.4
uses: softprops/action-gh-release@v2.0.5
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.2
uses: actions/checkout@v4.1.6
- 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.1.1.cjs
yarnPath: .yarn/releases/yarn-4.2.2.cjs

View File

@@ -1,7 +1,56 @@
import defineProvider from "@babel/helper-define-polyfill-provider";
import { join } from "node:path";
import paths from "../paths.cjs";
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
// List of polyfill keys with supported browser targets for the functionality
const PolyfillSupport = {
// Note states and shadowRoot properties should be supported.
"element-internals": {
android: 90,
chrome: 90,
edge: 90,
firefox: 126,
ios: 17.4,
opera: 76,
opera_mobile: 64,
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,
@@ -13,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,
@@ -24,17 +98,67 @@ const PolyfillSupport = {
safari: 10.0,
samsung: 5.0,
},
"resize-observer": {
android: 64,
chrome: 64,
edge: 79,
firefox: 69,
ios: 13.4,
opera: 51,
opera_mobile: 47,
safari: 13.1,
samsung: 9.0,
},
};
// Map of global variables and/or instance and static properties to the
// corresponding polyfill key and actual module to import
const polyfillMap = {
global: {
Proxy: { key: "proxy", module: "proxy-polyfill" },
fetch: { key: "fetch", module: "unfetch/polyfill" },
Proxy: { key: "proxy", module: "proxy-polyfill" },
ResizeObserver: {
key: "resize-observer",
module: join(POLYFILL_DIR, "resize-observer.ts"),
},
},
instance: {
attachInternals: {
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") },
])
),
},
},
instance: {},
static: {},
};
// Create plugin using the same factory as for CoreJS
@@ -42,14 +166,16 @@ 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);
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
debug(polyfill.desc.key);
utils.injectGlobalImport(polyfill.desc.module);
return true;
}
return false;
},
};
}

View File

@@ -3,6 +3,8 @@ const env = require("./env.cjs");
const paths = require("./paths.cjs");
const { dependencies } = require("../package.json");
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");
// GitHub base URL to use for production source maps
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
module.exports.sourceMapURL = () => {
@@ -100,22 +102,12 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
],
plugins: [
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.cjs"
),
path.join(BABEL_PLUGINS, "inline-constants-plugin.cjs"),
{
modules: ["@mdi/js"],
ignoreModuleNotFound: true,
},
],
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/custom-polyfill-plugin.js"
),
{ method: "usage-global" },
],
// Minify template literals for production
isProdBuild && [
"template-html-minifier",
@@ -153,6 +145,27 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
],
sourceMaps: !isTestBuild,
overrides: [
{
// Add plugin to inject various polyfills, excluding the polyfills
// themselves to prevent self-injection.
plugins: [
[
path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"),
{ method: "usage-global" },
],
],
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",
"proxy-polyfill",
"unfetch",
].map((p) => new RegExp(`/node_modules/${p}/`)),
],
},
{
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills

View File

@@ -9,7 +9,7 @@ import gulp from "gulp";
import jszip from "jszip";
import path from "path";
import process from "process";
import tar from "tar";
import { extract } from "tar";
const MAX_AGE = 24; // hours
const OWNER = "home-assistant";
@@ -156,7 +156,7 @@ gulp.task("fetch-nightly-translations", async function () {
console.log("Unpacking downloaded translations...");
const zip = await jszip.loadAsync(downloadResponse.data);
await deleteCurrent;
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.extract());
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract());
await new Promise((resolve, reject) => {
extractStream.on("close", resolve).on("error", reject);
});

View File

@@ -1,92 +1,112 @@
import { createHash } from "crypto";
import { deleteSync } from "del";
import { mkdirSync, readdirSync, readFileSync, renameSync } from "fs";
import { writeFile } from "node:fs/promises";
/* eslint-disable max-classes-per-file */
import { deleteAsync } from "del";
import { glob } from "glob";
import gulp from "gulp";
import flatmap from "gulp-flatmap";
import transform from "gulp-json-transform";
import merge from "gulp-merge-json";
import rename from "gulp-rename";
import path from "path";
import vinylBuffer from "vinyl-buffer";
import source from "vinyl-source-stream";
import merge from "lodash.merge";
import { createHash } from "node:crypto";
import { mkdir, readFile } from "node:fs/promises";
import { basename, join } from "node:path";
import { PassThrough, Transform } from "node:stream";
import { finished } from "node:stream/promises";
import env from "../env.cjs";
import paths from "../paths.cjs";
import { mapFiles } from "../util.cjs";
import "./fetch-nightly-translations.js";
const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend";
const workDir = "build/translations";
const fullDir = workDir + "/full";
const coreDir = workDir + "/core";
const outDir = workDir + "/output";
const outDir = join(workDir, "output");
const EN_SRC = join(paths.translations_src, "en.json");
const TEST_LOCALE = "en-x-test";
let mergeBackend = false;
gulp.task(
"translations-enable-merge-backend",
gulp.parallel((done) => {
gulp.parallel(async () => {
mergeBackend = true;
done();
}, "allow-setup-fetch-nightly-translations")
);
// Panel translations which should be split from the core translations.
const TRANSLATION_FRAGMENTS = Object.keys(
JSON.parse(
readFileSync(
path.resolve(paths.polymer_dir, "src/translations/en.json"),
"utf-8"
)
).ui.panel
);
// Transform stream to apply a function on Vinyl JSON files (buffer mode only).
// The provided function can either return a new object, or an array of
// [object, subdirectory] pairs for fragmentizing the JSON.
class CustomJSON extends Transform {
constructor(func, reviver = null) {
super({ objectMode: true });
this._func = func;
this._reviver = reviver;
}
function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach((key) => {
if (typeof data[key] === "object") {
output = {
...output,
...recursiveFlatten(prefix + key + ".", data[key]),
};
async _transform(file, _, callback) {
try {
let obj = JSON.parse(file.contents.toString(), this._reviver);
if (this._func) obj = this._func(obj, file.path);
for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
const outFile = file.clone({ contents: false });
outFile.contents = Buffer.from(JSON.stringify(outObj));
outFile.dirname += `/${dir}`;
this.push(outFile);
}
callback(null);
} catch (err) {
callback(err);
}
}
}
// Transform stream to merge Vinyl JSON files (buffer mode only).
class MergeJSON extends Transform {
_objects = [];
constructor(stem, startObj = {}, reviver = null) {
super({ objectMode: true, allowHalfOpen: false });
this._stem = stem;
this._startObj = structuredClone(startObj);
this._reviver = reviver;
}
async _transform(file, _, callback) {
try {
this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
if (!this._outFile) this._outFile = file.clone({ contents: false });
callback(null);
} catch (err) {
callback(err);
}
}
async _flush(callback) {
try {
const mergedObj = merge(this._startObj, ...this._objects);
this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
this._outFile.stem = this._stem;
callback(null, this._outFile);
} catch (err) {
callback(err);
}
}
}
// Utility to flatten object keys to single level using separator
const flatten = (data, prefix = "", sep = ".") => {
const output = {};
for (const [key, value] of Object.entries(data)) {
if (typeof value === "object") {
Object.assign(output, flatten(value, prefix + key + sep, sep));
} else {
output[prefix + key] = data[key];
output[prefix + key] = value;
}
});
}
return output;
}
};
function flatten(data) {
return recursiveFlatten("", data);
}
function emptyFilter(data) {
const newData = {};
Object.keys(data).forEach((key) => {
if (data[key]) {
if (typeof data[key] === "object") {
newData[key] = emptyFilter(data[key]);
} else {
newData[key] = data[key];
}
}
});
return newData;
}
function recursiveEmpty(data) {
const newData = {};
Object.keys(data).forEach((key) => {
if (data[key]) {
if (typeof data[key] === "object") {
newData[key] = recursiveEmpty(data[key]);
} else {
newData[key] = "TRANSLATED";
}
}
});
return newData;
}
// Filter functions that can be passed directly to JSON.parse()
const emptyReviver = (_key, value) => value || undefined;
const testReviver = (_key, value) =>
value && typeof value === "string" ? "TRANSLATED" : value;
/**
* Replace Lokalise key placeholders with their actual values.
@@ -95,60 +115,44 @@ function recursiveEmpty(data) {
* be included in src/translations/en.json, but still be usable while
* developing locally.
*
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
* @link https://docs.lokalise.com/en/articles/1400528-key-referencing
*/
const re_key_reference = /\[%key:([^%]+)%\]/;
function lokaliseTransform(data, original, file) {
const KEY_REFERENCE = /\[%key:([^%]+)%\]/;
const lokaliseTransform = (data, path, original = data) => {
const output = {};
Object.entries(data).forEach(([key, value]) => {
if (value instanceof Object) {
output[key] = lokaliseTransform(value, original, file);
for (const [key, value] of Object.entries(data)) {
if (typeof value === "object") {
output[key] = lokaliseTransform(value, path, original);
} else {
output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
output[key] = value.replace(KEY_REFERENCE, (_match, lokalise_key) => {
const replace = lokalise_key.split("::").reduce((tr, k) => {
if (!tr) {
throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
}
return tr[k];
}, original);
if (typeof replace !== "string") {
throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
}
return replace;
});
}
});
}
return output;
}
};
gulp.task("clean-translations", async () => deleteSync([workDir]));
gulp.task("clean-translations", () => deleteAsync([workDir]));
gulp.task("ensure-translations-build-dir", async () => {
mkdirSync(workDir, { recursive: true });
});
const makeWorkDir = () => mkdir(workDir, { recursive: true });
gulp.task("create-test-metadata", () =>
env.isProdBuild()
? Promise.resolve()
: writeFile(
workDir + "/testMetadata.json",
JSON.stringify({ test: { nativeName: "Test" } })
)
);
gulp.task("create-test-translation", () =>
const createTestTranslation = () =>
env.isProdBuild()
? Promise.resolve()
: gulp
.src(path.join(paths.translations_src, "en.json"))
.pipe(transform((data, _file) => recursiveEmpty(data)))
.pipe(rename("test.json"))
.pipe(gulp.dest(workDir))
);
.src(EN_SRC)
.pipe(new CustomJSON(null, testReviver))
.pipe(rename(`${TEST_LOCALE}.json`))
.pipe(gulp.dest(workDir));
/**
* This task will build a master translation file, to be used as the base for
@@ -159,279 +163,164 @@ gulp.task("create-test-translation", () =>
* project is buildable immediately after merging new translation keys, since
* the Lokalise update to translations/en.json will not happen immediately.
*/
gulp.task("build-master-translation", () => {
const src = [path.join(paths.translations_src, "en.json")];
const createMasterTranslation = () =>
gulp
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
.pipe(new CustomJSON(lokaliseTransform))
.pipe(new MergeJSON("en"))
.pipe(gulp.dest(workDir));
if (mergeBackend) {
src.push(path.join(inBackendDir, "en.json"));
const FRAGMENTS = ["base"];
const toggleSupervisorFragment = async () => {
FRAGMENTS[0] = "supervisor";
};
const panelFragment = (fragment) =>
fragment !== "base" && fragment !== "supervisor";
const HASHES = new Map();
const createTranslations = async () => {
// Parse and store the master to avoid repeating this for each locale, then
// add the panel fragments when processing the app.
const enMaster = JSON.parse(await readFile(`${workDir}/en.json`, "utf-8"));
if (FRAGMENTS[0] === "base") {
FRAGMENTS.push(...Object.keys(enMaster.ui.panel));
}
return gulp
.src(src)
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
// The downstream pipeline is setup first. It hashes the merged data for
// each locale, then fragmentizes and flattens the data for final output.
const translationFiles = await glob([
`${inFrontendDir}/!(en).json`,
...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
]);
const hashStream = new Transform({
objectMode: true,
transform: async (file, _, callback) => {
const hash = env.isProdBuild()
? createHash("md5").update(file.contents).digest("hex")
: "dev";
HASHES.set(file.stem, hash);
file.stem += `-${hash}`;
callback(null, file);
},
}).setMaxListeners(translationFiles.length + 1);
const fragmentsStream = hashStream
.pipe(
merge({
fileName: "en.json",
})
)
.pipe(gulp.dest(fullDir));
});
gulp.task("build-merged-translations", () =>
gulp
.src([
inFrontendDir + "/*.json",
"!" + inFrontendDir + "/en.json",
...(env.isProdBuild() ? [] : [workDir + "/test.json"]),
])
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
flatmap((stream, file) => {
// For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
//
// TODO: This is a naive interpretation of BCP47 that should be improved.
// Will be OK for now as long as we don't have anything more complicated
// than a base translation + region.
const tr = path.basename(file.history[0], ".json");
const subtags = tr.split("-");
const src = [fullDir + "/en.json"];
for (let i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join("-");
if (lang === "test") {
src.push(workDir + "/test.json");
} else if (lang !== "en") {
src.push(inFrontendDir + "/" + lang + ".json");
if (mergeBackend) {
src.push(inBackendDir + "/" + lang + ".json");
}
new CustomJSON((data) =>
FRAGMENTS.map((fragment) => {
switch (fragment) {
case "base":
// Remove the panels and supervisor to create the base translations
return [
flatten({
...data,
ui: { ...data.ui, panel: undefined },
supervisor: undefined,
}),
"",
];
case "supervisor":
// Supervisor key is at the top level
return [flatten(data.supervisor), ""];
default:
// Create a fragment with only the given panel
return [
flatten(data.ui.panel[fragment], `ui.panel.${fragment}.`),
fragment,
];
}
}
return gulp
.src(src, { allowEmpty: true })
.pipe(transform((data) => emptyFilter(data)))
.pipe(
merge({
fileName: tr + ".json",
})
)
.pipe(gulp.dest(fullDir));
})
)
);
let taskName;
const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, () =>
// Return only the translations for this fragment.
gulp
.src(fullDir + "/*.json")
.pipe(
transform((data) => ({
ui: {
panel: {
[fragment]: data.ui.panel[fragment],
},
},
}))
)
.pipe(gulp.dest(workDir + "/" + fragment))
);
splitTasks.push(taskName);
});
taskName = "build-translation-core";
gulp.task(taskName, () =>
// Remove the fragment translations from the core translation.
gulp
.src(fullDir + "/*.json")
.pipe(
transform((data, _file) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
delete data.supervisor;
return data;
})
)
.pipe(gulp.dest(coreDir))
);
splitTasks.push(taskName);
gulp.task("build-flattened-translations", () =>
// Flatten the split versions of our translations, and move them into outDir
gulp
.src(
TRANSLATION_FRAGMENTS.map(
(fragment) => workDir + "/" + fragment + "/*.json"
).concat(coreDir + "/*.json"),
{ base: workDir }
)
.pipe(
transform((data) =>
// Polymer.AppLocalizeBehavior requires flattened json
flatten(data)
})
)
)
.pipe(
rename((filePath) => {
if (filePath.dirname === "core") {
filePath.dirname = "";
.pipe(gulp.dest(outDir));
// Send the English master downstream first, then for each other locale
// generate merged JSON data to continue piping. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
//
// TODO: This is a naive interpretation of BCP47 that should be improved.
// Will be OK for now as long as we don't have anything more complicated
// than a base translation + region.
gulp
.src(`${workDir}/en.json`)
.pipe(new PassThrough({ objectMode: true }))
.pipe(hashStream, { end: false });
const mergesFinished = [];
for (const translationFile of translationFiles) {
const locale = basename(translationFile, ".json");
const subtags = locale.split("-");
const mergeFiles = [];
for (let i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join("-");
if (lang === TEST_LOCALE) {
mergeFiles.push(`${workDir}/${TEST_LOCALE}.json`);
} else if (lang !== "en") {
mergeFiles.push(`${inFrontendDir}/${lang}.json`);
if (mergeBackend) {
mergeFiles.push(`${inBackendDir}/${lang}.json`);
}
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
})
)
.pipe(gulp.dest(outDir))
);
const fingerprints = {};
gulp.task("build-translation-fingerprints", () => {
// Fingerprint full file of each language
const files = readdirSync(fullDir);
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
? createHash("md5")
.update(readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex")
: "dev",
};
}
// In dev we create the file with the fake hash in the filename
if (env.isProdBuild()) {
mapFiles(outDir, ".json", (filename) => {
const parsed = path.parse(filename);
// nl.json -> nl-<hash>.json
if (!(parsed.name in fingerprints)) {
throw new Error(`Unable to find hash for ${filename}`);
}
renameSync(
filename,
`${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${
parsed.ext
}`
);
});
}
const mergeStream = gulp
.src(mergeFiles, { allowEmpty: true })
.pipe(new MergeJSON(locale, enMaster, emptyReviver));
mergesFinished.push(finished(mergeStream));
mergeStream.pipe(hashStream, { end: false });
}
const stream = source("translationFingerprints.json");
stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
});
// Wait for all merges to finish, then it's safe to end writing to the
// downstream pipeline and wait for all fragments to finish writing.
await Promise.all(mergesFinished);
hashStream.end();
await finished(fragmentsStream);
};
gulp.task("build-translation-fragment-supervisor", () =>
const writeTranslationMetaData = () =>
gulp
.src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor))
.src([`${paths.translations_src}/translationMetadata.json`])
.pipe(
rename((filePath) => {
// In dev we create the file with the fake hash in the filename
new CustomJSON((meta) => {
// Add the test translation in development.
if (!env.isProdBuild()) {
filePath.basename += "-dev";
meta[TEST_LOCALE] = { nativeName: "Translation Test" };
}
})
)
.pipe(gulp.dest(workDir + "/supervisor"))
);
gulp.task("build-translation-flatten-supervisor", () =>
gulp
.src(workDir + "/supervisor/*.json")
.pipe(
transform((data) =>
// Polymer.AppLocalizeBehavior requires flattened json
flatten(data)
)
)
.pipe(gulp.dest(outDir))
);
gulp.task("build-translation-write-metadata", () =>
gulp
.src([
path.join(paths.translations_src, "translationMetadata.json"),
...(env.isProdBuild() ? [] : [workDir + "/testMetadata.json"]),
workDir + "/translationFingerprints.json",
])
.pipe(merge({}))
.pipe(
transform((data) => {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (value.nativeName) {
newData[key] = value;
} else {
// Filter out locales without a native name, and add the hashes.
for (const locale of Object.keys(meta)) {
if (!meta[locale].nativeName) {
meta[locale] = undefined;
console.warn(
`Skipping language ${key}. Native name was not translated.`
`Skipping locale ${locale} because native name is not translated.`
);
} else {
meta[locale].hash = HASHES.get(locale);
}
});
return newData;
}
return {
fragments: FRAGMENTS.filter(panelFragment),
translations: meta,
};
})
)
.pipe(
transform((data) => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
}))
)
.pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir))
);
gulp.task(
"create-translations",
gulp.series(
gulp.parallel("create-test-metadata", "create-test-translation"),
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations"
)
);
.pipe(gulp.dest(workDir));
gulp.task(
"build-translations",
gulp.series(
gulp.parallel(
"fetch-nightly-translations",
gulp.series("clean-translations", "ensure-translations-build-dir")
gulp.series("clean-translations", makeWorkDir)
),
"create-translations",
"build-translation-fingerprints",
"build-translation-write-metadata"
createTestTranslation,
createMasterTranslation,
createTranslations,
writeTranslationMetaData
)
);
gulp.task(
"build-supervisor-translations",
gulp.series(
gulp.parallel(
"fetch-nightly-translations",
gulp.series("clean-translations", "ensure-translations-build-dir")
),
gulp.parallel("create-test-metadata", "create-test-translation"),
"build-master-translation",
"build-merged-translations",
"build-translation-fragment-supervisor",
"build-translation-flatten-supervisor",
"build-translation-fingerprints",
"build-translation-write-metadata"
)
gulp.series(toggleSupervisorFragment, "build-translations")
);

View File

@@ -99,7 +99,7 @@ gulp.task("webpack-watch-app", () => {
).watch({ poll: isWsl }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("create-translations", "copy-translations-app")
gulp.series("build-translations", "copy-translations-app")
);
});

View File

@@ -1,16 +0,0 @@
const path = require("path");
const fs = require("fs");
// Helper function to map recursively over files in a folder and it's subfolders
module.exports.mapFiles = function mapFiles(startPath, filter, mapFunc) {
const files = fs.readdirSync(startPath);
for (let i = 0; i < files.length; i++) {
const filename = path.join(startPath, files[i]);
const stat = fs.lstatSync(filename);
if (stat.isDirectory()) {
mapFiles(filename, filter, mapFunc);
} else if (filename.indexOf(filter) >= 0) {
mapFunc(filename);
}
}
};

View File

@@ -10,6 +10,7 @@ const WebpackBar = require("webpackbar");
const {
TransformAsyncModulesPlugin,
} = require("transform-async-modules-webpack-plugin");
const { dependencies } = require("../package.json");
const paths = require("./paths.cjs");
const bundle = require("./bundle.cjs");
@@ -156,11 +157,15 @@ const createWebpackConfig = ({
transform: (stats) => JSON.stringify(filterStats(stats)),
}),
!latestBuild &&
new TransformAsyncModulesPlugin({ browserslistEnv: "legacy" }),
new TransformAsyncModulesPlugin({
browserslistEnv: "legacy",
runtime: { version: dependencies["@babel/runtime"] },
}),
].filter(Boolean),
resolve: {
extensions: [".ts", ".js", ".json"],
alias: {
"lit/static-html$": "lit/static-html.js",
"lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.js",
"lit/directives/until$": "lit/directives/until.js",

View File

@@ -1,5 +1,5 @@
import "@material/mwc-button/mwc-button";
import { mdiCast, mdiCastConnected } from "@mdi/js";
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket";
@@ -104,8 +104,11 @@ class HcCast extends LitElement {
slot="item-icon"
></ha-icon>
`
: ""}
${view.title || view.path}
: html`<ha-svg-icon
slot="item-icon"
.path=${mdiViewDashboard}
></ha-svg-icon>`}
${view.title || view.path || "Unnamed view"}
</paper-icon-item>
`
)}
@@ -250,7 +253,8 @@ class HcCast extends LitElement {
padding-top: 0;
}
paper-listbox ha-icon {
paper-listbox ha-icon,
paper-listbox ha-svg-icon {
padding: 12px;
color: var(--secondary-text-color);
}

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

@@ -10,6 +10,7 @@ import {
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
import { HomeAssistant } from "../../src/types";
import { selectedDemoConfig } from "./configs/demo-configs";
import { mockAreaRegistry } from "./stubs/area_registry";
import { mockAuth } from "./stubs/auth";
import { mockConfigEntries } from "./stubs/config_entries";
import { mockEnergy } from "./stubs/energy";
@@ -23,10 +24,10 @@ import { mockLovelace } from "./stubs/lovelace";
import { mockMediaPlayer } from "./stubs/media_player";
import { mockPersistentNotification } from "./stubs/persistent_notification";
import { mockRecorder } from "./stubs/recorder";
import { mockTodo } from "./stubs/todo";
import { mockSensor } from "./stubs/sensor";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTodo } from "./stubs/todo";
import { mockTranslations } from "./stubs/translations";
@customElement("ha-demo")
@@ -62,6 +63,7 @@ export class HaDemo extends HomeAssistantAppEl {
mockEnergy(hass);
mockPersistentNotification(hass);
mockConfigEntries(hass);
mockAreaRegistry(hass);
mockEntityRegistry(hass, [
{
config_entry_id: "co2signal",

View File

@@ -1,4 +1,4 @@
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
import { format, startOfToday, startOfTomorrow } from "date-fns";
import {
EnergyInfo,
EnergyPreferences,

View File

@@ -19,15 +19,15 @@ export const mockLovelace = (
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
};
customElements.whenDefined("hui-view").then(() => {
customElements.whenDefined("hui-card").then(() => {
// eslint-disable-next-line
const HUIView = customElements.get("hui-view");
const HUIView = customElements.get("hui-card");
// Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView!.prototype.createCardElement;
const oldCreateCard = HUIView!.prototype.createElement;
HUIView!.prototype.createCardElement = function (config) {
HUIView!.prototype.createElement = function (config) {
const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") {
if (config.type === "custom:ha-demo-card") {
(el as HADemoCard).lovelace = this.lovelace;
}
return el;

View File

@@ -64,6 +64,12 @@ const ACTIONS = [
entity_id: "input_boolean.toggle_4",
},
},
{
sequence: [
{ scene: "scene.kitchen_morning" },
{ service: "light.turn_off", target: { entity_id: "light.kitchen" } },
],
},
{
parallel: [
{ scene: "scene.kitchen_morning" },
@@ -136,7 +142,7 @@ export class DemoAutomationDescribeAction extends LitElement {
<div class="action">
<span>
${this._action
? describeAction(this.hass, [], this._action)
? describeAction(this.hass, [], [], [], this._action)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
@@ -149,7 +155,7 @@ export class DemoAutomationDescribeAction extends LitElement {
${ACTIONS.map(
(conf) => html`
<div class="action">
<span>${describeAction(this.hass, [], conf as any)}</span>
<span>${describeAction(this.hass, [], [], [], conf as any)}</span>
<pre>${dump(conf)}</pre>
</div>
`

View File

@@ -20,6 +20,7 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
import { Action } from "../../../../src/data/script";
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
@@ -39,6 +40,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "If-Then", actions: [HaIfAction.defaultConfig] },
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
{ name: "Sequence", actions: [HaSequenceAction.defaultConfig] },
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
{ name: "Stop", actions: [HaStopAction.defaultConfig] },
];

View File

@@ -161,12 +161,14 @@ const LABELS: LabelRegistryEntry[] = [
name: "Energy",
icon: null,
color: "yellow",
description: null,
},
{
label_id: "entertainment",
name: "Entertainment",
icon: "mdi:popcorn",
color: "blue",
description: null,
},
];

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

@@ -2,6 +2,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, query } from "lit/decorators";
import { CoverEntityFeature } from "../../../../src/data/cover";
import { LightColorMode } from "../../../../src/data/light";
import { LockEntityFeature } from "../../../../src/data/lock";
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
@@ -20,6 +21,11 @@ const ENTITIES = [
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Unavailable entity",
}),
getEntity("lock", "front_door", "locked", {
friendly_name: "Front Door Lock",
device_class: "lock",
supported_features: LockEntityFeature.OPEN,
}),
getEntity("climate", "thermostat", "heat", {
current_temperature: 73,
min_temp: 45,
@@ -138,6 +144,24 @@ const CONFIGS = [
- type: "color-temp"
`,
},
{
heading: "Lock commands feature",
config: `
- type: tile
entity: lock.front_door
features:
- type: "lock-commands"
`,
},
{
heading: "Lock open door feature",
config: `
- type: tile
entity: lock.front_door
features:
- type: "lock-open-door"
`,
},
{
heading: "Vacuum commands feature",
config: `

View File

@@ -368,6 +368,7 @@ export class DemoEntityState extends LitElement {
hass.localize,
entry.stateObj,
hass.locale,
[], // numericDeviceClasses
hass.config,
hass.entities
)}`,

View File

@@ -36,6 +36,8 @@ const createConfigEntry = (
pref_disable_new_entities: false,
pref_disable_polling: false,
reason: null,
error_reason_translation_key: null,
error_reason_translation_placeholders: null,
...override,
});

View File

@@ -1,4 +1,7 @@
import { globIterate } from "glob";
import { availableParallelism } from "node:os";
process.env.UV_THREADPOOL_SIZE = availableParallelism();
const gulpImports = [];

View File

@@ -1,19 +1,19 @@
import { mdiStorePlus, mdiUpdate } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { mdiRefresh, mdiStorePlus } from "@mdi/js";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-fab";
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-subpage";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs";
import "./hassio-addons";
import "../../../src/layouts/hass-subpage";
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { fireEvent } from "../../../src/common/dom/fire_event";
@customElement("hassio-dashboard")
class HassioDashboard extends LitElement {
@@ -43,7 +43,7 @@ class HassioDashboard extends LitElement {
<ha-icon-button
slot="toolbar-icon"
@click=${this._handleCheckUpdates}
.path=${mdiUpdate}
.path=${mdiRefresh}
.label=${this.supervisor.localize("store.check_updates")}
></ha-icon-button>
<hassio-addons

View File

@@ -25,24 +25,24 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.24.1",
"@braintree/sanitize-url": "7.0.1",
"@codemirror/autocomplete": "6.15.0",
"@codemirror/commands": "6.3.3",
"@codemirror/language": "6.10.1",
"@codemirror/legacy-modes": "6.3.3",
"@babel/runtime": "7.24.7",
"@braintree/sanitize-url": "7.0.2",
"@codemirror/autocomplete": "6.16.2",
"@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.1",
"@codemirror/view": "6.27.0",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.12.3",
"@formatjs/intl-displaynames": "6.6.6",
"@formatjs/intl-datetimeformat": "6.12.5",
"@formatjs/intl-displaynames": "6.6.8",
"@formatjs/intl-getcanonicallocales": "2.3.0",
"@formatjs/intl-listformat": "7.5.5",
"@formatjs/intl-locale": "3.4.5",
"@formatjs/intl-numberformat": "8.10.1",
"@formatjs/intl-pluralrules": "5.2.12",
"@formatjs/intl-relativetimeformat": "11.2.12",
"@formatjs/intl-listformat": "7.5.7",
"@formatjs/intl-locale": "4.0.0",
"@formatjs/intl-numberformat": "8.10.3",
"@formatjs/intl-pluralrules": "5.2.14",
"@formatjs/intl-relativetimeformat": "11.2.14",
"@fullcalendar/core": "6.1.11",
"@fullcalendar/daygrid": "6.1.11",
"@fullcalendar/interaction": "6.1.11",
@@ -53,7 +53,7 @@
"@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",
@@ -70,7 +70,6 @@
"@material/mwc-list": "0.27.0",
"@material/mwc-menu": "0.27.0",
"@material/mwc-radio": "0.27.0",
"@material/mwc-ripple": "0.27.0",
"@material/mwc-select": "0.27.0",
"@material/mwc-snackbar": "0.27.0",
"@material/mwc-switch": "0.27.0",
@@ -81,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.3.0",
"@material/web": "1.5.0",
"@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47",
"@polymer/paper-item": "3.0.1",
@@ -89,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.10",
"@vaadin/vaadin-themable-mixin": "24.3.10",
"@vaadin/combo-box": "24.3.13",
"@vaadin/vaadin-themable-mixin": "24.3.13",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -98,28 +97,28 @@
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"chart.js": "4.4.2",
"chart.js": "4.4.3",
"color-name": "2.0.0",
"comlink": "4.4.1",
"core-js": "3.36.1",
"cropperjs": "1.6.1",
"date-fns": "2.30.0",
"date-fns-tz": "2.0.1",
"core-js": "3.37.1",
"cropperjs": "1.6.2",
"date-fns": "3.6.0",
"date-fns-tz": "3.1.3",
"deep-clone-simple": "1.1.1",
"deep-freeze": "0.0.1",
"element-internals-polyfill": "1.3.10",
"element-internals-polyfill": "1.3.11",
"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.2.1",
"home-assistant-js-websocket": "9.3.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.11",
"intl-messageformat": "10.5.14",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "1.0.4",
"lit": "2.8.0",
"luxon": "3.4.4",
"marked": "12.0.1",
"marked": "12.0.2",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
@@ -134,121 +133,118 @@
"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",
"vue": "2.7.16",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
"workbox-cacheable-response": "7.0.0",
"workbox-core": "7.0.0",
"workbox-expiration": "7.0.0",
"workbox-precaching": "7.0.0",
"workbox-routing": "7.0.0",
"workbox-strategies": "7.0.0",
"workbox-cacheable-response": "7.1.0",
"workbox-core": "7.1.0",
"workbox-expiration": "7.1.0",
"workbox-precaching": "7.1.0",
"workbox-routing": "7.1.0",
"workbox-strategies": "7.1.0",
"xss": "1.0.15"
},
"devDependencies": {
"@babel/core": "7.24.3",
"@babel/helper-define-polyfill-provider": "0.6.1",
"@babel/plugin-proposal-decorators": "7.24.1",
"@babel/plugin-transform-runtime": "7.24.3",
"@babel/preset-env": "7.24.3",
"@babel/preset-typescript": "7.24.1",
"@bundle-stats/plugin-webpack-filter": "4.12.2",
"@babel/core": "7.24.7",
"@babel/helper-define-polyfill-provider": "0.6.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.3.0",
"@octokit/auth-oauth-device": "7.0.1",
"@octokit/plugin-retry": "7.0.3",
"@octokit/rest": "20.0.2",
"@lokalise/node-api": "12.5.0",
"@octokit/auth-oauth-device": "7.1.1",
"@octokit/plugin-retry": "7.1.1",
"@octokit/rest": "20.1.1",
"@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "25.0.7",
"@rollup/plugin-commonjs": "25.0.8",
"@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.13",
"@types/chromecast-caf-sender": "1.0.9",
"@types/color-name": "1.1.3",
"@types/chromecast-caf-receiver": "6.0.14",
"@types/chromecast-caf-sender": "1.0.10",
"@types/color-name": "1.1.4",
"@types/glob": "8.1.0",
"@types/html-minifier-terser": "7.0.2",
"@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.8",
"@types/leaflet": "1.9.12",
"@types/leaflet-draw": "1.0.11",
"@types/lodash.merge": "4.6.9",
"@types/luxon": "3.4.2",
"@types/mocha": "10.0.6",
"@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4",
"@types/sortablejs": "1.15.8",
"@types/tar": "6.1.11",
"@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.4.0",
"@typescript-eslint/eslint-plugin": "7.12.0",
"@typescript-eslint/parser": "7.12.0",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "5.1.0",
"chai": "5.1.1",
"del": "7.1.0",
"eslint": "8.57.0",
"eslint-config-airbnb-base": "15.0.0",
"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.11.0",
"eslint-plugin-lit": "1.14.0",
"eslint-plugin-lit-a11y": "4.1.2",
"eslint-plugin-unused-imports": "3.1.0",
"eslint-plugin-wc": "2.0.4",
"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.3.10",
"gulp": "4.0.2",
"gulp-flatmap": "1.0.2",
"glob": "10.4.1",
"gulp": "5.0.0",
"gulp-json-transform": "0.5.0",
"gulp-merge-json": "2.2.1",
"gulp-rename": "2.0.0",
"gulp-zopfli-green": "6.0.1",
"html-minifier-terser": "7.2.0",
"husky": "9.0.11",
"instant-mocha": "1.5.2",
"jszip": "3.10.1",
"lint-staged": "15.2.2",
"lint-staged": "15.2.5",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.5.0",
"magic-string": "0.30.8",
"magic-string": "0.30.10",
"map-stream": "0.0.7",
"mocha": "10.3.0",
"mocha": "10.4.0",
"object-hash": "3.0.0",
"open": "10.1.0",
"pinst": "3.0.0",
"prettier": "3.2.5",
"prettier": "3.3.1",
"rollup": "2.79.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.12.0",
"serve-handler": "6.1.5",
"sinon": "17.0.1",
"sinon": "18.0.0",
"source-map-url": "0.4.1",
"systemjs": "6.14.3",
"tar": "6.2.1",
"systemjs": "6.15.1",
"tar": "7.2.0",
"terser-webpack-plugin": "5.3.10",
"transform-async-modules-webpack-plugin": "1.0.4",
"transform-async-modules-webpack-plugin": "1.1.1",
"ts-lit-plugin": "2.0.2",
"typescript": "5.4.3",
"vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0",
"typescript": "5.4.5",
"webpack": "5.91.0",
"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.0.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": {
@@ -257,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.1.1"
"packageManager": "yarn@4.2.2"
}

View File

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

View File

@@ -40,6 +40,11 @@
"matchPackageNames": ["tsparticles-engine"],
"matchPackagePrefixes": ["tsparticles-preset-"]
},
{
"description": "Group date-fns with dependent timezone package",
"groupName": "date-fns",
"matchPackageNames": ["date-fns", "date-fns-tz"]
},
{
"description": "Group and temporarily disable WDS packages",
"groupName": "Web Dev Server",

View File

@@ -1,4 +1,4 @@
import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
import { toZonedTime, fromZonedTime } from "date-fns-tz";
import { HassConfig } from "home-assistant-js-websocket";
import { FrontendLocaleData, TimeZone } from "../../data/translation";
@@ -8,10 +8,10 @@ const calcZonedDate = (
fn: (date: Date, options?: any) => Date | number | boolean,
options?
) => {
const inputZoned = utcToZonedTime(date, tz);
const inputZoned = toZonedTime(date, tz);
const fnZoned = fn(inputZoned, options);
if (fnZoned instanceof Date) {
return zonedTimeToUtc(fnZoned, tz) as Date;
return fromZonedTime(fnZoned, tz) as Date;
}
return fnZoned;
};
@@ -51,6 +51,6 @@ export const calcDateDifferenceProperty = (
locale,
config,
locale.time_zone === TimeZone.server
? utcToZonedTime(startDate, config.time_zone)
? toZonedTime(startDate, config.time_zone)
: startDate
);

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

@@ -1,3 +1,5 @@
export type MediaQueriesListener = () => void;
/**
* Attach a media query. Listener is called right away and when it matches.
* @param mediaQuery media query to match.
@@ -7,7 +9,7 @@
export const listenMediaQuery = (
mediaQuery: string,
matchesChanged: (matches: boolean) => void
) => {
): MediaQueriesListener => {
const mql = matchMedia(mediaQuery);
const listener = (e) => matchesChanged(e.matches);
mql.addListener(listener);

View File

@@ -19,28 +19,11 @@ import { blankBeforeUnit } from "../translations/blank_before_unit";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
export const computeStateDisplaySingleEntity = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendLocaleData,
config: HassConfig,
entity: EntityRegistryDisplayEntry | undefined,
state?: string
): string =>
computeStateDisplayFromEntityAttributes(
localize,
locale,
config,
entity,
stateObj.entity_id,
stateObj.attributes,
state !== undefined ? state : stateObj.state
);
export const computeStateDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendLocaleData,
sensorNumericDeviceClasses: string[],
config: HassConfig,
entities: HomeAssistant["entities"],
state?: string
@@ -52,6 +35,7 @@ export const computeStateDisplay = (
return computeStateDisplayFromEntityAttributes(
localize,
locale,
sensorNumericDeviceClasses,
config,
entity,
stateObj.entity_id,
@@ -63,6 +47,7 @@ export const computeStateDisplay = (
export const computeStateDisplayFromEntityAttributes = (
localize: LocalizeFunc,
locale: FrontendLocaleData,
sensorNumericDeviceClasses: string[],
config: HassConfig,
entity: EntityRegistryDisplayEntry | undefined,
entityId: string,
@@ -73,8 +58,15 @@ export const computeStateDisplayFromEntityAttributes = (
return localize(`state.default.${state}`);
}
const domain = computeDomain(entityId);
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
if (isNumericFromAttributes(attributes)) {
if (
isNumericFromAttributes(
attributes,
domain === "sensor" ? sensorNumericDeviceClasses : []
)
) {
// state is duration
if (
attributes.device_class === "duration" &&
@@ -120,8 +112,6 @@ export const computeStateDisplayFromEntityAttributes = (
return value;
}
const domain = computeDomain(entityId);
if (domain === "datetime") {
const time = new Date(state);
return formatDateTime(time, locale, config);
@@ -187,11 +177,14 @@ export const computeStateDisplayFromEntityAttributes = (
if (
[
"button",
"conversation",
"event",
"image",
"input_button",
"notify",
"scene",
"stt",
"tag",
"tts",
"wake_word",
].includes(domain) ||

View File

@@ -28,7 +28,15 @@ export const FIXED_DOMAIN_STATES = {
input_button: [],
lawn_mower: ["error", "paused", "mowing", "docked"],
light: ["on", "off"],
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
lock: [
"jammed",
"locked",
"locking",
"unlocked",
"unlocking",
"opening",
"open",
],
media_player: [
"off",
"on",

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

@@ -14,8 +14,12 @@ export const isNumericState = (stateObj: HassEntity): boolean =>
isNumericFromAttributes(stateObj.attributes);
export const isNumericFromAttributes = (
attributes: HassEntityAttributeBase
): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
attributes: HassEntityAttributeBase,
numericDeviceClasses?: string[]
): boolean =>
!!attributes.unit_of_measurement ||
!!attributes.state_class ||
(numericDeviceClasses || []).includes(attributes.device_class || "");
export const numberFormatToLocale = (
localeOptions: FrontendLocaleData
@@ -59,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

@@ -21,7 +21,8 @@ export const computeFormatFunctions = async (
localize: LocalizeFunc,
locale: FrontendLocaleData,
config: HassConfig,
entities: HomeAssistant["entities"]
entities: HomeAssistant["entities"],
sensorNumericDeviceClasses: string[]
): Promise<{
formatEntityState: FormatEntityStateFunc;
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
@@ -35,7 +36,15 @@ export const computeFormatFunctions = async (
return {
formatEntityState: (stateObj, state) =>
computeStateDisplay(localize, stateObj, locale, config, entities, state),
computeStateDisplay(
localize,
stateObj,
locale,
sensorNumericDeviceClasses,
config,
entities,
state
),
formatEntityAttributeValue: (stateObj, attribute, value) =>
computeAttributeValueDisplay(
localize,

View File

@@ -1,7 +1,8 @@
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";
// Exclude some patterns from key type checking for now
// These are intended to be removed as errors are fixed
@@ -81,14 +82,15 @@ export interface FormatsType {
*/
export const computeLocalize = async <Keys extends string = LocalizeKeys>(
cache: any,
cache: HTMLElement & {
_localizationCache?: Record<string, IntlMessageFormat>;
},
language: string,
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 = {};
@@ -107,7 +109,7 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
}
const messageKey = key + translatedValue;
let translatedMessage = cache._localizationCache[messageKey] as
let translatedMessage = cache._localizationCache![messageKey] as
| IntlMessageFormat
| undefined;
@@ -121,7 +123,7 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
} catch (err: any) {
return "Translation error: " + err.message;
}
cache._localizationCache[messageKey] = translatedMessage;
cache._localizationCache![messageKey] = translatedMessage;
}
let argObject = {};
@@ -137,6 +139,12 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
try {
return translatedMessage.format<string>(argObject) as string;
} catch (err: any) {
// eslint-disable-next-line no-console
console.error("Translation error", key, language, err);
fireEvent(cache, "write_log", {
level: "error",
message: `Failed to format translation for key '${key}' in language '${language}'. ${err}`,
});
return "Translation " + err;
}
};

View File

@@ -0,0 +1,9 @@
export const hasRejectedItems = <T = any>(results: PromiseSettledResult<T>[]) =>
results.some((result) => result.status === "rejected");
export const rejectedItems = <T = any>(
results: PromiseSettledResult<T>[]
): PromiseRejectedResult[] =>
results.filter(
(result) => result.status === "rejected"
) as PromiseRejectedResult[];

View File

@@ -1,4 +1,4 @@
import { differenceInDays, differenceInWeeks, startOfWeek } from "date-fns/esm";
import { differenceInDays, differenceInWeeks, startOfWeek } from "date-fns";
import { FrontendLocaleData } from "../../data/translation";
import { firstWeekdayIndex } from "../datetime/first_weekday";

View File

@@ -34,7 +34,7 @@ import {
endOfMonth,
endOfQuarter,
endOfYear,
} from "date-fns/esm";
} from "date-fns";
import {
formatDate,
formatDateMonth,

View File

@@ -313,31 +313,38 @@ export class HaChartBase extends LitElement {
`;
}
private _loading = false;
private async _setupChart() {
if (this._loading) return;
const ctx: CanvasRenderingContext2D = this.renderRoot
.querySelector("canvas")!
.getContext("2d")!;
this._loading = true;
try {
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
const computedStyles = getComputedStyle(this);
const computedStyles = getComputedStyle(this);
ChartConstructor.defaults.borderColor =
computedStyles.getPropertyValue("--divider-color");
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color"
);
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";
ChartConstructor.defaults.borderColor =
computedStyles.getPropertyValue("--divider-color");
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color"
);
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";
this.chart = new ChartConstructor(ctx, {
type: this.chartType,
data: this.data,
options: this._createOptions(),
plugins: this._createPlugins(),
});
this.chart = new ChartConstructor(ctx, {
type: this.chartType,
data: this.data,
options: this._createOptions(),
plugins: this._createPlugins(),
});
} finally {
this._loading = false;
}
}
private _createOptions() {

View File

@@ -1,4 +1,3 @@
import "element-internals-polyfill";
import { MdAssistChip } from "@material/web/chips/assist-chip";
import { css, html } from "lit";
import { customElement, property } from "lit/decorators";

View File

@@ -1,4 +1,3 @@
import "element-internals-polyfill";
import { MdChipSet } from "@material/web/chips/chip-set";
import { customElement } from "lit/decorators";

View File

@@ -1,4 +1,3 @@
import "element-internals-polyfill";
import { MdFilterChip } from "@material/web/chips/filter-chip";
import { css, html } from "lit";
import { customElement, property } from "lit/decorators";

View File

@@ -1,4 +1,3 @@
import "element-internals-polyfill";
import { MdInputChip } from "@material/web/chips/input-chip";
import { css } from "lit";
import { customElement } from "lit/decorators";
@@ -20,6 +19,7 @@ export class HaInputChip extends MdInputChip {
0.15
);
--ha-input-chip-selected-container-opacity: 1;
--md-input-chip-label-text-font: Roboto, sans-serif;
}
/** Set the size of mdc icons **/
::slotted([slot="icon"]) {

View File

@@ -1,13 +1,13 @@
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
import { mdiArrowDown, mdiArrowUp, mdiChevronUp } from "@mdi/js";
import deepClone from "deep-clone-simple";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
TemplateResult,
css,
html,
nothing,
} from "lit";
import {
customElement,
@@ -22,7 +22,9 @@ import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { restoreScroll } from "../../common/decorators/restore-scroll";
import { fireEvent } from "../../common/dom/fire_event";
import { stringCompare } from "../../common/string/compare";
import { debounce } from "../../common/util/debounce";
import { groupBy } from "../../common/util/group-by";
import { nextRender } from "../../common/util/render-status";
import { haStyleScrollbar } from "../../resources/styles";
import { loadVirtualizer } from "../../resources/virtualizer";
@@ -32,17 +34,6 @@ import type { HaCheckbox } from "../ha-checkbox";
import "../ha-svg-icon";
import "../search-input";
import { filterData, sortData } from "./sort-filter";
import { groupBy } from "../../common/util/group-by";
import { stringCompare } from "../../common/string/compare";
declare global {
// for fire event
interface HASSDomEvents {
"selection-changed": SelectionChangedEvent;
"row-click": RowClickedEvent;
"sorting-changed": SortingChangedEvent;
}
}
export interface RowClickedEvent {
id: string;
@@ -52,6 +43,10 @@ export interface SelectionChangedEvent {
value: string[];
}
export interface CollapsedChangedEvent {
value: string[];
}
export interface SortingChangedEvent {
column: string;
direction: SortingDirection;
@@ -142,10 +137,14 @@ export class HaDataTable extends LitElement {
@property() public groupColumn?: string;
@property({ attribute: false }) public groupOrder?: string[];
@property() public sortColumn?: string;
@property() public sortDirection: SortingDirection = null;
@property({ attribute: false }) public initialCollapsedGroups?: string[];
@state() private _filterable = false;
@state() private _filter = "";
@@ -158,6 +157,8 @@ export class HaDataTable extends LitElement {
@state() private _items: DataTableRowData[] = [];
@state() private _collapsedGroups: string[] = [];
private _checkableRowsCount?: number;
private _checkedRows: string[] = [];
@@ -213,17 +214,19 @@ export class HaDataTable extends LitElement {
(column) => column.filterable
);
for (const columnId in this.columns) {
if (this.columns[columnId].direction) {
this.sortDirection = this.columns[columnId].direction!;
this.sortColumn = columnId;
if (!this.sortColumn) {
for (const columnId in this.columns) {
if (this.columns[columnId].direction) {
this.sortDirection = this.columns[columnId].direction!;
this.sortColumn = columnId;
fireEvent(this, "sorting-changed", {
column: columnId,
direction: this.sortDirection,
});
fireEvent(this, "sorting-changed", {
column: columnId,
direction: this.sortDirection,
});
break;
break;
}
}
}
@@ -248,13 +251,23 @@ export class HaDataTable extends LitElement {
).length;
}
if (!this.hasUpdated && this.initialCollapsedGroups) {
this._collapsedGroups = this.initialCollapsedGroups;
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
} else if (properties.has("groupColumn")) {
this._collapsedGroups = [];
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
}
if (
properties.has("data") ||
properties.has("columns") ||
properties.has("_filter") ||
properties.has("sortColumn") ||
properties.has("sortDirection") ||
properties.has("groupColumn")
properties.has("groupColumn") ||
properties.has("groupOrder") ||
properties.has("_collapsedGroups")
) {
this._sortFilterData();
}
@@ -447,6 +460,8 @@ export class HaDataTable extends LitElement {
}
return html`
<div
@mouseover=${this._setTitle}
@focus=${this._setTitle}
role=${column.main ? "rowheader" : "cell"}
class="mdc-data-table__cell ${classMap({
"mdc-data-table__cell--flex": column.type === "flex",
@@ -514,11 +529,7 @@ export class HaDataTable extends LitElement {
}
if (this.appendRow || this.hasFab || this.groupColumn) {
const items = [...data];
if (this.appendRow) {
items.push({ append: true, content: this.appendRow });
}
let items = [...data];
if (this.groupColumn) {
const grouped = groupBy(items, (item) => item[this.groupColumn!]);
@@ -530,45 +541,66 @@ export class HaDataTable extends LitElement {
const sorted: {
[key: string]: DataTableRowData[];
} = Object.keys(grouped)
.sort((a, b) =>
stringCompare(
.sort((a, b) => {
const orderA = this.groupOrder?.indexOf(a) ?? -1;
const orderB = this.groupOrder?.indexOf(b) ?? -1;
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
return orderA - orderB;
}
return stringCompare(
["", "-", "—"].includes(a) ? "zzz" : a,
["", "-", "—"].includes(b) ? "zzz" : b,
this.hass.locale.language
)
)
);
})
.reduce((obj, key) => {
obj[key] = grouped[key];
return obj;
}, {});
const groupedItems: DataTableRowData[] = [];
Object.entries(sorted).forEach(([groupName, rows]) => {
if (
groupName !== UNDEFINED_GROUP_KEY ||
Object.keys(sorted).length > 1
) {
groupedItems.push({
append: true,
content: html`<div
class="mdc-data-table__cell group-header"
role="cell"
groupedItems.push({
append: true,
content: html`<div
class="mdc-data-table__cell group-header"
role="cell"
.group=${groupName}
@click=${this._collapseGroup}
>
<ha-icon-button
.path=${mdiChevronUp}
class=${this._collapsedGroups.includes(groupName)
? "collapsed"
: ""}
>
${groupName === UNDEFINED_GROUP_KEY ? "" : groupName || ""}
</div>`,
});
</ha-icon-button>
${groupName === UNDEFINED_GROUP_KEY
? this.hass.localize("ui.components.data-table.ungrouped")
: groupName || ""}
</div>`,
});
if (!this._collapsedGroups.includes(groupName)) {
groupedItems.push(...rows);
}
groupedItems.push(...rows);
});
items = groupedItems;
}
this._items = groupedItems;
} else {
this._items = items;
if (this.appendRow) {
items.push({ append: true, content: this.appendRow });
}
if (this.hasFab) {
this._items = [...this._items, { empty: true }];
items.push({ empty: true });
}
this._items = items;
} else {
this._items = data;
}
@@ -649,6 +681,13 @@ export class HaDataTable extends LitElement {
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
};
private _setTitle(ev: Event) {
const target = ev.currentTarget as HTMLElement;
if (target.scrollWidth > target.offsetWidth) {
target.setAttribute("title", target.innerText);
}
}
private _checkedRowsChanged() {
// force scroller to update, change it's items
if (this._items.length) {
@@ -679,6 +718,40 @@ export class HaDataTable extends LitElement {
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
}
private _collapseGroup = (ev: Event) => {
const groupName = (ev.currentTarget as any).group;
if (this._collapsedGroups.includes(groupName)) {
this._collapsedGroups = this._collapsedGroups.filter(
(grp) => grp !== groupName
);
} else {
this._collapsedGroups = [...this._collapsedGroups, groupName];
}
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,
@@ -931,8 +1004,22 @@ export class HaDataTable extends LitElement {
.group-header {
padding-top: 12px;
padding-left: 12px;
padding-inline-start: 12px;
padding-inline-end: initial;
width: 100%;
font-weight: 500;
display: flex;
align-items: center;
cursor: pointer;
}
.group-header ha-icon-button {
transition: transform 0.2s ease;
}
.group-header ha-icon-button.collapsed {
transform: rotate(180deg);
}
:host {
@@ -1031,4 +1118,12 @@ declare global {
interface HTMLElementTagNameMap {
"ha-data-table": HaDataTable;
}
// for fire event
interface HASSDomEvents {
"selection-changed": SelectionChangedEvent;
"row-click": RowClickedEvent;
"sorting-changed": SortingChangedEvent;
"collapsed-changed": CollapsedChangedEvent;
}
}

View File

@@ -11,10 +11,10 @@ import {
} from "../common/datetime/localize_date";
import { mainWindow } from "../common/dom/get_main_window";
// Set the current date to the left picker instead of the right picker because the right is hidden
const CustomDateRangePicker = Vue.extend({
mixins: [DateRangePicker],
methods: {
// Set the current date to the left picker instead of the right picker because the right is hidden
selectMonthDate() {
const dt: Date = this.end || new Date();
// @ts-ignore
@@ -23,6 +23,33 @@ const CustomDateRangePicker = Vue.extend({
month: dt.getMonth() + 1,
});
},
// Fix the start/end date calculation when selecting a date range. The
// original code keeps track of the first clicked date (in_selection) but it
// never sets it to either the start or end date variables, so if the
// in_selection date is between the start and end date that were set by the
// hover the selection will enter a broken state that's counter-intuitive
// when hovering between weeks and leads to a random date when selecting a
// range across months. This bug doesn't seem to be present on v0.6.7 of the
// lib
hoverDate(value: Date) {
if (this.readonly) return;
if (this.in_selection) {
const pickA = this.in_selection as Date;
const pickB = value;
this.start = this.normalizeDatetime(
Math.min(pickA.valueOf(), pickB.valueOf()),
this.start
);
this.end = this.normalizeDatetime(
Math.max(pickA.valueOf(), pickB.valueOf()),
this.end
);
}
this.$emit("hover-date", value);
},
},
});

View File

@@ -76,6 +76,8 @@ class HaEntitiesPickerLight extends LitElement {
@property({ attribute: false })
public entityFilter?: HaEntityPickerEntityFilterFunc;
@property({ type: Array }) public createDomains?: string[];
protected render() {
if (!this.hass) {
return nothing;
@@ -103,6 +105,7 @@ class HaEntitiesPickerLight extends LitElement {
.value=${entityId}
.label=${this.pickedEntityLabel}
.disabled=${this.disabled}
.createDomains=${this.createDomains}
@value-changed=${this._entityChanged}
></ha-entity-picker>
</div>
@@ -122,6 +125,7 @@ class HaEntitiesPickerLight extends LitElement {
.label=${this.pickEntityLabel}
.helper=${this.helper}
.disabled=${this.disabled}
.createDomains=${this.createDomains}
.required=${this.required && !currentEntities.length}
@value-changed=${this._addEntity}
></ha-entity-picker>

View File

@@ -18,6 +18,12 @@ import "../ha-icon-button";
import "../ha-svg-icon";
import "./state-badge";
import { caseInsensitiveStringCompare } from "../../common/string/compare";
import { showHelperDetailDialog } from "../../panels/config/helpers/show-dialog-helper-detail";
import { domainToName } from "../../data/integration";
import {
isHelperDomain,
HelperDomain,
} from "../../panels/config/helpers/const";
interface HassEntityWithCachedName extends HassEntity, ScorableTextItem {
friendly_name: string;
@@ -25,6 +31,8 @@ interface HassEntityWithCachedName extends HassEntity, ScorableTextItem {
export type HaEntityPickerEntityFilterFunc = (entity: HassEntity) => boolean;
const CREATE_ID = "___create-new-entity___";
@customElement("ha-entity-picker")
export class HaEntityPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -44,6 +52,8 @@ export class HaEntityPicker extends LitElement {
@property() public helper?: string;
@property({ type: Array }) public createDomains?: string[];
/**
* Show entities from specific domains.
* @type {Array}
@@ -130,7 +140,11 @@ export class HaEntityPicker extends LitElement {
></state-badge>`
: ""}
<span>${item.friendly_name}</span>
<span slot="secondary">${item.entity_id}</span>
<span slot="secondary"
>${item.entity_id.startsWith(CREATE_ID)
? this.hass.localize("ui.components.entity.entity-picker.new_entity")
: item.entity_id}</span
>
</ha-list-item>`;
private _getStates = memoizeOne(
@@ -143,7 +157,8 @@ export class HaEntityPicker extends LitElement {
includeDeviceClasses: this["includeDeviceClasses"],
includeUnitOfMeasurement: this["includeUnitOfMeasurement"],
includeEntities: this["includeEntities"],
excludeEntities: this["excludeEntities"]
excludeEntities: this["excludeEntities"],
createDomains: this["createDomains"]
): HassEntityWithCachedName[] => {
let states: HassEntityWithCachedName[] = [];
@@ -152,6 +167,34 @@ export class HaEntityPicker extends LitElement {
}
let entityIds = Object.keys(hass.states);
const createItems = createDomains?.length
? createDomains.map((domain) => {
const newFriendlyName = hass.localize(
"ui.components.entity.entity-picker.create_helper",
{
domain: isHelperDomain(domain)
? hass.localize(
`ui.panel.config.helpers.types.${domain as HelperDomain}`
)
: domainToName(hass.localize, domain),
}
);
return {
entity_id: CREATE_ID + domain,
state: "on",
last_changed: "",
last_updated: "",
context: { id: "", user_id: null, parent_id: null },
friendly_name: newFriendlyName,
attributes: {
icon: "mdi:plus",
},
strings: [domain, newFriendlyName],
};
})
: [];
if (!entityIds.length) {
return [
{
@@ -171,6 +214,7 @@ export class HaEntityPicker extends LitElement {
},
strings: [],
},
...createItems,
];
}
@@ -281,9 +325,14 @@ export class HaEntityPicker extends LitElement {
},
strings: [],
},
...createItems,
];
}
if (createItems?.length) {
states.push(...createItems);
}
return states;
}
);
@@ -310,13 +359,18 @@ export class HaEntityPicker extends LitElement {
this.includeDeviceClasses,
this.includeUnitOfMeasurement,
this.includeEntities,
this.excludeEntities
this.excludeEntities,
this.createDomains
);
if (this._initedStates) {
this.comboBox.filteredItems = this._states;
}
this._initedStates = true;
}
if (changedProps.has("createDomains") && this.createDomains?.length) {
this.hass.loadFragmentTranslation("config");
}
}
protected render(): TemplateResult {
@@ -351,9 +405,21 @@ export class HaEntityPicker extends LitElement {
this._opened = ev.detail.value;
}
private _valueChanged(ev: ValueChangedEvent<string>) {
private _valueChanged(ev: ValueChangedEvent<string | undefined>) {
ev.stopPropagation();
const newValue = ev.detail.value;
const newValue = ev.detail.value?.trim();
if (newValue && newValue.startsWith(CREATE_ID)) {
const domain = newValue.substring(CREATE_ID.length);
showHelperDetailDialog(this, {
domain,
dialogClosedCallback: (item) => {
if (item.entityId) this._setValue(item.entityId);
},
});
return;
}
if (newValue !== this._value) {
this._setValue(newValue);
}
@@ -361,13 +427,13 @@ export class HaEntityPicker extends LitElement {
private _filterChanged(ev: CustomEvent): void {
const target = ev.target as HaComboBox;
const filterString = ev.detail.value.toLowerCase();
const filterString = ev.detail.value.trim().toLowerCase();
target.filteredItems = filterString.length
? fuzzyFilterSort<HassEntityWithCachedName>(filterString, this._states)
: this._states;
}
private _setValue(value: string) {
private _setValue(value: string | undefined) {
this.value = value;
setTimeout(() => {
fireEvent(this, "value-changed", { value });

View File

@@ -14,6 +14,8 @@ export class HaCard extends LitElement {
--ha-card-background,
var(--card-background-color, white)
);
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
backdrop-filter: var(--ha-card-backdrop-filter, none);
box-shadow: var(--ha-card-box-shadow, none);
box-sizing: border-box;
border-radius: var(--ha-card-border-radius, 12px);

View File

@@ -1,6 +1,5 @@
import "element-internals-polyfill";
import { MdCircularProgress } from "@material/web/progress/circular-progress";
import { CSSResult, PropertyValues, css } from "lit";
import { PropertyValues, css } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-circular-progress")
@@ -32,17 +31,15 @@ export class HaCircularProgress extends MdCircularProgress {
}
}
static get styles(): CSSResult[] {
return [
...super.styles,
css`
:host {
--md-sys-color-primary: var(--primary-color);
--md-circular-progress-size: 48px;
}
`,
];
}
static override styles = [
...super.styles,
css`
:host {
--md-sys-color-primary: var(--primary-color);
--md-circular-progress-size: 48px;
}
`,
];
}
declare global {

View File

@@ -1,14 +1,7 @@
import { Ripple } from "@material/mwc-ripple";
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import {
customElement,
eventOptions,
property,
queryAsync,
state,
} from "lit/decorators";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import "./ha-ripple";
@customElement("ha-control-button")
export class HaControlButton extends LitElement {
@@ -16,10 +9,6 @@ export class HaControlButton extends LitElement {
@property() public label?: string;
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
@state() private _shouldRenderRipple = false;
protected render(): TemplateResult {
return html`
<button
@@ -28,54 +17,13 @@ export class HaControlButton extends LitElement {
aria-label=${ifDefined(this.label)}
title=${ifDefined(this.label)}
.disabled=${Boolean(this.disabled)}
@focus=${this.handleRippleFocus}
@blur=${this.handleRippleBlur}
@mousedown=${this.handleRippleActivate}
@mouseup=${this.handleRippleDeactivate}
@mouseenter=${this.handleRippleMouseEnter}
@mouseleave=${this.handleRippleMouseLeave}
@touchstart=${this.handleRippleActivate}
@touchend=${this.handleRippleDeactivate}
@touchcancel=${this.handleRippleDeactivate}
>
<slot></slot>
${this._shouldRenderRipple && !this.disabled
? html`<mwc-ripple></mwc-ripple>`
: ""}
<ha-ripple .disabled=${this.disabled}></ha-ripple>
</button>
`;
}
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
this._shouldRenderRipple = true;
return this._ripple;
});
@eventOptions({ passive: true })
private handleRippleActivate(evt?: Event) {
this._rippleHandlers.startPress(evt);
}
private handleRippleDeactivate() {
this._rippleHandlers.endPress();
}
private handleRippleMouseEnter() {
this._rippleHandlers.startHover();
}
private handleRippleMouseLeave() {
this._rippleHandlers.endHover();
}
private handleRippleFocus() {
this._rippleHandlers.startFocus();
}
private handleRippleBlur() {
this._rippleHandlers.endFocus();
}
static get styles(): CSSResultGroup {
return css`
:host {
@@ -86,6 +34,7 @@ export class HaControlButton extends LitElement {
--control-button-border-radius: 10px;
--control-button-padding: 8px;
--mdc-icon-size: 20px;
--ha-ripple-color: var(--secondary-text-color);
color: var(--primary-text-color);
width: 40px;
height: 40px;
@@ -113,12 +62,14 @@ export class HaControlButton extends LitElement {
outline: none;
overflow: hidden;
background: none;
--mdc-ripple-color: var(--control-button-background-color);
/* For safari border-radius overflow */
z-index: 0;
font-size: inherit;
color: inherit;
}
.button:focus-visible {
--control-button-background-opacity: 0.4;
}
.button::before {
content: "";
position: absolute;

View File

@@ -1,22 +1,14 @@
import { Ripple } from "@material/mwc-ripple";
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
import { SelectBase } from "@material/mwc-select/mwc-select-base";
import { mdiMenuDown } from "@mdi/js";
import { css, html, nothing } from "lit";
import {
customElement,
eventOptions,
property,
query,
queryAsync,
state,
} from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { debounce } from "../common/util/debounce";
import { nextRender } from "../common/util/render-status";
import "./ha-icon";
import type { HaIcon } from "./ha-icon";
import "./ha-ripple";
import "./ha-svg-icon";
import type { HaSvgIcon } from "./ha-svg-icon";
@@ -32,10 +24,6 @@ export class HaControlSelectMenu extends SelectBase {
@property({ type: Boolean, attribute: "hide-label" })
public hideLabel = false;
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
@state() private _shouldRenderRipple = false;
public override render() {
const classes = {
"select-disabled": this.disabled,
@@ -69,17 +57,10 @@ export class HaControlSelectMenu extends SelectBase {
aria-labelledby=${ifDefined(labelledby)}
aria-label=${ifDefined(labelAttribute)}
aria-required=${this.required}
@click=${this.onClick}
@focus=${this.onFocus}
@blur=${this.onBlur}
@click=${this.onClick}
@keydown=${this.onKeydown}
@mousedown=${this.handleRippleActivate}
@mouseup=${this.handleRippleDeactivate}
@mouseenter=${this.handleRippleMouseEnter}
@mouseleave=${this.handleRippleMouseLeave}
@touchstart=${this.handleRippleActivate}
@touchend=${this.handleRippleDeactivate}
@touchcancel=${this.handleRippleDeactivate}
>
${this.renderIcon()}
<div class="content">
@@ -91,9 +72,7 @@ export class HaControlSelectMenu extends SelectBase {
: nothing}
</div>
${this.renderArrow()}
${this._shouldRenderRipple && !this.disabled
? html` <mwc-ripple></mwc-ripple> `
: nothing}
<ha-ripple .disabled=${this.disabled}></ha-ripple>
</div>
${this.renderMenu()}
</div>
@@ -135,46 +114,6 @@ export class HaControlSelectMenu extends SelectBase {
`;
}
protected onFocus() {
this.handleRippleFocus();
super.onFocus();
}
protected onBlur() {
this.handleRippleBlur();
super.onBlur();
}
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
this._shouldRenderRipple = true;
return this._ripple;
});
@eventOptions({ passive: true })
private handleRippleActivate(evt?: Event) {
this._rippleHandlers.startPress(evt);
}
private handleRippleDeactivate() {
this._rippleHandlers.endPress();
}
private handleRippleMouseEnter() {
this._rippleHandlers.startHover();
}
private handleRippleMouseLeave() {
this._rippleHandlers.endHover();
}
private handleRippleFocus() {
this._rippleHandlers.startFocus();
}
private handleRippleBlur() {
this._rippleHandlers.endFocus();
}
connectedCallback() {
super.connectedCallback();
window.addEventListener("translations-updated", this._translationsUpdated);
@@ -204,6 +143,7 @@ export class HaControlSelectMenu extends SelectBase {
--control-select-menu-height: 48px;
--control-select-menu-padding: 6px 10px;
--mdc-icon-size: 20px;
--ha-ripple-color: var(--secondary-text-color);
font-size: 14px;
line-height: 1.4;
width: auto;
@@ -224,7 +164,6 @@ export class HaControlSelectMenu extends SelectBase {
outline: none;
overflow: hidden;
background: none;
--mdc-ripple-color: var(--control-select-menu-background-color);
/* For safari border-radius overflow */
z-index: 0;
transition: color 180ms ease-in-out;
@@ -264,6 +203,10 @@ export class HaControlSelectMenu extends SelectBase {
letter-spacing: inherit;
}
.select-anchor:focus-visible {
--control-select-menu-background-opacity: 0.4;
}
.select-anchor::before {
content: "";
position: absolute;

View File

@@ -67,6 +67,9 @@ export class HaControlSlider extends LitElement {
@property({ attribute: "tooltip-mode" })
public tooltipMode: TooltipMode = "interaction";
@property({ attribute: "touch-action" })
public touchAction?: string;
@property({ type: Number })
public value?: number;
@@ -152,7 +155,7 @@ export class HaControlSlider extends LitElement {
setupListeners() {
if (this.slider && !this._mc) {
this._mc = new Manager(this.slider, {
touchAction: this.vertical ? "pan-x" : "pan-y",
touchAction: this.touchAction ?? (this.vertical ? "pan-x" : "pan-y"),
});
this._mc.add(
new Pan({

View File

@@ -33,6 +33,9 @@ export class HaControlSwitch extends LitElement {
// SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an <ha-icon slot="icon-off"> in)
@property({ type: String }) pathOff?: string;
@property({ attribute: "touch-action" })
public touchAction?: string;
private _mc?: HammerManager;
protected firstUpdated(changedProperties: PropertyValues): void {
@@ -73,7 +76,7 @@ export class HaControlSwitch extends LitElement {
setupListeners() {
if (this.switch && !this._mc) {
this._mc = new Manager(this.switch, {
touchAction: this.vertical ? "pan-x" : "pan-y",
touchAction: this.touchAction ?? (this.vertical ? "pan-x" : "pan-y"),
});
this._mc.add(
new Swipe({

View File

@@ -19,6 +19,7 @@ import { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import { getExtendedEntityRegistryEntry } from "../data/entity_registry";
const NONE = "__NONE_OPTION__";
@@ -107,13 +108,23 @@ export class HaConversationAgentPicker extends LitElement {
}
private async _maybeFetchConfigEntry() {
if (!this.value || this.value === "homeassistant") {
if (!this.value || !(this.value in this.hass.entities)) {
this._configEntry = undefined;
return;
}
try {
const regEntry = await getExtendedEntityRegistryEntry(
this.hass,
this.value
);
if (!regEntry.config_entry_id) {
this._configEntry = undefined;
return;
}
this._configEntry = (
await getConfigEntry(this.hass, this.value)
await getConfigEntry(this.hass, regEntry.config_entry_id)
).config_entry;
} catch (err) {
this._configEntry = undefined;

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

@@ -75,8 +75,14 @@ export class HaDialog extends DialogBase {
var(--divider-color)
);
z-index: var(--dialog-z-index, 8);
-webkit-backdrop-filter: var(--dialog-backdrop-filter, none);
backdrop-filter: var(--dialog-backdrop-filter, none);
-webkit-backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
--mdc-dialog-box-shadow: var(--dialog-box-shadow, none);
--mdc-typography-headline6-font-weight: 400;
--mdc-typography-headline6-font-size: 1.574rem;
@@ -119,6 +125,12 @@ export class HaDialog extends DialogBase {
margin-top: var(--dialog-surface-margin-top);
min-height: var(--mdc-dialog-min-height, auto);
border-radius: var(--ha-dialog-border-radius, 28px);
-webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
background: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
}
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
display: flex;

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();
}
}
}
@@ -69,7 +72,7 @@ export class HaFilterDevices extends LitElement {
@value-changed=${this._handleSearchChange}
>
</search-input-outlined>
<mwc-list class="ha-scrollbar">
<mwc-list class="ha-scrollbar" multi>
<lit-virtualizer
.items=${this._devices(
this.hass.devices,
@@ -94,7 +97,7 @@ export class HaFilterDevices extends LitElement {
? nothing
: html`<ha-check-list-item
.value=${device.id}
.selected=${this.value?.includes(device.id)}
.selected=${this.value?.includes(device.id) ?? false}
>
${computeDeviceName(device, this.hass)}
</ha-check-list-item>`;

View File

@@ -0,0 +1,204 @@
import { mdiFilterVariantRemove } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { stringCompare } from "../common/string/compare";
import { domainToName } from "../data/integration";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-domain-icon";
import "./search-input-outlined";
import { computeDomain } from "../common/entity/compute_domain";
@customElement("ha-filter-domains")
export class HaFilterDomains extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public value?: string[];
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean, reflect: true }) public expanded = false;
@state() private _shouldRender = false;
@state() private _filter?: string;
protected render() {
return html`
<ha-expansion-panel
leftChevron
.expanded=${this.expanded}
@expanded-will-change=${this._expandedWillChange}
@expanded-changed=${this._expandedChanged}
>
<div slot="header" class="header">
${this.hass.localize(
"ui.panel.config.entities.picker.headers.domain"
)}
${this.value?.length
? html`<div class="badge">${this.value?.length}</div>
<ha-icon-button
.path=${mdiFilterVariantRemove}
@click=${this._clearFilter}
></ha-icon-button>`
: nothing}
</div>
${this._shouldRender
? html`<search-input-outlined
.hass=${this.hass}
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
>
</search-input-outlined>
<mwc-list
class="ha-scrollbar"
@click=${this._handleItemClick}
multi
>
${repeat(
this._domains(this.hass.states, this._filter),
(i) => i,
(domain) =>
html`<ha-check-list-item
.value=${domain}
.selected=${(this.value || []).includes(domain)}
graphic="icon"
>
<ha-domain-icon
slot="graphic"
.hass=${this.hass}
.domain=${domain}
brandFallback
></ha-domain-icon>
${domainToName(this.hass.localize, domain)}
</ha-check-list-item>`
)}
</mwc-list> `
: nothing}
</ha-expansion-panel>
`;
}
private _domains = memoizeOne((states, filter) => {
const domains = new Set<string>();
Object.keys(states).forEach((entityId) => {
domains.add(computeDomain(entityId));
});
return Array.from(domains.values())
.filter(
(entry) =>
!filter ||
entry.toLowerCase().includes(filter) ||
domainToName(this.hass.localize, entry).toLowerCase().includes(filter)
)
.sort((a, b) => stringCompare(a, b, this.hass.locale.language));
});
protected updated(changed) {
if (changed.has("expanded") && this.expanded) {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("mwc-list")!.style.height =
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
}, 300);
}
}
private _expandedWillChange(ev) {
this._shouldRender = ev.detail.expanded;
}
private _expandedChanged(ev) {
this.expanded = ev.detail.expanded;
}
private _handleItemClick(ev) {
const listItem = ev.target.closest("ha-check-list-item");
const value = listItem?.value;
if (!value) {
return;
}
if (this.value?.includes(value)) {
this.value = this.value?.filter((val) => val !== value);
} else {
this.value = [...(this.value || []), value];
}
listItem.selected = this.value.includes(value);
fireEvent(this, "data-table-filter-changed", {
value: this.value,
items: undefined,
});
}
private _clearFilter(ev) {
ev.preventDefault();
this.value = undefined;
fireEvent(this, "data-table-filter-changed", {
value: undefined,
items: undefined,
});
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value.toLowerCase();
}
static get styles(): CSSResultGroup {
return [
haStyleScrollbar,
css`
:host {
border-bottom: 1px solid var(--divider-color);
}
:host([expanded]) {
flex: 1;
height: 0;
}
ha-expansion-panel {
--ha-card-border-radius: 0;
--expansion-panel-content-padding: 0;
}
.header {
display: flex;
align-items: center;
}
.header ha-icon-button {
margin-inline-start: initial;
margin-inline-end: 8px;
}
.badge {
display: inline-block;
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
min-width: 16px;
box-sizing: border-box;
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-primary-color);
}
search-input-outlined {
display: block;
padding: 0 8px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-filter-domains": HaFilterDomains;
}
}

View File

@@ -42,6 +42,9 @@ export class HaFilterEntities extends LitElement {
if (!this.hasUpdated) {
loadVirtualizer();
if (this.value?.length) {
this._findRelated();
}
}
}
@@ -71,7 +74,7 @@ export class HaFilterEntities extends LitElement {
@value-changed=${this._handleSearchChange}
>
</search-input-outlined>
<mwc-list class="ha-scrollbar">
<mwc-list class="ha-scrollbar" multi>
<lit-virtualizer
.items=${this._entities(
this.hass.states,
@@ -108,7 +111,7 @@ export class HaFilterEntities extends LitElement {
? nothing
: html`<ha-check-list-item
.value=${entity.entity_id}
.selected=${this.value?.includes(entity.entity_id)}
.selected=${this.value?.includes(entity.entity_id) ?? false}
graphic="icon"
>
<ha-state-icon
@@ -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

@@ -1,4 +1,3 @@
import { SelectedDetail } from "@material/mwc-list";
import { mdiFilterVariantRemove } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -57,9 +56,9 @@ export class HaFilterIntegrations extends LitElement {
>
</search-input-outlined>
<mwc-list
@selected=${this._integrationsSelected}
multi
class="ha-scrollbar"
@click=${this._handleItemClick}
multi
>
${repeat(
this._integrations(this._manifests, this._filter, this.value),
@@ -131,34 +130,21 @@ export class HaFilterIntegrations extends LitElement {
)
);
private async _integrationsSelected(
ev: CustomEvent<SelectedDetail<Set<number>>>
) {
const integrations = this._integrations(
this._manifests!,
this._filter,
this.value
);
if (!ev.detail.index.size) {
fireEvent(this, "data-table-filter-changed", {
value: [],
items: undefined,
});
this.value = [];
private _handleItemClick(ev) {
const listItem = ev.target.closest("ha-check-list-item");
const value = listItem?.value;
if (!value) {
return;
}
const value: string[] = [];
for (const index of ev.detail.index) {
const domain = integrations[index].domain;
value.push(domain);
if (this.value?.includes(value)) {
this.value = this.value?.filter((val) => val !== value);
} else {
this.value = [...(this.value || []), value];
}
this.value = value;
listItem.selected = this.value?.includes(value);
fireEvent(this, "data-table-filter-changed", {
value,
value: this.value,
items: undefined,
});
}

View File

@@ -62,8 +62,8 @@ export class HaFilterStates extends LitElement {
(item) =>
html`<ha-check-list-item
.value=${item.value}
.selected=${this.value?.includes(item.value)}
.graphic=${hasIcon ? "icon" : undefined}
.selected=${this.value?.includes(item.value) ?? false}
.graphic=${hasIcon ? "icon" : null}
>
${item.icon
? html`<ha-icon

View File

@@ -71,6 +71,10 @@ export const computeInitialHaFormData = (
if (selector.country?.countries?.length) {
data[field.name] = selector.country.countries[0];
}
} else if ("language" in selector) {
if (selector.language?.languages?.length) {
data[field.name] = selector.language.languages[0];
}
} else if ("duration" in selector) {
data[field.name] = {
hours: 0,
@@ -93,7 +97,9 @@ export const computeInitialHaFormData = (
) {
data[field.name] = {};
} else {
throw new Error("Selector not supported in initial form data");
throw new Error(
`Selector ${Object.keys(selector)[0]} not supported in initial form data`
);
}
}
});

View File

@@ -1,13 +1,29 @@
import { FormfieldBase } from "@material/mwc-formfield/mwc-formfield-base";
import { styles } from "@material/mwc-formfield/mwc-formfield.css";
import { css } from "lit";
import { css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../common/dom/fire_event";
@customElement("ha-formfield")
export class HaFormfield extends FormfieldBase {
@property({ type: Boolean, reflect: true }) public disabled = false;
protected override render() {
const classes = {
"mdc-form-field--align-end": this.alignEnd,
"mdc-form-field--space-between": this.spaceBetween,
"mdc-form-field--nowrap": this.nowrap,
};
return html` <div class="mdc-form-field ${classMap(classes)}">
<slot></slot>
<label class="mdc-label" @click=${this._labelClick}
><slot name="label">${this.label}</slot></label
>
</div>`;
}
protected _labelClick() {
const input = this.input as HTMLInputElement | undefined;
if (!input) return;
@@ -39,6 +55,9 @@ export class HaFormfield extends FormfieldBase {
margin-inline-end: 10px;
margin-inline-start: inline;
}
.mdc-form-field {
align-items: var(--ha-formfield-align-items, center);
}
.mdc-form-field > label {
direction: var(--direction);
margin-inline-start: 0;

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

@@ -302,6 +302,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
name: this.hass.localize("ui.components.label-picker.no_match"),
icon: null,
color: null,
description: null,
},
];
}
@@ -315,6 +316,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
name: this.hass.localize("ui.components.label-picker.add_new"),
icon: "mdi:plus",
color: null,
description: null,
},
];
}

View File

@@ -1,6 +1,5 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "@material/web/ripple/ripple";
@customElement("ha-label")
class HaLabel extends LitElement {
@@ -11,7 +10,6 @@ class HaLabel extends LitElement {
<span class="content">
<slot name="icon"></slot>
<slot></slot>
<md-ripple></md-ripple>
</span>
`;
}
@@ -27,7 +25,6 @@ class HaLabel extends LitElement {
0.15
);
--ha-label-background-opacity: 1;
position: relative;
box-sizing: border-box;
display: inline-flex;

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";

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