Compare commits

...

136 Commits

Author SHA1 Message Date
Bram Kragten
b3766cbc62 Merge branch 'rc' 2024-02-08 18:09:07 +01:00
Bram Kragten
3469013f1a Bumped version to 20240207.1 2024-02-08 18:07:15 +01:00
Bram Kragten
03486e4125 cast allow empty view, pick first (#19739) 2024-02-08 18:06:57 +01:00
Bram Kragten
20560fb847 Fix cast launch screen (#19738) 2024-02-08 18:06:37 +01:00
Bram Kragten
bad18da658 Fix icons in gallery, demo and cast (#19732)
* Fix icons in gallery and demo

* Add to lovelace gallery

* Update icons.ts

* add BACKWARDS_COMPAT support for icons

* Update entity-state.ts

* Update icons.ts

* Update icons.ts
2024-02-08 18:06:27 +01:00
Paul Bottein
e26c7c491a Fix demo dashboard (#19734) 2024-02-08 18:06:11 +01:00
Paul Bottein
b0c8ae0c94 Fix suggest card dialog (#19735) 2024-02-08 18:05:55 +01:00
Bram Kragten
cc1658cbab Add service icons to traces (again :D) (#19728)
* Add service icons to traces (again :D)

* Update hat-graph-node.ts
2024-02-08 18:05:41 +01:00
Paul Bottein
4b768f0635 Use rgb theme variables for qrcode (#19726) 2024-02-08 18:05:27 +01:00
Paul Bottein
989057d947 Show icon of disabled entities (#19717) 2024-02-08 18:05:16 +01:00
Bram Kragten
6671d24fa6 Improve matter ping dialog (#19715) 2024-02-08 18:05:05 +01:00
Bram Kragten
297c721229 Matter cleanup on close dialog (#19714) 2024-02-08 18:04:51 +01:00
Bram Kragten
70c502bb45 Merge branch 'rc' 2024-02-07 11:22:12 +01:00
Bram Kragten
add0b55657 Merge branch 'dev' into rc 2024-02-07 11:21:56 +01:00
Bram Kragten
d61fc9ec6c Bumped version to 20240207.0 2024-02-07 11:21:33 +01:00
Paul Bottein
d1592bf262 Fix ha-state-badge name in picture element card (#19703)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-02-07 10:15:10 +01:00
karwosts
3c744c09f1 Fix plant battery level icon (#19704)
* Fix plant battery level icon

* cleanup

* cleanup
2024-02-06 15:42:23 +01:00
Cody C
3ef61aaf02 Move cloud login forgot password link into card actions area (#19642)
* Move cloud login forgot password link into card actions area

Aligns login forgot password link styling with create resend confirmation styling.

* Amend cloud-login buttons / links to show on single line
2024-02-06 15:30:55 +01:00
Paul Bottein
c738127c09 Fix horizontal stack margin (#19700) 2024-02-06 11:36:22 +01:00
Paulus Schoutsen
6e62f568fc Include climate attributes in history download (#19667)
* Include climate attributes in history download

* Add attributes for water heater and humidifier

* Simplify typing
2024-02-06 10:36:47 +01:00
Bram Kragten
2ba3a991a9 Improve matter share device dialog (#19693)
* Improve matter share device dialog

* Add border

* Update dialog-matter-open-commissioning-window.ts
2024-02-05 18:15:10 +01:00
Bram Kragten
50e559487d Merge branch 'rc' 2024-02-05 14:45:51 +01:00
Bram Kragten
4cd02c81bb Merge branch 'dev' into rc 2024-02-05 14:45:33 +01:00
Bram Kragten
55c6d3a7c4 Bumped version to 20240205.0 2024-02-05 14:45:17 +01:00
renovate[bot]
242f3813bc Update vaadinWebComponents monorepo to v24.3.5 (#19684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-05 13:32:18 +00:00
renovate[bot]
aa93cb17a7 Update dependency @lit-labs/motion to v1.0.7 (#19650)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-05 13:30:44 +00:00
renovate[bot]
4692d885d1 Update dependency @codemirror/language to v6.10.1 (#19688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-05 13:30:21 +00:00
Cody C
b39ac984f9 Fix last input margins in supervisor-network.ts (#19659)
* Fix last input margins in supervisor-network.ts

Resolves #19658

* Update src/panels/config/network/supervisor-network.ts

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-02-05 13:28:01 +00:00
renovate[bot]
9894d83e22 Update Yarn to v4.1.0 (#19634)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-05 14:12:49 +01:00
Cody C
113083a241 Avoid unnecessary redirect on empty state card page (#19643)
Avoid unnecessary redirect on hui-empty-state-card page
2024-02-05 13:53:52 +01:00
Cody C
32971cc875 Remove flag that sets icon as required when creating a new dashboard (#19640)
Less friction to create a new dashboard. Moreover, an icon isn't actually required in order to create the dashboard anyway. See https://github.com/home-assistant/frontend/issues/19639
2024-02-05 13:41:37 +01:00
Bram Kragten
137f59feb1 Remove comments from css (#19689)
remove comments from css
2024-02-05 13:39:47 +01:00
Paulus Schoutsen
6675121b85 Allow deselecting all values (#19677) 2024-02-05 12:48:18 +01:00
Paul Bottein
aed0a35c9c Fix button card color when off (#19685) 2024-02-05 12:47:23 +01:00
Paul Bottein
65a8518d99 Increase drop zone for automation editor (#19687) 2024-02-05 12:35:34 +01:00
dependabot[bot]
cb690e9d4e Bump release-drafter/release-drafter from 5 to 6 (#19683)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-02-05 08:59:50 +01:00
renovate[bot]
5da67de95f Update dependency magic-string to v0.30.6 (#19651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 21:04:01 +01:00
renovate[bot]
b9609f2154 Update dependency husky to v9.0.10 (#19674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 21:01:29 +01:00
renovate[bot]
aaabb6e1fb Update dependency webpack to v5.90.1 (#19675)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 20:35:46 +01:00
renovate[bot]
6561de34f0 Update dependency husky to v9.0.9 (#19672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 16:36:32 +01:00
Cody C
016ff74483 Fix grammar of Storage area messages (#19644) 2024-02-03 14:14:01 +01:00
renovate[bot]
f5e9839b42 Update dependency lint-staged to v15.2.1 (#19646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-03 14:12:04 +01:00
Bram Kragten
d044f4d34e 20240202.0Merge branch 'rc' 2024-02-02 16:29:21 +01:00
Bram Kragten
696717dd90 20240202.0Merge branch 'dev' into rc 2024-02-02 16:28:34 +01:00
Bram Kragten
eb3b168975 Bumped version to 20240202.0 2024-02-02 16:28:10 +01:00
Bram Kragten
aa400ce6ab Reload entity component icons when missing (#19629)
* Reload entity component icons when missing

* Improve typing, improve caching

* Make copy

* review suggestion

* overload

* Update icons.ts

* Update icons.ts
2024-02-02 16:26:47 +01:00
Paulus Schoutsen
682f9a0f04 Clean up the overlapping history in the CSV download (#19622)
* Clean up the overlapping history in the CSV download

* Speed up merge

* undefined unit

* Fix targetPickerValue handling
2024-02-02 16:06:29 +01:00
Paulus Schoutsen
e478038206 Remove refresh button from history panel (#19631) 2024-02-02 15:21:29 +01:00
Paul Bottein
259a9a4f58 Fix assist devices search bar color (#19627) 2024-02-02 12:12:43 +01:00
Bram Kragten
b08d1ae7e9 Update button focus style to match tile focus style (#19608) 2024-02-02 12:02:54 +01:00
renovate[bot]
3970fdd070 Update dependency hls.js to v1.5.3 (#19619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-02 11:46:33 +01:00
renovate[bot]
946445b2df Update dependency eslint-plugin-lit-a11y to v4.1.2 (#19626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-02 11:45:09 +01:00
renovate[bot]
17b090af58 Update typescript-eslint monorepo to v6.20.0 (#19613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-02 11:44:43 +01:00
karwosts
6690a0e4b1 Add names to map path tooltip (#19565)
* Add names to map path tooltip

* Apply suggestions from code review

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

* prettier

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-02-02 11:40:31 +01:00
Paul Bottein
c291448ffa Improve automation drag and drop interaction (#19624) 2024-02-02 11:31:01 +01:00
renovate[bot]
6f831699be Update dependency husky to v9.0.7 (#19612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-02 09:37:14 +01:00
Bram Kragten
fb73bfb964 Only show more info on graphs when mouse is used (#19606)
only show more info on graphs when mouse is used
2024-02-01 17:57:08 +01:00
Bram Kragten
28a0d216f9 Fix race in lovelace (#19609) 2024-02-01 17:56:46 +01:00
Bram Kragten
69f2566526 Matter tweaks (#19607) 2024-02-01 17:56:37 +01:00
Yosi Levy
7b3797502a RTL simplification and updates (#19586)
* RTL simplification and updates

* Fixes

* Fix weird icon offset when mwc-list-item is in a concat html``

* Additional updates
2024-02-01 14:29:00 +01:00
Paul Bottein
cf960be07e Fix item path for default in choose automation action (#19602) 2024-02-01 14:05:01 +01:00
Paulus Schoutsen
8a410d6c82 Add error checking to download button (#19596)
Clean up history panel + add better error checking
2024-02-01 09:25:52 +01:00
Bram Kragten
22c3132638 20240131.0 (#19595) 2024-01-31 19:02:00 +01:00
Bram Kragten
ce32de6e23 20240131.0 (#19594) 2024-01-31 19:00:50 +01:00
Bram Kragten
b6bc88e460 Merge branch 'dev' of https://github.com/home-assistant/frontend into dev 2024-01-31 18:55:23 +01:00
Bram Kragten
6e00806f1a Update state-control-circular-slider-style.ts 2024-01-31 18:54:56 +01:00
Bram Kragten
d9fa148c49 Merge branch 'master' into dev 2024-01-31 18:52:58 +01:00
Bram Kragten
939b3a8092 Update button card styles (#19591) 2024-01-31 18:48:58 +01:00
Bram Kragten
95920ba710 Default to error correction Q when there is a center image (#19593)
default to error correction Q when there is a center image
2024-01-31 18:48:45 +01:00
Bram Kragten
462ac79890 Bumped version to 20240131.0 2024-01-31 18:35:09 +01:00
Paul Bottein
601a165b2a Disable reorder for readonly automation and disabled block (#19592) 2024-01-31 17:48:22 +01:00
Bram Kragten
62bb9b1a87 Add QR code selector (#19588) 2024-01-31 16:31:54 +01:00
Paul Bottein
b60ba35a9f Don't allow dragging parent into child element in automation editor (#19589) 2024-01-31 16:28:20 +01:00
Bram Kragten
c97c3f2fc4 Fix disabled users picker (#19590)
fix disabled users picker
2024-01-31 15:26:37 +00:00
Bram Kragten
ed888200f9 Add support for re-auth flows in repairs (#19587) 2024-01-31 15:06:01 +01:00
Bram Kragten
f4859320eb Add icon to areas (#19585)
* Add icon to areas

* Fix gallery

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-01-31 13:18:43 +00:00
Marcel van der Veldt
b159f4c074 Add matter device info and actions (#19578)
* add matter device info panel (WIP)

* actually enable card on device page

* fix remove fabric

* add some translation labels

* add dialog to interview node

* do not show info for bridged devices

* first device action

* add ping node action and dialog

* ping should be always available

* update model for MatterCommissioningParameters

* add basic support for open commissioning window

* move fabric management to dialog

* review

* Add link to thread panel

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-01-31 14:16:21 +01:00
Cody C
b700e08d52 Simplify MFA onboarding styling & flow (#19584)
Align MFA onboarding flow

* Aligns QR Code to centre to make scanning on mobile easier
* Removes background from manual key text
* Re-adds spacing to bottom of form so it is not hard up against the code entry form
2024-01-31 10:51:07 +01:00
karwosts
c1bdd679ff Promote edit dashboard button out of overflow menu (#19345) 2024-01-31 10:45:10 +01:00
Paulus Schoutsen
b728b9efc4 Allow mobile apps to provide QR code functionality (#19570)
* Add QR code scanner to external bus

* Make `hasQRScanner` a version number
2024-01-31 10:44:21 +01:00
David F. Mulcahey
8acae63939 Update ZHA reconfigure device dialog to show accurate cluster configuration statuses (#19527) 2024-01-31 09:50:55 +01:00
Bram Kragten
374f5ee1be Update thread preferred router (#19580) 2024-01-31 00:58:03 +01:00
karwosts
528533a2dd Combine climate graph with temperature device_classes (#19485) 2024-01-31 00:33:28 +01:00
Maxim A
2b18db8525 Add Y axis limits options for historical charts (#19297)
* Add Y axis limit options for historical charts

* Fir formatting according to linter

* Revert statistic graph changes

* Cleanup local tests leftover

* Show fit Y fit option only if limits are set
2024-01-31 00:18:14 +01:00
Joni Käki-Mäkelä
6cd8ee9253 Disable pointer-events for tile-card .icon-container class that don't have a "button" role (#19497)
Set pointer-events to none for icon containers that don't have button role
2024-01-31 00:15:41 +01:00
karwosts
e45709fffc Fix map icon color (#19567)
* Fix map icon color

* different solution

* updates from code review
2024-01-30 23:57:35 +01:00
Simon Lamon
28c21b1041 Localize trigger state in automation editor (#19554)
* trigger state

* Lokalize

* don't change existing trigger

* space

* Fixes
2024-01-30 23:36:39 +01:00
G Johansson
0919f0e89e Add new TURN_ON and TURN_OFF Climate feature flags (#19523) 2024-01-30 15:11:08 +01:00
Bram Kragten
7ce9a937b1 20240104.0 (#19284) 2024-01-04 17:48:13 +01:00
Paul Bottein
456c011f3e Fix thermostat and humidifier card rendering when off (#19281)
* Fix thermostat and humidifier card rendering when off

* Fix action color
2024-01-04 17:44:58 +01:00
Bram Kragten
a31b9f1b4d Fix due date when no time in certain timezones (#19280)
* Fix due date when no time in certain timezones

* Update dialog-todo-item-editor.ts
2024-01-04 17:44:57 +01:00
Bram Kragten
a1cf18468b Remove overflow hidden from profile (#19279) 2024-01-04 17:44:56 +01:00
Bram Kragten
f147a5e909 fix valve entities row (#19278) 2024-01-04 17:44:55 +01:00
Franck Nijhof
8d541595b8 Update getStates to support valves (#19277) 2024-01-04 17:44:54 +01:00
Bram Kragten
32fd8270d7 Fix turning valve on/off (#19269) 2024-01-04 17:44:53 +01:00
Bram Kragten
efddbfcfa0 Fix circular progress size + fix bug in assist pipeline debug (#19268) 2024-01-04 17:44:53 +01:00
karwosts
0b20725f5f Fix select view dialog (#19267)
* Fix select view dialog

* add import
2024-01-04 17:44:52 +01:00
renovate[bot]
030566c1e8 Update dependency marked to v11.1.1 (#19254) 2024-01-04 17:44:51 +01:00
Bram Kragten
fef2c44cb8 Bumped version to 20240104.0 2024-01-04 17:44:16 +01:00
Bram Kragten
b99b13251f 20240103.3 (#19263) 2024-01-03 15:03:00 +01:00
Bram Kragten
288d173a4d Bumped version to 20240103.3 2024-01-03 15:02:14 +01:00
Bram Kragten
2c69fe8c53 Fix checking todo item that dont support due date (#19262)
* Fix checking todo item that dont support due date

* make cleaner

* Revert "make cleaner"

This reverts commit fa33b33614.

* Update dialog-todo-item-editor.ts

* do check in 1 place
2024-01-03 15:00:24 +01:00
Paul Bottein
62dafac72b Display edit button for climate fan mode feature (#19259) 2024-01-03 15:00:23 +01:00
karwosts
22929672a0 Remove tile pointer/ripple/index when it has no action (#19137)
* Remove tile pointer/ripple/index when it has no action

* update

* Apply suggestions from code review

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

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-01-03 15:00:22 +01:00
Bram Kragten
ae0eac3415 Bumped version to 20240103.0 2024-01-03 15:00:06 +01:00
Bram Kragten
4ea7d826bc 20240103.1 (#19258) 2024-01-03 12:37:44 +01:00
Bram Kragten
336214d97f Revert conditional rendering of condition (#19257)
* Fix conditionally showing `triggered by`

* revert conditional rendering

* Update add-automation-element-dialog.ts

* Update add-automation-element-dialog.ts
2024-01-03 12:29:30 +01:00
Bram Kragten
c9a0ae6e2d Bumped version to 20240103.1 2024-01-03 12:29:10 +01:00
Bram Kragten
0bc69fb9b3 20240103.0 (#19256) 2024-01-03 10:55:23 +01:00
Bram Kragten
6e7366bf69 Calculate used domains on open of action dialog (#19255) 2024-01-03 10:38:51 +01:00
Bram Kragten
8ee4aa9e63 Bumped version to 20240103.0 2024-01-03 10:38:30 +01:00
Bram Kragten
386c3ea1ca 20240102.0 (#19234) 2024-01-02 20:02:03 +01:00
Simon Lamon
c2f3e43ee5 Set default values for required and disabled for labeled slider (#19246)
Set default values
2024-01-02 20:01:15 +01:00
Bram Kragten
7354988ec9 Move notification services to main list (#19235) 2024-01-02 20:01:14 +01:00
Bram Kragten
53dedc6c65 Bumped version to 20240102.0 2024-01-02 18:49:50 +01:00
Bram Kragten
de3b9a5bb2 Give todo and calendar edit static header (#19233) 2024-01-02 18:48:59 +01:00
Bram Kragten
8d496e1511 Update add-automation-element-dialog.ts 2024-01-02 18:48:06 +01:00
JLo
01bd88ce10 New copy for device trigger in automation editor (#19232)
New copy for device trigger in automation editor: 
"Set of conditions provided by your device. Great way to start."
2024-01-02 18:44:33 +01:00
Josh McCarty
18b5fd59a6 Add missing device classes for entity-registry-settings-editor (#19231)
* Add connectivity device class for binary sensors

* Add update device class

* Separate connectivity and update
2024-01-02 18:44:32 +01:00
Bram Kragten
750c1d5013 Change format of service description (#19229) 2024-01-02 18:44:31 +01:00
Bram Kragten
f2226cdec2 Use brand icons in actions (#19227) 2024-01-02 18:44:30 +01:00
Bram Kragten
c125ec087a Remove references to "service call" from actions (#19226) 2024-01-02 18:44:29 +01:00
Bram Kragten
f099f66065 Automation editor tweaks (#19225)
* Automation editor tweaks

* fix styling
2024-01-02 18:44:28 +01:00
JLo
4fcf99faa7 Review on automation editor text (#19223)
- Added `.` to bloc descriptions
- Changed "Other" into "OTher triggers" "Other conditions" and "Other actions"
- Adapted a few descriptions
2024-01-02 18:44:28 +01:00
karwosts
2add88ccc2 Localize a device action string (#19203) 2024-01-02 18:44:27 +01:00
Bram Kragten
aa94ec7949 20240101.0 (#19217) 2024-01-01 14:08:07 +01:00
Bram Kragten
7b4ecfd30a 20231228.0 (#19170) 2023-12-28 15:34:42 +01:00
Bram Kragten
9d9e789f4b 20231227.0 (#19157) 2023-12-27 17:29:11 +01:00
Paul Bottein
6ce613acd2 20231208.2 (#18971) 2023-12-08 14:50:28 +01:00
Paul Bottein
aa38e2d409 20231208.1 (#18962) 2023-12-08 10:36:45 +01:00
Paul Bottein
fce4e5e382 20231206.0 (#18925) 2023-12-06 14:24:48 +01:00
Bram Kragten
eb5e7ba3f3 20231205.0 (#18916) 2023-12-05 18:10:37 +01:00
Bram Kragten
ae2e8e7402 20231204.0 (#18882) 2023-12-04 12:10:33 +01:00
Bram Kragten
b854d23431 20231130.0 (#18843) 2023-11-30 17:19:47 +01:00
Bram Kragten
ef735d65cf 20231129.1 (#18811) 2023-11-29 15:31:24 +01:00
Bram Kragten
2803e6aa95 20231129.0 (#18809) 2023-11-29 12:53:12 +01:00
183 changed files with 6473 additions and 3876 deletions

View File

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

View File

@@ -1,39 +0,0 @@
diff --git a/modular/sortable.complete.esm.js b/modular/sortable.complete.esm.js
index 02e9f2d6bebeb430fe6e7c1cc3f9c3c9df051f14..bb8268b0844a1faa4108cc92c0be2a3dbaf23f83 100644
--- a/modular/sortable.complete.esm.js
+++ b/modular/sortable.complete.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
index b04c8b4634f7c6b4ef1aadbb48afe6564306dea9..39a107163c8c336ebd669b5ea8a936af87e1c1e7 100644
--- a/modular/sortable.core.esm.js
+++ b/modular/sortable.core.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();
diff --git a/modular/sortable.esm.js b/modular/sortable.esm.js
index 6ec7ed1bb557e21c2578200161e989c65d23150b..0a05475a22904472fac6c13f524c674da76584b0 100644
--- a/modular/sortable.esm.js
+++ b/modular/sortable.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();

View File

@@ -0,0 +1,73 @@
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b441f523f 100644
--- a/modular/sortable.core.esm.js
+++ b/modular/sortable.core.esm.js
@@ -1461,7 +1461,7 @@ Sortable.prototype = /** @lends Sortable.prototype */{
}
target = parent; // store last element
}
- /* jshint boss:true */ while (parent = parent.parentNode);
+ /* jshint boss:true */ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();
}
@@ -1781,11 +1781,16 @@ Sortable.prototype = /** @lends Sortable.prototype */{
}
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
capture();
- if (elLastChild && elLastChild.nextSibling) {
- // the last draggable element is not the last node
- el.insertBefore(dragEl, elLastChild.nextSibling);
- } else {
- el.appendChild(dragEl);
+ try {
+ if (elLastChild && elLastChild.nextSibling) {
+ // the last draggable element is not the last node
+ el.insertBefore(dragEl, elLastChild.nextSibling);
+ } else {
+ el.appendChild(dragEl);
+ }
+ }
+ catch(err) {
+ return completed(false);
}
parentEl = el; // actualization
@@ -1802,7 +1807,13 @@ Sortable.prototype = /** @lends Sortable.prototype */{
targetRect = getRect(target);
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) {
capture();
- el.insertBefore(dragEl, firstChild);
+ try {
+ el.insertBefore(dragEl, firstChild);
+ }
+ catch(err) {
+ return completed(false);
+ }
+
parentEl = el; // actualization
changed();
@@ -1849,12 +1860,17 @@ Sortable.prototype = /** @lends Sortable.prototype */{
_silent = true;
setTimeout(_unsilent, 30);
capture();
- if (after && !nextSibling) {
- el.appendChild(dragEl);
- } else {
- target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
- }
+ try {
+ if (after && !nextSibling) {
+ el.appendChild(dragEl);
+ } else {
+ target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
+ }
+ }
+ catch(err) {
+ return completed(false);
+ }
// Undo chrome's scroll adjustment (has no effect on other browsers)
if (scrolledPastTop) {
scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);

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.0.2.cjs
yarnPath: .yarn/releases/yarn-4.1.0.cjs

View File

@@ -28,7 +28,6 @@ class HcLaunchScreen extends LitElement {
:host {
display: block;
height: 100vh;
padding-top: 64px;
background-color: white;
font-size: 24px;
}
@@ -36,12 +35,13 @@ class HcLaunchScreen extends LitElement {
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
height: 100%;
justify-content: space-evenly;
}
img {
width: 717px;
height: 376px;
display: block;
margin: 0 auto;
max-width: 80%;
object-fit: cover;
}
.status {
padding-right: 54px;

View File

@@ -17,7 +17,7 @@ class HcLovelace extends LitElement {
@property({ attribute: false })
public lovelaceConfig!: LovelaceConfig;
@property() public viewPath?: string | number;
@property() public viewPath?: string | number | null;
@property() public urlPath: string | null = null;
@@ -93,6 +93,9 @@ class HcLovelace extends LitElement {
}
private get _viewIndex() {
if (this.viewPath === null) {
return 0;
}
const selectedView = this.viewPath;
const selectedViewInt = parseInt(selectedView as string, 10);
for (let i = 0; i < this.lovelaceConfig.views.length; i++) {

View File

@@ -51,10 +51,10 @@ export class HcMain extends HassElement {
@state() private _lovelacePath: string | number | null = null;
@state() private _error?: string;
@state() private _urlPath?: string | null;
@state() private _error?: string;
private _hassUUID?: string;
private _unsubLovelace?: UnsubscribeFunc;
@@ -81,7 +81,7 @@ export class HcMain extends HassElement {
if (
!this._lovelaceConfig ||
this._lovelacePath === null ||
this._urlPath === undefined ||
// Guard against part of HA not being loaded yet.
!this.hass ||
!this.hass.states ||
@@ -99,8 +99,8 @@ export class HcMain extends HassElement {
<hc-lovelace
.hass=${this.hass}
.lovelaceConfig=${this._lovelaceConfig}
.viewPath=${this._lovelacePath}
.urlPath=${this._urlPath}
.viewPath=${this._lovelacePath}
@config-refresh=${this._generateDefaultLovelaceConfig}
></hc-lovelace>
`;
@@ -226,9 +226,9 @@ export class HcMain extends HassElement {
this.initializeHass(auth, connection);
if (this._hassUUID !== msg.hassUUID) {
this._hassUUID = msg.hassUUID;
this._lovelacePath = null;
this._urlPath = undefined;
this._lovelaceConfig = undefined;
this._urlPath = undefined;
this._lovelacePath = null;
if (this._unsubLovelace) {
this._unsubLovelace();
this._unsubLovelace = undefined;
@@ -285,7 +285,7 @@ export class HcMain extends HassElement {
],
};
this._urlPath = "energy";
this._lovelacePath = 0;
this._lovelacePath = null;
this._sendStatus();
return;
}

View File

@@ -17,12 +17,14 @@ import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events";
import { mockFrontend } from "./stubs/frontend";
import { mockIcons } from "./stubs/icons";
import { mockHistory } from "./stubs/history";
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 { mockTranslations } from "./stubs/translations";
@@ -50,11 +52,13 @@ export class HaDemo extends HomeAssistantAppEl {
mockHistory(hass);
mockRecorder(hass);
mockTodo(hass);
mockSensor(hass);
mockSystemLog(hass);
mockTemplate(hass);
mockEvents(hass);
mockMediaPlayer(hass);
mockFrontend(hass);
mockIcons(hass);
mockEnergy(hass);
mockPersistentNotification(hass);
mockConfigEntries(hass);

33
demo/src/stubs/icons.ts Normal file
View File

@@ -0,0 +1,33 @@
import { IconCategory } from "../../../src/data/icons";
import { ENTITY_COMPONENT_ICONS } from "../../../src/fake_data/entity_component_icons";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockIcons = (hass: MockHomeAssistant) => {
hass.mockWS(
"frontend/get_icons",
async ({
category,
integration,
}: {
category: IconCategory;
integration?: string;
}) => {
if (integration) {
try {
const response = await fetch(
`https://raw.githubusercontent.com/home-assistant/core/dev/homeassistant/components/${integration}/icons.json`
).then((resp) => resp.json());
return { resources: { [integration]: response[category] || {} } };
} catch {
return { resources: {} };
}
}
if (category === "entity_component") {
return {
resources: ENTITY_COMPONENT_ICONS,
};
}
return { resources: {} };
}
);
};

58
demo/src/stubs/sensor.ts Normal file
View File

@@ -0,0 +1,58 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockSensor = (hass: MockHomeAssistant) => {
hass.mockWS("sensor/numeric_device_classes", () => [
{
numeric_device_classes: [
"volume_storage",
"gas",
"data_size",
"irradiance",
"wind_speed",
"volatile_organic_compounds",
"volatile_organic_compounds_parts",
"voltage",
"frequency",
"precipitation_intensity",
"volume",
"precipitation",
"battery",
"nitrogen_dioxide",
"speed",
"signal_strength",
"pm1",
"nitrous_oxide",
"atmospheric_pressure",
"data_rate",
"temperature",
"power_factor",
"aqi",
"current",
"volume_flow_rate",
"humidity",
"duration",
"ozone",
"distance",
"pressure",
"pm25",
"weight",
"energy",
"carbon_monoxide",
"apparent_power",
"illuminance",
"energy_storage",
"moisture",
"power",
"water",
"carbon_dioxide",
"ph",
"reactive_power",
"monetary",
"nitrogen_monoxide",
"pm10",
"sound_pressure",
"sulphur_dioxide",
],
},
]);
};

View File

@@ -21,4 +21,5 @@ export const mockTodo = (hass: MockHomeAssistant) => {
},
] as TodoItem[],
}));
hass.mockWS("todo/item/subscribe", (_msg, _hass) => () => {});
};

View File

@@ -3,7 +3,6 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/trace/hat-script-graph";
import "../../../../src/components/trace/hat-trace-timeline";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";

View File

@@ -10,6 +10,7 @@ import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervis
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
@@ -97,22 +98,25 @@ const DEVICES = [
},
];
const AREAS = [
const AREAS: AreaRegistryEntry[] = [
{
area_id: "backyard",
name: "Backyard",
icon: null,
picture: null,
aliases: [],
},
{
area_id: "bedroom",
name: "Bedroom",
icon: "mdi:bed",
picture: null,
aliases: [],
},
{
area_id: "livingroom",
name: "Livingroom",
icon: "mdi:sofa",
picture: null,
aliases: [],
},

View File

@@ -9,6 +9,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import "../../../../src/components/ha-selector/ha-selector";
import "../../../../src/components/ha-settings-row";
import type { AreaRegistryEntry } from "../../../../src/data/area_registry";
import { BlueprintInput } from "../../../../src/data/blueprint";
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
import { getEntity } from "../../../../src/fake_data/entity";
@@ -93,22 +94,25 @@ const DEVICES = [
},
];
const AREAS = [
const AREAS: AreaRegistryEntry[] = [
{
area_id: "backyard",
name: "Backyard",
icon: null,
picture: null,
aliases: [],
},
{
area_id: "bedroom",
name: "Bedroom",
icon: "mdi:bed",
picture: null,
aliases: [],
},
{
area_id: "livingroom",
name: "Livingroom",
icon: "mdi:sofa",
picture: null,
aliases: [],
},

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", {
@@ -84,6 +85,7 @@ class DemoAlarmPanelEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
@@ -146,6 +147,7 @@ class DemoArea extends LitElement {
entity_id: "binary_sensor.kitchen_door",
},
]);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "controller_1", "on", {
@@ -66,6 +67,7 @@ class DemoConditional extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
@@ -323,6 +324,7 @@ class DemoEntities extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
@@ -82,6 +83,7 @@ class DemoButtonEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "work", {
@@ -123,6 +124,7 @@ class DemoEntityFilter extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("sensor", "brightness", "12", {}),
@@ -128,6 +129,7 @@ class DemoGaugeEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "home", {
@@ -238,6 +239,7 @@ class DemoGlanceEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -4,6 +4,7 @@ import { mockHistory } from "../../../../demo/src/stubs/history";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "kitchen_lights", "on", {
@@ -214,6 +215,7 @@ class DemoStack extends LitElement {
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockHistory(hass);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
@@ -76,6 +77,7 @@ class DemoLightEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
@@ -138,6 +139,7 @@ class DemoPictureElements extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "kitchen_lights", "on", {
@@ -93,6 +94,7 @@ class DemoPictureEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("switch", "decorative_lights", "on", {
@@ -134,6 +135,7 @@ class DemoPictureGlance extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { createPlantEntities } from "../../data/plants";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const CONFIGS = [
{
@@ -43,6 +44,7 @@ export class DemoPlantEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(createPlantEntities());
mockIcons(hass);
}
}

View File

@@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("climate", "ecobee", "auto", {
@@ -116,6 +117,7 @@ class DemoThermostatEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -6,6 +6,7 @@ import { VacuumEntityFeature } from "../../../../src/data/vacuum";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("switch", "tv_outlet", "on", {
@@ -79,6 +80,18 @@ const CONFIGS = [
color: pink
`,
},
{
heading: "Whole tile tap action",
config: `
- type: tile
entity: switch.tv_outlet
color: pink
tap_action:
action: toggle
icon_tap_action:
action: none
`,
},
{
heading: "Unknown entity",
config: `
@@ -172,6 +185,7 @@ class DemoTile extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}

View File

@@ -4,6 +4,7 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { getEntity } from "../../../../src/fake_data/entity";
import { mockTodo } from "../../../../demo/src/stubs/todo";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("todo", "shopping_list", "2", {
@@ -47,6 +48,7 @@ class DemoTodoListEntity extends LitElement {
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
mockTodo(hass);
}

View File

@@ -11,6 +11,7 @@ import "../../../../src/components/data-table/ha-data-table";
import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table";
import "../../../../src/components/entity/state-badge";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { mockIcons } from "../../../../demo/src/stubs/icons";
import { HomeAssistant } from "../../../../src/types";
const SENSOR_DEVICE_CLASSES = [
@@ -291,6 +292,7 @@ const ENTITIES: HassEntity[] = [
createEntity("water_heater.high_demand", "high_demand"),
createEntity("water_heater.heat_pump", "heat_pump"),
createEntity("water_heater.gas", "gas"),
createEntity("select.speed", "ridiculous_speed"),
];
function createEntity(
@@ -397,6 +399,16 @@ export class DemoEntityState extends LitElement {
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const hass = provideHass(this);
mockIcons(hass);
hass.updateHass({
entities: {
"select.speed": {
entity_id: "select.speed",
translation_key: "speed",
platform: "demo",
},
},
});
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
}

View File

@@ -29,7 +29,7 @@
"@braintree/sanitize-url": "7.0.0",
"@codemirror/autocomplete": "6.12.0",
"@codemirror/commands": "6.3.3",
"@codemirror/language": "6.10.0",
"@codemirror/language": "6.10.1",
"@codemirror/legacy-modes": "6.3.3",
"@codemirror/search": "6.5.5",
"@codemirror/state": "6.4.0",
@@ -51,7 +51,7 @@
"@fullcalendar/timegrid": "6.1.10",
"@lezer/highlight": "1.2.0",
"@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.6",
"@lit-labs/motion": "1.0.7",
"@lit-labs/observers": "2.0.2",
"@lit-labs/virtualizer": "2.0.12",
"@lrnwebcomponents/simple-tooltip": "8.0.0",
@@ -89,8 +89,8 @@
"@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.3.4",
"@vaadin/vaadin-themable-mixin": "24.3.4",
"@vaadin/combo-box": "24.3.5",
"@vaadin/vaadin-themable-mixin": "24.3.5",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -109,7 +109,7 @@
"element-internals-polyfill": "1.3.10",
"fuse.js": "7.0.0",
"google-timezones-json": "1.2.0",
"hls.js": "1.5.2",
"hls.js": "1.5.3",
"home-assistant-js-websocket": "9.1.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.11",
@@ -183,8 +183,8 @@
"@types/tar": "6.1.11",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "6.19.1",
"@typescript-eslint/parser": "6.19.1",
"@typescript-eslint/eslint-plugin": "6.20.0",
"@typescript-eslint/parser": "6.20.0",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
@@ -199,7 +199,7 @@
"eslint-plugin-disable": "2.0.3",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-lit": "1.11.0",
"eslint-plugin-lit-a11y": "4.1.1",
"eslint-plugin-lit-a11y": "4.1.2",
"eslint-plugin-unused-imports": "3.0.0",
"eslint-plugin-wc": "2.0.4",
"fancy-log": "2.0.0",
@@ -212,13 +212,13 @@
"gulp-rename": "2.0.0",
"gulp-zopfli-green": "6.0.1",
"html-minifier-terser": "7.2.0",
"husky": "9.0.6",
"husky": "9.0.10",
"instant-mocha": "1.5.2",
"jszip": "3.10.1",
"lint-staged": "15.2.0",
"lint-staged": "15.2.1",
"lit-analyzer": "2.0.3",
"lodash.template": "4.5.0",
"magic-string": "0.30.5",
"magic-string": "0.30.6",
"map-stream": "0.0.7",
"mocha": "10.2.0",
"object-hash": "3.0.0",
@@ -240,7 +240,7 @@
"typescript": "5.3.3",
"vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0",
"webpack": "5.90.0",
"webpack": "5.90.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1",
"webpack-manifest-plugin": "5.0.0",
@@ -255,8 +255,8 @@
"lit": "2.8.0",
"clean-css": "5.3.3",
"@lit/reactive-element": "1.6.3",
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
"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.0.2"
"packageManager": "yarn@4.1.0"
}

View File

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

View File

@@ -38,4 +38,8 @@ export function setDirectionStyles(direction: string, element: LitElement) {
"--margin-title",
direction === "ltr" ? "var(--margin-title-ltr)" : "var(--margin-title-rtl)"
);
element.style.setProperty(
"--scale-direction",
direction === "ltr" ? "1" : "-1"
);
}

View File

@@ -10,7 +10,6 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { clamp } from "../../common/number/clamp";
import { computeRTL } from "../../common/util/compute_rtl";
import { HomeAssistant } from "../../types";
import { debounce } from "../../common/util/debounce";
@@ -212,12 +211,10 @@ export class HaChartBase extends LitElement {
height: `${
this.height ?? this._chartHeight ?? this.clientWidth / 2
}px`,
"padding-left": `${
computeRTL(this.hass) ? 0 : this._paddingYAxisInternal
}px`,
"padding-right": `${
computeRTL(this.hass) ? this._paddingYAxisInternal : 0
}px`,
"padding-left": `${this._paddingYAxisInternal}`,
"padding-right": 0,
"padding-inline-start": `${this._paddingYAxisInternal}`,
"padding-inline-end": 0,
})}
>
<canvas></canvas>
@@ -433,14 +430,6 @@ export class HaChartBase extends LitElement {
.chartTooltip .bullet {
align-self: baseline;
}
:host([rtl]) .chartLegend .bullet,
:host([rtl]) .chartTooltip .bullet {
margin-right: inherit;
margin-left: 6px;
margin-inline-end: inherit;
margin-inline-start: 6px;
direction: var(--direction);
}
.chartTooltip {
padding: 8px;
font-size: 90%;
@@ -449,12 +438,13 @@ export class HaChartBase extends LitElement {
color: white;
border-radius: 4px;
pointer-events: none;
z-index: 1000;
z-index: 1;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
width: 200px;
box-sizing: border-box;
}
:host([rtl]) .chartTooltip {
direction: rtl;
direction: var(--direction);
}
.chartLegend ul,
.chartTooltip ul {

View File

@@ -47,6 +47,12 @@ export class StateHistoryChartLine extends LitElement {
@property({ type: Boolean }) public logarithmicScale = false;
@property({ type: Number }) public minYAxis?: number;
@property({ type: Number }) public maxYAxis?: number;
@property({ type: Boolean }) public fitYData = false;
@state() private _chartData?: ChartData<"line">;
@state() private _entityIds: string[] = [];
@@ -84,7 +90,10 @@ export class StateHistoryChartLine extends LitElement {
changedProps.has("startTime") ||
changedProps.has("endTime") ||
changedProps.has("unit") ||
changedProps.has("logarithmicScale")
changedProps.has("logarithmicScale") ||
changedProps.has("minYAxis") ||
changedProps.has("maxYAxis") ||
changedProps.has("fitYData")
) {
this._chartOptions = {
parsing: false,
@@ -121,6 +130,10 @@ export class StateHistoryChartLine extends LitElement {
},
},
y: {
suggestedMin: this.fitYData ? this.minYAxis : null,
suggestedMax: this.fitYData ? this.maxYAxis : null,
min: this.fitYData ? null : this.minYAxis,
max: this.fitYData ? null : this.maxYAxis,
ticks: {
maxTicksLimit: 7,
},
@@ -207,7 +220,12 @@ export class StateHistoryChartLine extends LitElement {
// @ts-expect-error
locale: numberFormatToLocale(this.hass.locale),
onClick: (e: any) => {
if (!this.clickForMoreInfo) {
if (
!this.clickForMoreInfo ||
!(e.native instanceof MouseEvent) ||
(e.native instanceof PointerEvent &&
e.native.pointerType !== "mouse")
) {
return;
}

View File

@@ -224,7 +224,11 @@ export class StateHistoryChartTimeline extends LitElement {
// @ts-expect-error
locale: numberFormatToLocale(this.hass.locale),
onClick: (e: any) => {
if (!this.clickForMoreInfo) {
if (
!this.clickForMoreInfo ||
!(e.native instanceof MouseEvent) ||
(e.native instanceof PointerEvent && e.native.pointerType !== "mouse")
) {
return;
}

View File

@@ -74,6 +74,12 @@ export class StateHistoryCharts extends LitElement {
@property({ type: Boolean }) public logarithmicScale = false;
@property({ type: Number }) public minYAxis?: number;
@property({ type: Number }) public maxYAxis?: number;
@property({ type: Boolean }) public fitYData = false;
private _computedStartTime!: Date;
private _computedEndTime!: Date;
@@ -161,6 +167,9 @@ export class StateHistoryCharts extends LitElement {
.chartIndex=${index}
.clickForMoreInfo=${this.clickForMoreInfo}
.logarithmicScale=${this.logarithmicScale}
.minYAxis=${this.minYAxis}
.maxYAxis=${this.maxYAxis}
.fitYData=${this.fitYData}
@y-width-changed=${this._yWidthChanged}
></state-history-chart-line>
</div> `;

View File

@@ -688,15 +688,12 @@ export class HaDataTable extends LitElement {
padding-left: 16px;
/* @noflip */
padding-right: 0;
/* @noflip */
padding-inline-start: 16px;
/* @noflip */
padding-inline-end: initial;
width: 60px;
}
:host([dir="rtl"]) .mdc-data-table__header-cell--checkbox,
:host([dir="rtl"]) .mdc-data-table__cell--checkbox {
/* @noflip */
padding-left: 0;
/* @noflip */
padding-right: 16px;
}
.mdc-data-table__table {
height: 100%;
@@ -723,11 +720,7 @@ export class HaDataTable extends LitElement {
}
.mdc-data-table__cell--numeric {
text-align: right;
}
:host([dir="rtl"]) .mdc-data-table__cell--numeric {
/* @noflip */
text-align: left;
text-align: var(--float-end);
}
.mdc-data-table__cell--icon {
@@ -753,15 +746,7 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(
.not-sorted
) {
text-align: left;
}
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover,
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(
.not-sorted
) {
text-align: right;
text-align: var(--float-start);
}
.mdc-data-table__cell--icon:first-child img,
@@ -771,27 +756,14 @@ export class HaDataTable extends LitElement {
.mdc-data-table__cell--icon:first-child ha-domain-icon,
.mdc-data-table__cell--icon:first-child ha-service-icon {
margin-left: 8px;
}
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon,
:host([dir="rtl"])
.mdc-data-table__cell--icon:first-child
ha-state-icon,
:host([dir="rtl"])
.mdc-data-table__cell--icon:first-child
ha-svg-icon
:host([dir="rtl"])
.mdc-data-table__cell--icon:first-child
img {
margin-left: auto;
margin-right: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
}
.mdc-data-table__cell--icon:first-child state-badge {
margin-right: -8px;
}
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child state-badge {
margin-right: auto;
margin-left: -8px;
margin-inline-end: -8px;
margin-inline-start: initial;
}
.mdc-data-table__cell--overflow-menu,
@@ -824,15 +796,8 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell--icon-button:first-child,
.mdc-data-table__cell--icon-button:first-child {
padding-left: 16px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell--overflow-menu:first-child,
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child,
:host([dir="rtl"])
.mdc-data-table__header-cell--overflow-menu:first-child,
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child {
padding-left: 8px;
padding-right: 16px;
padding-inline-start: 16px;
padding-inline-end: initial;
}
.mdc-data-table__cell--overflow-menu:last-child,
@@ -840,14 +805,8 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell--icon-button:last-child,
.mdc-data-table__cell--icon-button:last-child {
padding-right: 16px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell--overflow-menu:last-child,
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:last-child,
:host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child,
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child {
padding-right: 8px;
padding-left: 16px;
padding-inline-end: 16px;
padding-inline-start: initial;
}
.mdc-data-table__cell--overflow-menu,
.mdc-data-table__header-cell--overflow-menu {
@@ -867,28 +826,15 @@ export class HaDataTable extends LitElement {
letter-spacing: 0.0071428571em;
text-decoration: inherit;
text-transform: inherit;
text-align: left;
}
:host([dir="rtl"]) .mdc-data-table__header-cell {
/* @noflip */
text-align: right;
text-align: var(--float-start);
}
.mdc-data-table__header-cell--numeric {
text-align: right;
text-align: var(--float-end);
}
.mdc-data-table__header-cell--numeric.sortable:hover,
.mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) {
text-align: left;
}
:host([dir="rtl"]) .mdc-data-table__header-cell--numeric {
/* @noflip */
text-align: left;
}
:host([dir="rtl"]) .mdc-data-table__header-cell--numeric.sortable:hover,
:host([dir="rtl"])
.mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) {
text-align: right;
text-align: var(--float-start);
}
/* custom from here */
@@ -909,20 +855,15 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell span {
position: relative;
left: 0px;
}
:host([dir="rtl"]) .mdc-data-table__header-cell span {
left: auto;
right: 0px;
inset-inline-start: 0px;
inset-inline-end: initial;
}
.mdc-data-table__header-cell.sortable {
cursor: pointer;
}
.mdc-data-table__header-cell > * {
transition: left 0.2s ease;
}
:host([dir="rtl"]) .mdc-data-table__header-cell > * {
transition: right 0.2s ease;
transition: var(--float-start) 0.2s ease;
}
.mdc-data-table__header-cell ha-svg-icon {
top: -3px;
@@ -930,35 +871,20 @@ export class HaDataTable extends LitElement {
}
.mdc-data-table__header-cell.not-sorted ha-svg-icon {
left: -20px;
}
:host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-svg-icon {
right: -20px;
inset-inline-start: -20px;
inset-inline-end: initial;
}
.mdc-data-table__header-cell.sortable:not(.not-sorted) span,
.mdc-data-table__header-cell.sortable.not-sorted:hover span {
left: 24px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable:not(.not-sorted)
span,
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable.not-sorted:hover
span {
left: auto;
right: 24px;
inset-inline-start: 24px;
inset-inline-end: initial;
}
.mdc-data-table__header-cell.sortable:not(.not-sorted) ha-svg-icon,
.mdc-data-table__header-cell.sortable:hover.not-sorted ha-svg-icon {
left: 12px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable:not(.not-sorted)
ha-svg-icon,
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable:hover.not-sorted
ha-svg-icon {
left: auto;
right: 12px;
inset-inline-start: 12px;
inset-inline-end: initial;
}
.table-header {
border-bottom: 1px solid var(--divider-color);
@@ -966,6 +892,8 @@ export class HaDataTable extends LitElement {
search-input {
display: block;
flex: 1;
--mdc-text-field-fill-color: var(--sidebar-background-color);
--mdc-text-field-idle-line-color: transparent;
}
slot[name="header"] {
display: block;

View File

@@ -9,6 +9,7 @@ import {
localizeWeekdays,
localizeMonths,
} 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({
@@ -157,7 +158,7 @@ class DateRangePickerElement extends WrappedElement {
min-width: initial !important;
max-height: var(--date-range-picker-max-height);
overflow-y: auto;
}
}
.daterangepicker:before {
display: none;
}
@@ -267,15 +268,37 @@ class DateRangePickerElement extends WrappedElement {
.calendar-table {
padding: 0 !important;
}
.daterangepicker.ltr {
.calendar-time {
direction: ltr;
text-align: left;
}
.daterangepicker.ltr {
direction: var(--direction);
text-align: var(--float-start);
}
.vue-daterange-picker{
min-width: unset !important;
display: block !important;
}
`;
if (mainWindow.document.dir === "rtl") {
style.innerHTML += `
.daterangepicker .calendar-table .next span {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
.daterangepicker .calendar-table .prev span {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
.daterangepicker td.start-date {
border-radius: 0 50% 50% 0;
}
.daterangepicker td.end-date {
border-radius: 50% 0 0 50%;
}
`;
}
const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style);
// Stop click events from reaching the document, otherwise it will close the picker immediately.

View File

@@ -133,9 +133,9 @@ export class HaStateLabelBadge extends LitElement {
entityState,
this._timerTimeRemaining
)}
.description=${this.showName === false
? undefined
: this.name ?? computeStateName(entityState)}
.description=${this.showName
? this.name ?? computeStateName(entityState)
: undefined}
>
${!image && showIcon
? html`<ha-state-icon

View File

@@ -3,7 +3,6 @@ import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { computeStateName } from "../../common/entity/compute_state_name";
import { computeRTL } from "../../common/util/compute_rtl";
import type { HomeAssistant } from "../../types";
import "../ha-relative-time";
import "./state-badge";
@@ -16,9 +15,6 @@ class StateInfo extends LitElement {
@property({ type: Boolean }) public inDialog = false;
// property used only in CSS
@property({ type: Boolean, reflect: true }) public rtl = false;
@property() public color?: string;
protected render() {
@@ -79,18 +75,6 @@ class StateInfo extends LitElement {
</div>`;
}
protected updated(changedProps) {
super.updated(changedProps);
if (!changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.locale !== this.hass.locale) {
this.rtl = computeRTL(this.hass);
}
}
static get styles(): CSSResultGroup {
return css`
:host {
@@ -106,17 +90,14 @@ class StateInfo extends LitElement {
.info {
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
min-width: 0;
}
:host([rtl]) .info {
margin-right: 8px;
margin-left: 0;
text-align: right;
text-align: var(--float-start);
}
.name {

View File

@@ -1,6 +1,6 @@
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
@@ -36,8 +36,12 @@ type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry;
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) =>
html`<ha-list-item
graphic="icon"
class=${classMap({ "add-new": item.area_id === "add_new" })}
>
${item.icon
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
: nothing}
${item.name}
</ha-list-item>`;
@@ -135,6 +139,7 @@ export class HaAreaPicker extends LitElement {
area_id: "no_areas",
name: this.hass.localize("ui.components.area-picker.no_areas"),
picture: null,
icon: null,
aliases: [],
},
];
@@ -262,7 +267,9 @@ export class HaAreaPicker extends LitElement {
}
if (areaIds) {
outputAreas = areas.filter((area) => areaIds!.includes(area.area_id));
outputAreas = outputAreas.filter((area) =>
areaIds!.includes(area.area_id)
);
}
if (excludeAreas) {
@@ -277,6 +284,7 @@ export class HaAreaPicker extends LitElement {
area_id: "no_areas",
name: this.hass.localize("ui.components.area-picker.no_match"),
picture: null,
icon: null,
aliases: [],
},
];
@@ -290,6 +298,7 @@ export class HaAreaPicker extends LitElement {
area_id: "add_new",
name: this.hass.localize("ui.components.area-picker.add_new"),
picture: null,
icon: "mdi:plus",
aliases: [],
},
];

View File

@@ -69,6 +69,7 @@ export class HaButtonToggleGroup extends LitElement {
display: flex;
--mdc-icon-button-size: var(--button-toggle-size, 36px);
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
direction: ltr;
}
mwc-button {
--mdc-shape-small: 0;
@@ -119,19 +120,6 @@ export class HaButtonToggleGroup extends LitElement {
--mdc-shape-small: 4px;
border-right-width: 1px;
}
:host([dir="rtl"]) ha-icon-button:first-child,
:host([dir="rtl"]) mwc-button:first-child {
border-radius: 0 4px 4px 0;
border-right-width: 1px;
--mdc-shape-small: 0 4px 4px 0;
--mdc-button-outline-width: 1px;
}
:host([dir="rtl"]) ha-icon-button:last-child,
:host([dir="rtl"]) mwc-button:last-child {
--mdc-shape-small: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
`;
}
}

View File

@@ -32,7 +32,6 @@ import { firstWeekdayIndex } from "../common/datetime/first_weekday";
import { formatDate } from "../common/datetime/format_date";
import { formatDateTime } from "../common/datetime/format_date_time";
import { useAmPm } from "../common/datetime/use_am_pm";
import { computeRTLDirection } from "../common/util/compute_rtl";
import { HomeAssistant } from "../types";
import "./date-range-picker";
import "./ha-icon-button";
@@ -65,8 +64,6 @@ export class HaDateRangePicker extends LitElement {
@state() private _hour24format = false;
@state() private _rtlDirection = "ltr";
@property({ type: Boolean }) public extendedPresets = false;
@property() public openingDirection?: "right" | "left" | "center" | "inline";
@@ -236,7 +233,6 @@ export class HaDateRangePicker extends LitElement {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.locale !== this.hass.locale) {
this._hour24format = !useAmPm(this.hass.locale);
this._rtlDirection = computeRTLDirection(this.hass);
}
}
}
@@ -306,11 +302,7 @@ export class HaDateRangePicker extends LitElement {
></ha-icon-button>`}
</div>
${this.ranges !== false && (this.ranges || this._ranges)
? html`<div
slot="ranges"
class="date-range-ranges"
.dir=${this._rtlDirection}
>
? html`<div slot="ranges" class="date-range-ranges">
<mwc-list @action=${this._setDateRange} activatable>
${Object.keys(this.ranges || this._ranges!).map(
(name) => html`<mwc-list-item>${name}</mwc-list-item>`

View File

@@ -3,6 +3,7 @@ import { styles } from "@material/mwc-drawer/mwc-drawer.css";
import { css, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
const blockingElements = (document as any).$blockingElements;
@@ -12,6 +13,8 @@ export class HaDrawer extends DrawerBase {
private _mc?: HammerManager;
private _rtlStyle?: HTMLElement;
protected createAdapter() {
return {
...super.createAdapter(),
@@ -31,8 +34,26 @@ export class HaDrawer extends DrawerBase {
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("direction")) {
this.mdcRoot.dir = this.direction;
if (mainWindow.document.dir === "rtl") {
this._rtlStyle = document.createElement("style");
this._rtlStyle.innerHTML = `
.mdc-drawer--animate {
transform: translateX(100%);
}
.mdc-drawer--opening {
transform: translateX(0);
}
.mdc-drawer--closing {
transform: translateX(100%);
}
`;
this.shadowRoot!.appendChild(this._rtlStyle);
} else if (this._rtlStyle) {
this.shadowRoot!.removeChild(this._rtlStyle);
}
}
if (changedProps.has("open") && this.open && this.type === "modal") {
this._setupSwipe();
} else if (this._mc) {
@@ -66,6 +87,8 @@ export class HaDrawer extends DrawerBase {
position: fixed;
top: 0;
border-color: var(--divider-color, rgba(0, 0, 0, 0.12));
inset-inline-start: 0 !important;
inset-inline-end: initial !important;
}
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
z-index: 200;

View File

@@ -2,6 +2,7 @@ import { LitElement, PropertyValues, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import QRCode from "qrcode";
import "./ha-alert";
import { rgb2hex } from "../common/color/convert-color";
@customElement("ha-qr-code")
export class HaQrCode extends LitElement {
@@ -65,16 +66,37 @@ export class HaQrCode extends LitElement {
changedProperties.has("centerImage"))
) {
const computedStyles = getComputedStyle(this);
const textRgb = computedStyles.getPropertyValue(
"--rgb-primary-text-color"
);
const backgroundRgb = computedStyles.getPropertyValue(
"--rgb-card-background-color"
);
const textHex = rgb2hex(
textRgb.split(",").map((a) => parseInt(a, 10)) as [
number,
number,
number,
]
);
const backgroundHex = rgb2hex(
backgroundRgb.split(",").map((a) => parseInt(a, 10)) as [
number,
number,
number,
]
);
QRCode.toCanvas(canvas, this.data, {
errorCorrectionLevel: this.errorCorrectionLevel,
errorCorrectionLevel:
this.errorCorrectionLevel || (this.centerImage ? "Q" : "M"),
width: this.width,
scale: this.scale,
margin: this.margin,
maskPattern: this.maskPattern,
color: {
light: computedStyles.getPropertyValue("--card-background-color"),
dark: computedStyles.getPropertyValue("--primary-text-color"),
light: backgroundHex,
dark: textHex,
},
}).catch((err) => {
this._error = err.message;

View File

@@ -0,0 +1,30 @@
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { QRCodeSelector } from "../../data/selector";
import "../ha-qr-code";
@customElement("ha-selector-qr_code")
export class HaSelectorQRCode extends LitElement {
@property({ attribute: false }) public selector!: QRCodeSelector;
protected render() {
return html`<ha-qr-code
.data=${this.selector.qr_code?.data}
.scale=${this.selector.qr_code?.scale}
.errorCorrectionLevel=${this.selector.qr_code?.error_correction_level}
.centerImage=${this.selector.qr_code?.center_image}
></ha-qr-code>`;
}
static styles = css`
ha-qr-code {
text-align: center;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-qr_code": HaSelectorQRCode;
}
}

View File

@@ -34,6 +34,7 @@ const LOAD_ELEMENTS = {
navigation: () => import("./ha-selector-navigation"),
number: () => import("./ha-selector-number"),
object: () => import("./ha-selector-object"),
qr_code: () => import("./ha-selector-qr-code"),
select: () => import("./ha-selector-select"),
selector: () => import("./ha-selector-selector"),
state: () => import("./ha-selector-state"),

View File

@@ -38,7 +38,6 @@ import { storage } from "../common/decorators/storage";
import { fireEvent } from "../common/dom/fire_event";
import { toggleAttribute } from "../common/dom/toggle_attribute";
import { stringCompare } from "../common/string/compare";
import { computeRTL } from "../common/util/compute_rtl";
import { throttle } from "../common/util/throttle";
import { ActionHandlerDetail } from "../data/lovelace/action_handler";
import {
@@ -307,16 +306,12 @@ class HaSidebar extends SubscribeMixin(LitElement) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.locale !== this.hass.locale) {
toggleAttribute(this, "rtl", computeRTL(this.hass));
}
this._calculateCounts();
if (!SUPPORT_SCROLL_IF_NEEDED) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) {
const selectedEl = this.shadowRoot!.querySelector(".iron-selected");
if (selectedEl) {
@@ -851,29 +846,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
font-size: 20px;
align-items: center;
padding-left: calc(4px + env(safe-area-inset-left));
}
:host([rtl]) .menu {
padding-left: 4px;
padding-right: calc(4px + env(safe-area-inset-right));
padding-inline-start: calc(4px + env(safe-area-inset-left));
padding-inline-end: initial;
}
:host([expanded]) .menu {
width: calc(256px + env(safe-area-inset-left));
}
:host([rtl][expanded]) .menu {
width: calc(256px + env(safe-area-inset-right));
}
.menu ha-icon-button {
color: var(--sidebar-icon-color);
}
.title {
margin-left: 19px;
margin-inline-start: 19px;
margin-inline-end: initial;
width: 100%;
display: none;
}
:host([rtl]) .title {
margin-left: 0;
margin-right: 19px;
}
:host([narrow]) .title {
margin: 0;
padding: 0 16px;
@@ -904,11 +892,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
overflow-x: hidden;
background: none;
margin-left: env(safe-area-inset-left);
}
:host([rtl]) paper-listbox {
margin-left: initial;
margin-right: env(safe-area-inset-right);
margin-inline-start: env(safe-area-inset-left);
margin-inline-end: initial;
}
a {
@@ -925,6 +910,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
box-sizing: border-box;
margin: 4px;
padding-left: 12px;
padding-inline-start: 12px;
padding-inline-end: initial;
border-radius: 4px;
--paper-item-min-height: 40px;
width: 48px;
@@ -932,10 +919,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
:host([expanded]) paper-icon-item {
width: 248px;
}
:host([rtl]) paper-icon-item {
padding-left: auto;
padding-right: 12px;
}
ha-icon[slot="item-icon"],
ha-svg-icon[slot="item-icon"] {
@@ -1010,11 +993,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
.configuration-container {
display: flex;
margin-left: env(safe-area-inset-left);
}
:host([rtl]) .notifications-container,
:host([rtl]) .configuration-container {
margin-left: initial;
margin-right: env(safe-area-inset-right);
margin-inline-start: env(safe-area-inset-left);
margin-inline-end: initial;
}
.notifications {
cursor: pointer;
@@ -1025,23 +1005,18 @@ class HaSidebar extends SubscribeMixin(LitElement) {
}
.profile {
margin-left: env(safe-area-inset-left);
}
:host([rtl]) .profile {
margin-left: initial;
margin-right: env(safe-area-inset-right);
margin-inline-start: env(safe-area-inset-left);
margin-inline-end: initial;
}
.profile paper-icon-item {
padding-left: 4px;
}
:host([rtl]) .profile paper-icon-item {
padding-left: auto;
padding-right: 4px;
margin-inline-start: 4px;
margin-inline-end: auto;
}
.profile .item-text {
margin-left: 8px;
}
:host([rtl]) .profile .item-text {
margin-right: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
}
.notification-badge,
@@ -1106,9 +1081,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
font-weight: 500;
}
:host([rtl]) .menu ha-icon-button {
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
.menu ha-icon-button {
-webkit-transform: scaleX(var(--scale-direction));
transform: scaleX(var(--scale-direction));
}
`,
];

View File

@@ -2,9 +2,15 @@ import { customElement } from "lit/decorators";
import "element-internals-polyfill";
import { MdSlider } from "@material/web/slider/slider";
import { CSSResult, css } from "lit";
import { mainWindow } from "../common/dom/get_main_window";
@customElement("ha-slider")
export class HaSlider extends MdSlider {
public connectedCallback() {
super.connectedCallback();
this.dir = mainWindow.document.dir;
}
static override styles: CSSResult[] = [
...MdSlider.styles,
css`

View File

@@ -39,6 +39,12 @@ export class HaSortable extends LitElement {
@property({ type: String, attribute: "group" })
public group?: string;
@property({ type: Number, attribute: "swap-threshold" })
public swapThreshold?: number;
@property({ type: Boolean, attribute: "invert-swap" })
public invertSwap?: boolean;
protected updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("disabled")) {
if (this.disabled) {
@@ -81,7 +87,7 @@ export class HaSortable extends LitElement {
}
.sortable-ghost {
border: 2px solid var(--primary-color);
box-shadow: 0 0 0 2px var(--primary-color);
background: rgba(var(--rgb-primary-color), 0.25);
border-radius: 4px;
opacity: 0.4;
@@ -108,7 +114,7 @@ export class HaSortable extends LitElement {
const options: SortableInstance.Options = {
animation: 150,
swapThreshold: 0.75,
swapThreshold: 1,
onChoose: this._handleChoose,
onEnd: this._handleEnd,
};
@@ -116,6 +122,13 @@ export class HaSortable extends LitElement {
if (this.draggableSelector) {
options.draggable = this.draggableSelector;
}
if (this.swapThreshold !== undefined) {
options.swapThreshold = this.swapThreshold;
}
if (this.invertSwap !== undefined) {
options.invertSwap = this.invertSwap;
}
if (this.handleSelector) {
options.handle = this.handleSelector;
}

View File

@@ -98,6 +98,7 @@ export class HaTargetPicker extends LitElement {
area_id,
area?.name || area_id,
undefined,
area?.icon,
mdiSofa
);
})
@@ -110,6 +111,7 @@ export class HaTargetPicker extends LitElement {
device_id,
device ? computeDeviceName(device, this.hass) : device_id,
undefined,
undefined,
mdiDevices
);
})
@@ -209,7 +211,8 @@ export class HaTargetPicker extends LitElement {
id: string,
name: string,
entityState?: HassEntity,
iconPath?: string
icon?: string | null,
fallbackIconPath?: string
) {
return html`
<div
@@ -217,12 +220,17 @@ export class HaTargetPicker extends LitElement {
[type]: true,
})}"
>
${iconPath
? html`<ha-svg-icon
${icon
? html`<ha-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${iconPath}
></ha-svg-icon>`
: ""}
.icon=${icon}
></ha-icon>`
: fallbackIconPath
? html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${fallbackIconPath}
></ha-svg-icon>`
: ""}
${entityState
? html`<ha-state-icon
class="mdc-chip__icon mdc-chip__icon--leading"

View File

@@ -3,18 +3,11 @@ import { styles as textfieldStyles } from "@material/mwc-textfield/mwc-textfield
import { styles as textareaStyles } from "@material/mwc-textarea/mwc-textarea.css";
import { css, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { mainWindow } from "../common/dom/get_main_window";
@customElement("ha-textarea")
export class HaTextArea extends TextAreaBase {
@property({ type: Boolean, reflect: true }) autogrow = false;
firstUpdated() {
super.firstUpdated();
this.setAttribute("dir", mainWindow.document.dir);
}
updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (this.autogrow && changedProperties.has("value")) {
@@ -54,9 +47,10 @@ export class HaTextArea extends TextAreaBase {
margin-top: 16px;
margin-bottom: 16px;
}
:host([dir="rtl"]) .mdc-floating-label {
right: 16px;
left: initial;
.mdc-floating-label {
inset-inline-start: 16px !important;
inset-inline-end: initial !important;
transform-origin: var(--float-start) top;
}
`,
];

View File

@@ -133,7 +133,7 @@ export class HaLocationsEditor extends LitElement {
.layers=${this._getLayers(this._circles, this._locationMarkers)}
.zoom=${this.zoom}
.autoFit=${this.autoFit}
.darkMode=${this.darkMode}
?darkMode=${this.darkMode}
></ha-map>
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`

View File

@@ -8,12 +8,18 @@ import type {
Marker,
Polyline,
} from "leaflet";
import { isToday } from "date-fns";
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
LeafletModuleType,
setupLeafletMap,
} from "../../common/dom/setup-leaflet-map";
import {
formatTimeWithSeconds,
formatTimeWeekday,
} from "../../common/datetime/format_time";
import { formatDateTime } from "../../common/datetime/format_date_time";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { loadPolyfillIfNeeded } from "../../resources/resize-observer.polyfill";
@@ -27,12 +33,14 @@ const getEntityId = (entity: string | HaMapEntity): string =>
export interface HaMapPathPoint {
point: LatLngTuple;
tooltip: string;
timestamp: Date;
}
export interface HaMapPaths {
points: HaMapPathPoint[];
color?: string;
name?: string;
gradualOpacity?: number;
fullDatetime?: boolean;
}
export interface HaMapEntity {
@@ -156,9 +164,9 @@ export class HaMap extends ReactiveElement {
}
private _updateMapStyle(): void {
const darkMode = this.darkMode ?? this.hass.themes.darkMode ?? false;
const forcedDark = this.darkMode ?? false;
const map = this.shadowRoot!.getElementById("map");
const darkMode = this.darkMode || (this.hass.themes.darkMode ?? false);
const forcedDark = this.darkMode;
const map = this.renderRoot.querySelector("#map");
map!.classList.toggle("dark", darkMode);
map!.classList.toggle("forced-dark", forcedDark);
}
@@ -242,6 +250,30 @@ export class HaMap extends ReactiveElement {
});
}
private _computePathTooltip(path: HaMapPaths, point: HaMapPathPoint): string {
let formattedTime: string;
if (path.fullDatetime) {
formattedTime = formatDateTime(
point.timestamp,
this.hass.locale,
this.hass.config
);
} else if (isToday(point.timestamp)) {
formattedTime = formatTimeWithSeconds(
point.timestamp,
this.hass.locale,
this.hass.config
);
} else {
formattedTime = formatTimeWeekday(
point.timestamp,
this.hass.locale,
this.hass.config
);
}
return `${path.name}<br>${formattedTime}`;
}
private _drawPaths(): void {
const hass = this.hass;
const map = this.leafletMap;
@@ -289,7 +321,10 @@ export class HaMap extends ReactiveElement {
fillOpacity: opacity,
interactive: true,
})
.bindTooltip(path.points[pointIndex].tooltip, { direction: "top" })
.bindTooltip(
this._computePathTooltip(path, path.points[pointIndex]),
{ direction: "top" }
)
);
// DRAW line between this and next point
@@ -319,7 +354,10 @@ export class HaMap extends ReactiveElement {
fillOpacity: opacity,
interactive: true,
})
.bindTooltip(path.points[pointIndex].tooltip, { direction: "top" })
.bindTooltip(
this._computePathTooltip(path, path.points[pointIndex]),
{ direction: "top" }
)
);
}
this._mapPaths.forEach((marker) => map.addLayer(marker));
@@ -361,7 +399,7 @@ export class HaMap extends ReactiveElement {
);
const className =
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light";
this.darkMode || this.hass.themes.darkMode ? "dark" : "light";
for (const entity of this.entities) {
const stateObj = hass.states[getEntityId(entity)];
@@ -556,6 +594,7 @@ export class HaMap extends ReactiveElement {
color: white !important;
border-radius: 4px;
box-shadow: none !important;
text-align: center;
}
`;
}

View File

@@ -25,7 +25,6 @@ import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { until } from "lit/directives/until";
import { fireEvent } from "../../common/dom/fire_event";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import { debounce } from "../../common/util/debounce";
import { isUnavailableState } from "../../data/entity";
import type { MediaPlayerItem } from "../../data/media-player";
@@ -539,7 +538,6 @@ export class HaMediaPlayerBrowse extends LitElement {
.graphic=${mediaClass.show_list_images
? "medium"
: "avatar"}
dir=${computeRTLDirection(this.hass)}
>
<span class="title">
${this.hass.localize(
@@ -637,7 +635,6 @@ export class HaMediaPlayerBrowse extends LitElement {
@click=${this._childClicked}
.item=${child}
.graphic=${mediaClass.show_list_images ? "medium" : "avatar"}
dir=${computeRTLDirection(this.hass)}
>
${backgroundImage === "none" && !child.can_play
? html`<ha-svg-icon
@@ -1198,10 +1195,8 @@ export class HaMediaPlayerBrowse extends LitElement {
mwc-list-item .title {
margin-left: 16px;
}
mwc-list-item[dir="rtl"] .title {
margin-right: 16px;
margin-left: 0;
margin-inline-start: 16px;
margin-inline-end: initial;
}
/* ============= Narrow ============= */
@@ -1332,6 +1327,10 @@ export class HaMediaPlayerBrowse extends LitElement {
lit-virtualizer.not_shown {
height: calc(100% - 36px);
}
ha-browse-media-tts {
direction: var(--direction);
}
`,
];
}

View File

@@ -5,6 +5,7 @@ import {
html,
TemplateResult,
svg,
nothing,
} from "lit";
import { customElement, property } from "lit/decorators";
import { NODE_SIZE, SPACING } from "./hat-graph-const";
@@ -51,7 +52,7 @@ export class HatGraphNode extends LitElement {
: Math.ceil((NODE_SIZE + SPACING * 2) / 2)} ${width} ${height}"
>
${this.graphStart
? ``
? nothing
: svg`
<path
class="connector"
@@ -64,7 +65,6 @@ export class HatGraphNode extends LitElement {
`}
<g class="node">
<circle cx="0" cy="0" r=${NODE_SIZE / 2} />
}
${this.badge
? svg`
<g class="number">
@@ -81,9 +81,11 @@ export class HatGraphNode extends LitElement {
>${this.badge > 9 ? "9+" : this.badge}</text>
</g>
`
: ""}
: nothing}
<g style="pointer-events: none" transform="translate(${-12} ${-12})">
${this.iconPath ? svg`<path class="icon" d=${this.iconPath}/>` : ""}
${this.iconPath
? svg`<path class="icon" d=${this.iconPath}/>`
: svg`<foreignObject><span class="icon"><slot name="icon"></slot></span></foreignObject>`}
</g>
</g>
</svg>
@@ -152,6 +154,13 @@ export class HatGraphNode extends LitElement {
path.icon {
fill: var(--icon-clr);
}
foreignObject {
width: 24px;
height: 24px;
}
.icon {
color: var(--icon-clr);
}
`;
}
}

View File

@@ -17,11 +17,10 @@ import {
mdiRoomService,
mdiShuffleDisabled,
} from "@mdi/js";
import { LitElement, PropertyValues, css, html } from "lit";
import { LitElement, PropertyValues, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { ensureArray } from "../../common/array/ensure-array";
import { fireEvent } from "../../common/dom/fire_event";
import { ACTION_ICONS } from "../../data/action";
import { Condition, Trigger } from "../../data/automation";
import {
Action,
@@ -41,11 +40,14 @@ import {
IfActionTraceStep,
TraceExtended,
} from "../../data/trace";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "../ha-service-icon";
import "./hat-graph-branch";
import { BRANCH_HEIGHT, NODE_SIZE, SPACING } from "./hat-graph-const";
import "./hat-graph-node";
import "./hat-graph-spacer";
import { ACTION_ICONS } from "../../data/action";
export interface NodeInfo {
path: string;
@@ -64,6 +66,8 @@ export class HatScriptGraph extends LitElement {
@property({ attribute: false }) public selected?: string;
public hass!: HomeAssistant;
public renderedNodes: Record<string, NodeInfo> = {};
public trackedNodes: Record<string, NodeInfo> = {};
@@ -415,13 +419,21 @@ export class HatScriptGraph extends LitElement {
return html`
<hat-graph-node
.graphStart=${graphStart}
.iconPath=${mdiRoomService}
.iconPath=${node.service ? undefined : mdiRoomService}
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
></hat-graph-node>
>
${node.service
? html`<ha-service-icon
slot="icon"
.hass=${this.hass}
.service=${node.service}
></ha-service-icon>`
: nothing}
</hat-graph-node>
`;
}
@@ -667,8 +679,6 @@ export class HatScriptGraph extends LitElement {
}
.parent {
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
margin-top: 16px;
}
.error {

View File

@@ -24,6 +24,8 @@ class HaUsersPickerLight extends LitElement {
@property({ attribute: false })
public users?: User[];
@property({ type: Boolean }) public disabled = false;
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
if (this.users === undefined) {
@@ -57,6 +59,7 @@ class HaUsersPickerLight extends LitElement {
this.users,
notSelectedUsers
)}
.disabled=${this.disabled}
@value-changed=${this._userChanged}
></ha-user-picker>
<ha-icon-button
@@ -78,7 +81,7 @@ class HaUsersPickerLight extends LitElement {
this.hass!.localize("ui.components.user-picker.add_user")}
.hass=${this.hass}
.users=${notSelectedUsers}
.disabled=${!notSelectedUsers?.length}
.disabled=${this.disabled || !notSelectedUsers?.length}
@value-changed=${this._addUser}
></ha-user-picker>
`;

View File

@@ -9,6 +9,7 @@ export interface AreaRegistryEntry {
area_id: string;
name: string;
picture: string | null;
icon: string | null;
aliases: string[];
}
@@ -23,6 +24,7 @@ export interface AreaDeviceLookup {
export interface AreaRegistryEntryMutableParams {
name: string;
picture?: string | null;
icon?: string | null;
aliases?: string[];
}

View File

@@ -74,8 +74,8 @@ export interface StateTrigger extends BaseTrigger {
platform: "state";
entity_id: string | string[];
attribute?: string;
from?: string | number;
to?: string | string[] | number;
from?: string | string[];
to?: string | string[];
for?: string | number | ForDict;
}

View File

@@ -199,57 +199,46 @@ const tryDescribeTrigger = (
// State Trigger
if (trigger.platform === "state") {
let base = "When";
const entities: string[] = [];
const states = hass.states;
let attribute = "";
if (trigger.attribute) {
const stateObj = Array.isArray(trigger.entity_id)
? hass.states[trigger.entity_id[0]]
: hass.states[trigger.entity_id];
base += ` ${computeAttributeNameDisplay(
attribute = computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
trigger.attribute
)} of`;
);
}
if (Array.isArray(trigger.entity_id)) {
for (const entity of trigger.entity_id.values()) {
const entityArray: string[] = ensureArray(trigger.entity_id);
if (entityArray) {
for (const entity of entityArray) {
if (states[entity]) {
entities.push(computeStateName(states[entity]) || entity);
}
}
} else if (trigger.entity_id) {
entities.push(
states[trigger.entity_id]
? computeStateName(states[trigger.entity_id])
: trigger.entity_id
);
}
if (entities.length === 0) {
// no entity_id or empty array
entities.push("something");
}
const stateObj = hass.states[entityArray[0]];
base += ` ${entities} changes`;
const stateObj =
hass.states[
Array.isArray(trigger.entity_id)
? trigger.entity_id[0]
: trigger.entity_id
];
let fromChoice = "other";
let fromString = "";
if (trigger.from !== undefined) {
let fromArray: string[] = [];
if (trigger.from === null) {
if (!trigger.attribute) {
base += " from any state";
fromChoice = "null";
}
} else if (Array.isArray(trigger.from)) {
} else {
fromArray = ensureArray(trigger.from);
const from: string[] = [];
for (const state of trigger.from.values()) {
for (const state of fromArray) {
from.push(
trigger.attribute
? hass
@@ -263,34 +252,25 @@ const tryDescribeTrigger = (
);
}
if (from.length !== 0) {
const fromString = formatListWithOrs(hass.locale, from);
base += ` from ${fromString}`;
fromString = formatListWithOrs(hass.locale, from);
fromChoice = "fromUsed";
}
} else {
base += ` from ${
trigger.attribute
? hass
.formatEntityAttributeValue(
stateObj,
trigger.attribute,
trigger.from
)
.toString()
: hass
.formatEntityState(stateObj, trigger.from.toString())
.toString()
}`;
}
}
let toChoice = "other";
let toString = "";
if (trigger.to !== undefined) {
let toArray: string[] = [];
if (trigger.to === null) {
if (!trigger.attribute) {
base += " to any state";
toChoice = "null";
}
} else if (Array.isArray(trigger.to)) {
} else {
toArray = ensureArray(trigger.to);
const to: string[] = [];
for (const state of trigger.to.values()) {
for (const state of toArray) {
to.push(
trigger.attribute
? hass
@@ -304,21 +284,9 @@ const tryDescribeTrigger = (
);
}
if (to.length !== 0) {
const toString = formatListWithOrs(hass.locale, to);
base += ` to ${toString}`;
toString = formatListWithOrs(hass.locale, to);
toChoice = "toUsed";
}
} else {
base += ` to ${
trigger.attribute
? hass
.formatEntityAttributeValue(
stateObj,
trigger.attribute,
trigger.to
)
.toString()
: hass.formatEntityState(stateObj, trigger.to.toString())
}`;
}
}
@@ -327,17 +295,29 @@ const tryDescribeTrigger = (
trigger.from === undefined &&
trigger.to === undefined
) {
base += " state or any attributes";
toChoice = "special";
}
let duration = "";
if (trigger.for) {
const duration = describeDuration(hass.locale, trigger.for);
if (duration) {
base += ` for ${duration}`;
}
duration = describeDuration(hass.locale, trigger.for) ?? "";
}
return base;
return hass.localize(
`${triggerTranslationBaseKey}.state.description.full`,
{
hasAttribute: attribute !== "" ? "true" : "false",
attribute: attribute,
hasEntity: entities.length !== 0 ? "true" : "false",
entity: formatListWithOrs(hass.locale, entities),
fromChoice: fromChoice,
fromString: fromString,
toChoice: toChoice,
toString: toString,
hasDuration: duration !== "" ? "true" : "false",
duration: duration,
}
);
}
// Sun Trigger

View File

@@ -72,6 +72,8 @@ export const enum ClimateEntityFeature {
PRESET_MODE = 16,
SWING_MODE = 32,
AUX_HEAT = 64,
TURN_OFF = 128,
TURN_ON = 256,
}
const hvacModeOrdering = HVAC_MODES.reduce(

View File

@@ -470,9 +470,15 @@ export const computeHistory = (
}[domain];
}
const deviceClass: string | undefined = (
currentState?.attributes || numericStateFromHistory?.a
)?.device_class;
const specialDomainClasses = {
climate: "temperature",
humidifier: "humidity",
water_heater: "temperature",
};
const deviceClass: string | undefined =
specialDomainClasses[domain] ||
(currentState?.attributes || numericStateFromHistory?.a)?.device_class;
const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);

View File

@@ -8,37 +8,49 @@ import {
EntityRegistryDisplayEntry,
EntityRegistryEntry,
} from "./entity_registry";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { atLeastVersion } from "../common/config/version";
const resources: Record<IconCategory, any> = {
const resources: {
entity: Record<string, Promise<PlatformIcons>>;
entity_component: {
domains?: string[];
resources?: Promise<Record<string, ComponentIcons>>;
};
services: {
all?: Promise<Record<string, ServiceIcons>>;
domains: { [domain: string]: ServiceIcons | Promise<ServiceIcons> };
};
} = {
entity: {},
entity_component: undefined,
services: {},
entity_component: {},
services: { domains: {} },
};
interface IconResources {
resources: Record<string, string | Record<string, string>>;
interface IconResources<
T extends ComponentIcons | PlatformIcons | ServiceIcons,
> {
resources: Record<string, T>;
}
interface PlatformIcons {
[domain: string]: {
[translation_key: string]: {
state: Record<string, string>;
state_attributes: Record<
string,
{
state: Record<string, string>;
default: string;
}
>;
default: string;
};
[translation_key: string]: {
state: Record<string, string>;
state_attributes: Record<
string,
{
state: Record<string, string>;
default: string;
}
>;
default: string;
};
}
interface ComponentIcons {
export interface ComponentIcons {
[device_class: string]: {
state: Record<string, string>;
state_attributes: Record<
state?: Record<string, string>;
state_attributes?: Record<
string,
{
state: Record<string, string>;
@@ -55,12 +67,18 @@ interface ServiceIcons {
export type IconCategory = "entity" | "entity_component" | "services";
export const getHassIcons = async (
type CategoryType = {
entity: PlatformIcons;
entity_component: ComponentIcons;
services: ServiceIcons;
};
export const getHassIcons = async <T extends IconCategory>(
hass: HomeAssistant,
category: IconCategory,
category: T,
integration?: string
): Promise<IconResources> =>
hass.callWS<{ resources: Record<string, string> }>({
) =>
hass.callWS<IconResources<CategoryType[T]>>({
type: "frontend/get_icons",
category,
integration,
@@ -70,14 +88,20 @@ export const getPlatformIcons = async (
hass: HomeAssistant,
integration: string,
force = false
): Promise<PlatformIcons> => {
): Promise<PlatformIcons | undefined> => {
if (!force && integration in resources.entity) {
return resources.entity[integration];
}
const result = getHassIcons(hass, "entity", integration);
resources.entity[integration] = result.then(
if (
!isComponentLoaded(hass, integration) ||
!atLeastVersion(hass.connection.haVersion, 2024, 2)
) {
return undefined;
}
const result = getHassIcons(hass, "entity", integration).then(
(res) => res?.resources[integration]
);
resources.entity[integration] = result;
return resources.entity[integration];
};
@@ -85,45 +109,70 @@ export const getComponentIcons = async (
hass: HomeAssistant,
domain: string,
force = false
): Promise<ComponentIcons> => {
if (!force && resources.entity_component) {
return resources.entity_component.then((res) => res[domain]);
): Promise<ComponentIcons | undefined> => {
// For Cast, old instances can connect to it.
if (
__BACKWARDS_COMPAT__ &&
!atLeastVersion(hass.connection.haVersion, 2024, 2)
) {
return import("../fake_data/entity_component_icons")
.then((mod) => mod.ENTITY_COMPONENT_ICONS)
.then((res) => res[domain]);
}
resources.entity_component = getHassIcons(hass, "entity_component").then(
(result) => result.resources
);
return resources.entity_component.then((res) => res[domain]);
if (
!force &&
resources.entity_component.resources &&
resources.entity_component.domains?.includes(domain)
) {
return resources.entity_component.resources.then((res) => res[domain]);
}
if (!isComponentLoaded(hass, domain)) {
return undefined;
}
resources.entity_component.domains = [...hass.config.components];
resources.entity_component.resources = getHassIcons(
hass,
"entity_component"
).then((result) => result.resources);
return resources.entity_component.resources.then((res) => res[domain]);
};
export const getServiceIcons = async (
hass: HomeAssistant,
domain?: string,
force = false
): Promise<ServiceIcons> => {
): Promise<ServiceIcons | Record<string, ServiceIcons> | undefined> => {
if (!domain) {
if (!force && resources.services.all) {
return resources.services.all;
}
resources.services.all = getHassIcons(hass, "services", domain).then(
(res) => {
resources.services = res.resources;
resources.services.domains = res.resources;
return res?.resources;
}
);
return resources.services.all;
}
if (!force && domain && domain in resources.services) {
return resources.services[domain];
if (!force && domain in resources.services.domains) {
return resources.services.domains[domain];
}
if (resources.services.all && !force) {
await resources.services.all;
if (domain in resources.services) {
return resources.services[domain];
if (domain in resources.services.domains) {
return resources.services.domains[domain];
}
}
if (!isComponentLoaded(hass, domain)) {
return undefined;
}
const result = getHassIcons(hass, "services", domain);
resources.services[domain] = result.then((res) => res?.resources[domain]);
return resources.services[domain];
resources.services.domains[domain] = result.then(
(res) => res?.resources[domain]
);
return resources.services.domains[domain];
};
export const entityIcon = async (
@@ -238,7 +287,7 @@ export const serviceIcon = async (
const serviceName = computeObjectId(service);
const serviceIcons = await getServiceIcons(hass, domain);
if (serviceIcons) {
icon = serviceIcons[serviceName];
icon = serviceIcons[serviceName] as string;
}
if (!icon) {
icon = await domainIcon(hass, domain);

View File

@@ -3,6 +3,50 @@ import { navigate } from "../common/navigate";
import { HomeAssistant } from "../types";
import { subscribeDeviceRegistry } from "./device_registry";
export enum NetworkType {
THREAD = "thread",
WIFI = "wifi",
ETHERNET = "ethernet",
UNKNOWN = "unknown",
}
export enum NodeType {
END_DEVICE = "end_device",
SLEEPY_END_DEVICE = "sleepy_end_device",
ROUTING_END_DEVICE = "routing_end_device",
BRIDGE = "bridge",
UNKNOWN = "unknown",
}
export interface MatterFabricData {
fabric_id: number;
vendor_id: number;
fabric_index: number;
fabric_label?: string;
vendor_name?: string;
}
export interface MatterNodeDiagnostics {
node_id: number;
network_type: NetworkType;
node_type: NodeType;
network_name?: string;
ip_adresses: string[];
mac_address?: string;
available: boolean;
active_fabrics: MatterFabricData[];
}
export interface MatterPingResult {
[ip_address: string]: boolean;
}
export interface MatterCommissioningParameters {
setup_pin_code: number;
setup_manual_code: string;
setup_qr_code: string;
}
export const canCommissionMatterExternal = (hass: HomeAssistant) =>
hass.auth.external?.config.canCommissionMatter;
@@ -86,3 +130,50 @@ export const matterSetThread = (
type: "matter/set_thread",
thread_operation_dataset,
});
export const getMatterNodeDiagnostics = (
hass: HomeAssistant,
device_id: string
): Promise<MatterNodeDiagnostics> =>
hass.callWS({
type: "matter/node_diagnostics",
device_id,
});
export const pingMatterNode = (
hass: HomeAssistant,
device_id: string
): Promise<MatterPingResult> =>
hass.callWS({
type: "matter/ping_node",
device_id,
});
export const openMatterCommissioningWindow = (
hass: HomeAssistant,
device_id: string
): Promise<MatterCommissioningParameters> =>
hass.callWS({
type: "matter/open_commissioning_window",
device_id,
});
export const removeMatterFabric = (
hass: HomeAssistant,
device_id: string,
fabric_index: number
): Promise<void> =>
hass.callWS({
type: "matter/remove_matter_fabric",
device_id,
fabric_index,
});
export const interviewMatterNode = (
hass: HomeAssistant,
device_id: string
): Promise<void> =>
hass.callWS({
type: "matter/interview_node",
device_id,
});

View File

@@ -32,6 +32,13 @@ export const fetchRepairsIssues = (conn: Connection) =>
type: "repairs/list_issues",
});
export const fetchRepairsIssueData = (conn: Connection, domain, issue_id) =>
conn.sendMessagePromise<{ issue_data: { string: any } }>({
type: "repairs/get_issue_data",
domain,
issue_id,
});
export const ignoreRepairsIssue = async (
hass: HomeAssistant,
issue: RepairsIssue,

View File

@@ -41,6 +41,7 @@ export type Selector =
| NumberSelector
| ObjectSelector
| AssistPipelineSelector
| QRCodeSelector
| SelectSelector
| SelectorSelector
| StateSelector
@@ -340,6 +341,15 @@ export interface BackupLocationSelector {
backup_location: {} | null;
}
export interface QRCodeSelector {
qr_code: {
data: string;
scale?: number;
error_correction_level?: "low" | "medium" | "quartile" | "high";
center_image?: string;
} | null;
}
export interface StringSelector {
text: {
multiline?: boolean;

View File

@@ -22,6 +22,7 @@ export interface ThreadDataSet {
network_name: string;
pan_id: string | null;
preferred_border_agent_id: string | null;
preferred_extended_address: string | null;
preferred: boolean;
source: string;
}
@@ -107,10 +108,12 @@ export const setPreferredThreadDataSet = (
export const setPreferredBorderAgent = (
hass: HomeAssistant,
dataset_id: string,
border_agent_id: string
border_agent_id: string | null,
extended_address: string
): Promise<void> =>
hass.callWS({
type: "thread/set_preferred_border_agent_id",
type: "thread/set_preferred_border_agent",
dataset_id,
border_agent_id,
extended_address,
});

View File

@@ -1,19 +1,12 @@
import { Connection, createCollection } from "home-assistant-js-websocket";
import { Store } from "home-assistant-js-websocket/dist/store";
import { stringCompare } from "../common/string/compare";
import { AreaRegistryEntry } from "./area_registry";
import { debounce } from "../common/util/debounce";
import { AreaRegistryEntry } from "./area_registry";
const fetchAreaRegistry = (conn: Connection) =>
conn
.sendMessagePromise({
type: "config/area_registry/list",
})
.then((areas) =>
(areas as AreaRegistryEntry[]).sort((ent1, ent2) =>
stringCompare(ent1.name, ent2.name)
)
);
conn.sendMessagePromise<AreaRegistryEntry[]>({
type: "config/area_registry/list",
});
const subscribeAreaRegistryUpdates = (
conn: Connection,

View File

@@ -73,7 +73,7 @@ export interface ClusterAttributeData {
export interface AttributeConfigurationStatus {
id: number;
name: string;
success: boolean | undefined;
status: string;
min: number;
max: number;
change: number;

View File

@@ -2,7 +2,6 @@ import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import "../../components/ha-dialog";
import "../../components/ha-formfield";
import "../../components/ha-switch";
@@ -82,7 +81,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
}
)}
</p>`}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${!this._disableNewEntities}
@@ -109,7 +107,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
}
)}
</p>`}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${!this._disablePolling}

View File

@@ -14,7 +14,6 @@ import { customElement, property } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { stateActive } from "../../../common/entity/state_active";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import "../../../components/ha-icon-button";
import "../../../components/ha-select";
import "../../../components/ha-slider";
@@ -131,7 +130,6 @@ class MoreInfoMediaPlayer extends LitElement {
<ha-slider
labeled
id="input"
.dir=${computeRTLDirection(this.hass!)}
.value=${Number(stateObj.attributes.volume_level) * 100}
@change=${this._selectedValueChanged}
></ha-slider>

View File

@@ -35,6 +35,13 @@ interface EMOutgoingMessageConfigGet extends EMMessage {
type: "config/get";
}
interface EMOutgoingMessageScanQRCode extends EMMessage {
type: "qr_code/scan";
title: string;
description: string;
alternative_option_label?: string;
}
interface EMOutgoingMessageMatterCommission extends EMMessage {
type: "matter/commission";
}
@@ -48,6 +55,13 @@ type EMOutgoingMessageWithAnswer = {
request: EMOutgoingMessageConfigGet;
response: ExternalConfig;
};
"qr_code/scan": {
request: EMOutgoingMessageScanQRCode;
response:
| EMIncomingMessageQRCodeResponseCanceled
| EMIncomingMessageQRCodeResponseAlternativeOptions
| EMIncomingMessageQRCodeResponseScanResult;
};
};
interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage {
@@ -158,6 +172,19 @@ interface EMIncomingMessageShowAutomationEditor {
};
}
export interface EMIncomingMessageQRCodeResponseCanceled {
action: "canceled";
}
export interface EMIncomingMessageQRCodeResponseAlternativeOptions {
action: "alternative_options";
}
export interface EMIncomingMessageQRCodeResponseScanResult {
action: "scan_result";
result: string;
}
export type EMIncomingMessageCommands =
| EMIncomingMessageRestart
| EMIncomingMessageShowNotifications
@@ -180,6 +207,7 @@ export interface ExternalConfig {
canCommissionMatter: boolean;
canImportThreadCredentials: boolean;
hasAssist: boolean;
hasQRScanner: number;
}
export class ExternalMessaging {

View File

@@ -14,7 +14,53 @@ export const demoConfig: HassConfig = {
wind_speed: "m/s",
accumulated_precipitation: "mm",
},
components: ["notify.html5", "history", "todo", "forecast_solar", "energy"],
components: [
"notify.html5",
"history",
"forecast_solar",
"energy",
"person",
"number",
"select",
"tts",
"datetime",
"vacuum",
"wake_word",
"light",
"alarm_control_panel",
"text",
"lawn_mower",
"siren",
"input_boolean",
"lock",
"calendar",
"image",
"device_tracker",
"scene",
"script",
"todo",
"cover",
"switch",
"button",
"water_heater",
"binary_sensor",
"sensor",
"humidifier",
"valve",
"time",
"media_player",
"air_quality",
"camera",
"date",
"fan",
"automation",
"weather",
"climate",
"stt",
"update",
"event",
"demo",
],
time_zone: "America/Los_Angeles",
config_dir: "/config",
version: "DEMO",

View File

@@ -0,0 +1,962 @@
import { ComponentIcons } from "../data/icons";
export const ENTITY_COMPONENT_ICONS: Record<string, ComponentIcons> = {
person: {
_: {
default: "mdi:account",
state: {
not_home: "mdi:account-arrow-right",
},
},
},
number: {
_: {
default: "mdi:ray-vertex",
},
apparent_power: {
default: "mdi:flash",
},
aqi: {
default: "mdi:air-filter",
},
atmospheric_pressure: {
default: "mdi:thermometer-lines",
},
battery: {
default: "mdi:battery",
},
carbon_dioxide: {
default: "mdi:molecule-co2",
},
carbon_monoxide: {
default: "mdi:molecule-co",
},
current: {
default: "mdi:current-ac",
},
data_rate: {
default: "mdi:transmission-tower",
},
data_size: {
default: "mdi:database",
},
distance: {
default: "mdi:arrow-left-right",
},
duration: {
default: "mdi:progress-clock",
},
energy: {
default: "mdi:lightning-bolt",
},
energy_storage: {
default: "mdi:car-battery",
},
frequency: {
default: "mdi:sine-wave",
},
gas: {
default: "mdi:meter-gas",
},
humidity: {
default: "mdi:water-percent",
},
illuminance: {
default: "mdi:brightness-5",
},
irradiance: {
default: "mdi:sun-wireless",
},
moisture: {
default: "mdi:water-percent",
},
monetary: {
default: "mdi:cash",
},
nitrogen_dioxide: {
default: "mdi:molecule",
},
nitrogen_monoxide: {
default: "mdi:molecule",
},
nitrous_oxide: {
default: "mdi:molecule",
},
ozone: {
default: "mdi:molecule",
},
ph: {
default: "mdi:ph",
},
pm1: {
default: "mdi:molecule",
},
pm10: {
default: "mdi:molecule",
},
pm25: {
default: "mdi:molecule",
},
power: {
default: "mdi:flash",
},
power_factor: {
default: "mdi:angle-acute",
},
precipitation: {
default: "mdi:weather-rainy",
},
precipitation_intensity: {
default: "mdi:weather-pouring",
},
pressure: {
default: "mdi:gauge",
},
reactive_power: {
default: "mdi:flash",
},
signal_strength: {
default: "mdi:wifi",
},
sound_pressure: {
default: "mdi:ear-hearing",
},
speed: {
default: "mdi:speedometer",
},
sulfur_dioxide: {
default: "mdi:molecule",
},
temperature: {
default: "mdi:thermometer",
},
volatile_organic_compounds: {
default: "mdi:molecule",
},
volatile_organic_compounds_parts: {
default: "mdi:molecule",
},
voltage: {
default: "mdi:sine-wave",
},
volume: {
default: "mdi:car-coolant-level",
},
volume_storage: {
default: "mdi:storage-tank",
},
water: {
default: "mdi:water",
},
weight: {
default: "mdi:weight",
},
wind_speed: {
default: "mdi:weather-windy",
},
},
select: {
_: {
default: "mdi:format-list-bulleted",
},
},
tts: {
_: {
default: "mdi:speaker-message",
},
},
datetime: {
_: {
default: "mdi:calendar-clock",
},
},
vacuum: {
_: {
default: "mdi:robot-vacuum",
},
},
wake_word: {
_: {
default: "mdi:chat-sleep",
},
},
light: {
_: {
default: "mdi:lightbulb",
},
},
alarm_control_panel: {
_: {
default: "mdi:shield",
state: {
armed_away: "mdi:shield-lock",
armed_custom_bypass: "mdi:security",
armed_home: "mdi:shield-home",
armed_night: "mdi:shield-moon",
armed_vacation: "mdi:shield-airplane",
disarmed: "mdi:shield-off",
pending: "mdi:shield-outline",
triggered: "mdi:bell-ring",
},
},
},
text: {
_: {
default: "mdi:form-textbox",
},
},
lawn_mower: {
_: {
default: "mdi:robot-mower",
},
},
siren: {
_: {
default: "mdi:bullhorn",
},
},
input_boolean: {
_: {
default: "mdi:check-circle-outline",
state: {
off: "mdi:close-circle-outline",
},
},
},
lock: {
_: {
default: "mdi:lock",
state: {
jammed: "mdi:lock-alert",
locking: "mdi:lock-clock",
unlocked: "mdi:lock-open",
unlocking: "mdi:lock-clock",
},
},
},
calendar: {
_: {
default: "mdi:calendar",
state: {
on: "mdi:calendar-check",
off: "mdi:calendar-blank",
},
},
},
image: {
_: {
default: "mdi:image",
},
},
device_tracker: {
_: {
default: "mdi:account",
state: {
not_home: "mdi:account-arrow-right",
},
},
},
scene: {
_: {
default: "mdi:palette",
},
},
script: {
_: {
default: "mdi:script-text",
state: {
on: "mdi:script-text-play",
},
},
},
todo: {
_: {
default: "mdi:clipboard-list",
},
},
cover: {
_: {
default: "mdi:window-open",
state: {
closed: "mdi:window-closed",
closing: "mdi:arrow-down-box",
opening: "mdi:arrow-up-box",
},
},
blind: {
default: "mdi:blinds-horizontal",
state: {
closed: "mdi:blinds-horizontal-closed",
closing: "mdi:arrow-down-box",
opening: "mdi:arrow-up-box",
},
},
curtain: {
default: "mdi:curtains",
state: {
closed: "mdi:curtains-closed",
closing: "mdi:arrow-collapse-horizontal",
opening: "mdi:arrow-split-vertical",
},
},
damper: {
default: "mdi:circle",
state: {
closed: "mdi:circle-slice-8",
},
},
door: {
default: "mdi:door-open",
state: {
closed: "mdi:door-closed",
},
},
garage: {
default: "mdi:garage-open",
state: {
closed: "mdi:garage",
closing: "mdi:arrow-down-box",
opening: "mdi:arrow-up-box",
},
},
gate: {
default: "mdi:gate-open",
state: {
closed: "mdi:gate",
closing: "mdi:arrow-right",
opening: "mdi:arrow-right",
},
},
shade: {
default: "mdi:roller-shade",
state: {
closed: "mdi:roller-shade-closed",
closing: "mdi:arrow-down-box",
opening: "mdi:arrow-up-box",
},
},
shutter: {
default: "mdi:window-shutter-open",
state: {
closed: "mdi:window-shutter",
closing: "mdi:arrow-down-box",
opening: "mdi:arrow-up-box",
},
},
window: {
default: "mdi:window-open",
state: {
closed: "mdi:window-closed",
closing: "mdi:arrow-down-box",
opening: "mdi:arrow-up-box",
},
},
},
switch: {
_: {
default: "mdi:toggle-switch-variant",
},
switch: {
default: "mdi:toggle-switch-variant",
state: {
off: "mdi:toggle-switch-variant-off",
},
},
outlet: {
default: "mdi:power-plug",
state: {
off: "mdi:power-plug-off",
},
},
},
button: {
_: {
default: "mdi:button-pointer",
},
restart: {
default: "mdi:restart",
},
identify: {
default: "mdi:crosshairs-question",
},
update: {
default: "mdi:package-up",
},
},
water_heater: {
_: {
default: "mdi:water-boiler",
state: {
off: "mdi:water-boiler-off",
},
state_attributes: {
operation_mode: {
default: "mdi:circle-medium",
state: {
eco: "mdi:leaf",
electric: "mdi:lightning-bolt",
gas: "mdi:fire-circle",
heat_pump: "mdi:heat-wave",
high_demand: "mdi:finance",
off: "mdi:power",
performance: "mdi:rocket-launch",
},
},
},
},
},
binary_sensor: {
_: {
default: "mdi:radiobox-blank",
state: {
on: "mdi:checkbox-marked-circle",
},
},
battery: {
default: "mdi:battery",
state: {
on: "mdi:battery-outline",
},
},
battery_charging: {
default: "mdi:battery",
state: {
on: "mdi:battery-charging",
},
},
carbon_monoxide: {
default: "mdi:smoke-detector",
state: {
on: "mdi:smoke-detector-alert",
},
},
cold: {
default: "mdi:thermometer",
state: {
on: "mdi:snowflake",
},
},
connectivity: {
default: "mdi:close-network-outline",
state: {
on: "mdi:check-network-outline",
},
},
door: {
default: "mdi:door-closed",
state: {
on: "mdi:door-open",
},
},
garage_door: {
default: "mdi:garage",
state: {
on: "mdi:garage-open",
},
},
gas: {
default: "mdi:check-circle",
state: {
on: "mdi:alert-circle",
},
},
heat: {
default: "mdi:thermometer",
state: {
on: "mdi:fire",
},
},
light: {
default: "mdi:brightness-5",
state: {
on: "mdi:brightness-7",
},
},
lock: {
default: "mdi:lock",
state: {
on: "mdi:lock-open",
},
},
moisture: {
default: "mdi:water-off",
state: {
on: "mdi:water",
},
},
motion: {
default: "mdi:motion-sensor-off",
state: {
on: "mdi:motion-sensor",
},
},
moving: {
default: "mdi:arrow-right",
state: {
on: "mdi:octagon",
},
},
occupancy: {
default: "mdi:home-outline",
state: {
on: "mdi:home",
},
},
opening: {
default: "mdi:square",
state: {
on: "mdi:square-outline",
},
},
plug: {
default: "mdi:power-plug-off",
state: {
on: "mdi:power-plug",
},
},
power: {
default: "mdi:power-plug-off",
state: {
on: "mdi:power-plug",
},
},
presence: {
default: "mdi:home-outline",
state: {
on: "mdi:home",
},
},
problem: {
default: "mdi:check-circle",
state: {
on: "mdi:alert-circle",
},
},
running: {
default: "mdi:stop",
state: {
on: "mdi:play",
},
},
safety: {
default: "mdi:check-circle",
state: {
on: "mdi:alert-circle",
},
},
smoke: {
default: "mdi:smoke-detector-variant",
state: {
on: "mdi:smoke-detector-variant-alert",
},
},
sound: {
default: "mdi:music-note-off",
state: {
on: "mdi:music-note",
},
},
tamper: {
default: "mdi:check-circle",
state: {
on: "mdi:alert-circle",
},
},
update: {
default: "mdi:package",
state: {
on: "mdi:package-up",
},
},
vibration: {
default: "mdi:crop-portrait",
state: {
on: "mdi:vibrate",
},
},
window: {
default: "mdi:window-closed",
state: {
on: "mdi:window-open",
},
},
},
sensor: {
_: {
default: "mdi:eye",
},
apparent_power: {
default: "mdi:flash",
},
aqi: {
default: "mdi:air-filter",
},
atmospheric_pressure: {
default: "mdi:thermometer-lines",
},
carbon_dioxide: {
default: "mdi:molecule-co2",
},
carbon_monoxide: {
default: "mdi:molecule-co",
},
current: {
default: "mdi:current-ac",
},
data_rate: {
default: "mdi:transmission-tower",
},
data_size: {
default: "mdi:database",
},
date: {
default: "mdi:calendar",
},
distance: {
default: "mdi:arrow-left-right",
},
duration: {
default: "mdi:progress-clock",
},
energy: {
default: "mdi:lightning-bolt",
},
energy_storage: {
default: "mdi:car-battery",
},
enum: {
default: "mdi:eye",
},
frequency: {
default: "mdi:sine-wave",
},
gas: {
default: "mdi:meter-gas",
},
humidity: {
default: "mdi:water-percent",
},
illuminance: {
default: "mdi:brightness-5",
},
irradiance: {
default: "mdi:sun-wireless",
},
moisture: {
default: "mdi:water-percent",
},
monetary: {
default: "mdi:cash",
},
nitrogen_dioxide: {
default: "mdi:molecule",
},
nitrogen_monoxide: {
default: "mdi:molecule",
},
nitrous_oxide: {
default: "mdi:molecule",
},
ozone: {
default: "mdi:molecule",
},
ph: {
default: "mdi:ph",
},
pm1: {
default: "mdi:molecule",
},
pm10: {
default: "mdi:molecule",
},
pm25: {
default: "mdi:molecule",
},
power: {
default: "mdi:flash",
},
power_factor: {
default: "mdi:angle-acute",
},
precipitation: {
default: "mdi:weather-rainy",
},
precipitation_intensity: {
default: "mdi:weather-pouring",
},
pressure: {
default: "mdi:gauge",
},
reactive_power: {
default: "mdi:flash",
},
signal_strength: {
default: "mdi:wifi",
},
sound_pressure: {
default: "mdi:ear-hearing",
},
speed: {
default: "mdi:speedometer",
},
sulfur_dioxide: {
default: "mdi:molecule",
},
temperature: {
default: "mdi:thermometer",
},
timestamp: {
default: "mdi:clock",
},
volatile_organic_compounds: {
default: "mdi:molecule",
},
volatile_organic_compounds_parts: {
default: "mdi:molecule",
},
voltage: {
default: "mdi:sine-wave",
},
volume: {
default: "mdi:car-coolant-level",
},
volume_storage: {
default: "mdi:storage-tank",
},
water: {
default: "mdi:water",
},
weight: {
default: "mdi:weight",
},
wind_speed: {
default: "mdi:weather-windy",
},
},
humidifier: {
_: {
default: "mdi:air-humidifier",
state: {
off: "mdi:air-humidifier-off",
},
state_attributes: {
action: {
default: "mdi:circle-medium",
state: {
drying: "mdi:arrow-down-bold",
humidifying: "mdi:arrow-up-bold",
idle: "mdi:clock-outline",
off: "mdi:power",
},
},
mode: {
default: "mdi:circle-medium",
state: {
auto: "mdi:refresh-auto",
away: "mdi:account-arrow-right",
baby: "mdi:baby-carriage",
boost: "mdi:rocket-launch",
comfort: "mdi:sofa",
eco: "mdi:leaf",
home: "mdi:home",
normal: "mdi:water-percent",
sleep: "mdi:power-sleep",
},
},
},
},
},
valve: {
_: {
default: "mdi:pipe-valve",
},
gas: {
default: "mdi:meter-gas",
},
water: {
default: "mdi:pipe-valve",
},
},
time: {
_: {
default: "mdi:clock",
},
},
media_player: {
_: {
default: "mdi:cast",
state: {
off: "mdi:cast-off",
paused: "mdi:cast-connected",
playing: "mdi:cast-connected",
},
},
receiver: {
default: "mdi:audio-video",
state: {
off: "mdi:audio-video-off",
},
},
speaker: {
default: "mdi:speaker",
state: {
off: "mdi:speaker-off",
paused: "mdi:speaker-pause",
playing: "mdi:speaker-play",
},
},
tv: {
default: "mdi:television",
state: {
off: "mdi:television-off",
paused: "mdi:television-pause",
playing: "mdi:television-play",
},
},
},
air_quality: {
_: {
default: "mdi:air-filter",
},
},
camera: {
_: {
default: "mdi:video",
state: {
off: "mdi:video-off",
},
},
},
date: {
_: {
default: "mdi:calendar",
},
},
fan: {
_: {
default: "mdi:fan",
state: {
off: "mdi:fan-off",
},
state_attributes: {
direction: {
default: "mdi:rotate-right",
state: {
reverse: "mdi:rotate-left",
},
},
},
},
},
automation: {
_: {
default: "mdi:robot",
state: {
off: "mdi:robot-off",
unavailable: "mdi:robot-confused",
},
},
},
weather: {
_: {
default: "mdi:weather-partly-cloudy",
state: {
"clear-night": "mdi:weather-night",
cloudy: "mdi:weather-cloudy",
exceptional: "mdi:alert-circle-outline",
fog: "mdi:weather-fog",
hail: "mdi:weather-hail",
lightning: "mdi:weather-lightning",
"lightning-rainy": "mdi:weather-lightning-rainy",
pouring: "mdi:weather-pouring",
rainy: "mdi:weather-rainy",
snowy: "mdi:weather-snowy",
"snowy-rainy": "mdi:weather-snowy-rainy",
sunny: "mdi:weather-sunny",
windy: "mdi:weather-windy",
"windy-variant": "mdi:weather-windy-variant",
},
},
},
climate: {
_: {
default: "mdi:thermostat",
state_attributes: {
fan_mode: {
default: "mdi:circle-medium",
state: {
diffuse: "mdi:weather-windy",
focus: "mdi:target",
high: "mdi:speedometer",
low: "mdi:speedometer-slow",
medium: "mdi:speedometer-medium",
middle: "mdi:speedometer-medium",
off: "mdi:fan-off",
on: "mdi:fan",
},
},
hvac_action: {
default: "mdi:circle-medium",
state: {
cooling: "mdi:snowflake",
drying: "mdi:water-percent",
fan: "mdi:fan",
heating: "mdi:fire",
idle: "mdi:clock-outline",
off: "mdi:power",
preheating: "mdi:heat-wave",
},
},
preset_mode: {
default: "mdi:circle-medium",
state: {
activity: "mdi:motion-sensor",
away: "mdi:account-arrow-right",
boost: "mdi:rocket-launch",
comfort: "mdi:sofa",
eco: "mdi:leaf",
home: "mdi:home",
sleep: "mdi:bed",
},
},
swing_mode: {
default: "mdi:circle-medium",
state: {
both: "mdi:arrow-all",
horizontal: "mdi:arrow-left-right",
off: "mdi:arrow-oscillating-off",
on: "mdi:arrow-oscillating",
vertical: "mdi:arrow-up-down",
},
},
},
},
},
stt: {
_: {
default: "mdi:microphone-message",
},
},
update: {
_: {
default: "mdi:package-up",
state: {
off: "mdi:package",
},
},
},
event: {
_: {
default: "mdi:eye-check",
},
button: {
default: "mdi:gesture-tap-button",
},
doorbell: {
default: "mdi:doorbell",
},
motion: {
default: "mdi:motion-sensor",
},
},
};

View File

@@ -1,15 +1,6 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, eventOptions, property } from "lit/decorators";
import { restoreScroll } from "../common/decorators/restore-scroll";
import { toggleAttribute } from "../common/dom/toggle_attribute";
import { computeRTL } from "../common/util/compute_rtl";
import "../components/ha-icon-button-arrow-prev";
import "../components/ha-menu-button";
import { HomeAssistant } from "../types";
@@ -34,17 +25,6 @@ class HassSubpage extends LitElement {
// @ts-ignore
@restoreScroll(".content") private _savedScrollPos?: number;
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (!changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.locale !== this.hass.locale) {
toggleAttribute(this, "rtl", computeRTL(this.hass));
}
}
protected render(): TemplateResult {
return html`
<div class="toolbar">
@@ -160,6 +140,9 @@ class HassSubpage extends LitElement {
#fab {
position: absolute;
right: calc(16px + env(safe-area-inset-right));
inset-inline-end: calc(16px + env(safe-area-inset-right));
inset-inline-start: initial;
bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1;
}
@@ -169,15 +152,8 @@ class HassSubpage extends LitElement {
#fab[is-wide] {
bottom: 24px;
right: 24px;
}
:host([rtl]) #fab {
right: auto;
left: calc(16px + env(safe-area-inset-left));
}
:host([rtl][is-wide]) #fab {
bottom: 24px;
left: 24px;
right: auto;
inset-inline-end: 24px;
inset-inline-start: initial;
}
`,
];

View File

@@ -4,7 +4,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize";
import { computeRTLDirection } from "../common/util/compute_rtl";
import "../components/data-table/ha-data-table";
import type {
DataTableColumnContainer,
@@ -244,7 +243,6 @@ export class HaTabsSubpageDataTable extends LitElement {
.selectable=${this.selectable}
.hasFab=${this.hasFab}
.id=${this.id}
.dir=${computeRTLDirection(this.hass)}
.clickable=${this.clickable}
.appendRow=${this.appendRow}
>

View File

@@ -13,7 +13,6 @@ import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { restoreScroll } from "../common/decorators/restore-scroll";
import { LocalizeFunc } from "../common/translations/localize";
import { computeRTL } from "../common/util/compute_rtl";
import "../components/ha-icon-button-arrow-prev";
import "../components/ha-menu-button";
import "../components/ha-svg-icon";
@@ -58,8 +57,6 @@ class HassTabsSubpage extends LitElement {
@property({ type: Boolean, reflect: true, attribute: "is-wide" })
public isWide = false;
@property({ type: Boolean, reflect: true }) public rtl = false;
@state() private _activeTab?: PageNavigation;
// @ts-ignore
@@ -123,14 +120,6 @@ class HassTabsSubpage extends LitElement {
`${this.route.prefix}${this.route.path}`.includes(tab.path)
);
}
if (changedProperties.has("hass")) {
const oldHass = changedProperties.get("hass") as
| HomeAssistant
| undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
this.rtl = computeRTL(this.hass);
}
}
super.willUpdate(changedProperties);
}
@@ -334,6 +323,8 @@ class HassTabsSubpage extends LitElement {
#fab {
position: fixed;
right: calc(16px + env(safe-area-inset-right));
inset-inline-end: calc(16px + env(safe-area-inset-right));
inset-inline-start: initial;
bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1;
}
@@ -343,15 +334,8 @@ class HassTabsSubpage extends LitElement {
#fab[is-wide] {
bottom: 24px;
right: 24px;
}
:host([rtl]) #fab {
right: auto;
left: calc(16px + env(safe-area-inset-left));
}
:host([rtl][is-wide]) #fab {
bottom: 24px;
left: 24px;
right: auto;
inset-inline-end: 24px;
inset-inline-start: initial;
}
`,
];

View File

@@ -11,7 +11,6 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
import { listenMediaQuery } from "../common/dom/media_query";
import { toggleAttribute } from "../common/dom/toggle_attribute";
import { computeRTLDirection } from "../common/util/compute_rtl";
import "../components/ha-drawer";
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
import type { HomeAssistant, Route } from "../types";
@@ -62,7 +61,6 @@ export class HomeAssistantMain extends LitElement {
<ha-drawer
.type=${sidebarNarrow ? "modal" : ""}
.open=${sidebarNarrow ? this._drawerOpen : undefined}
.direction=${computeRTLDirection(this.hass)}
@MDCDrawer:closed=${this._drawerClosed}
>
<ha-sidebar

View File

@@ -28,7 +28,6 @@ import { useAmPm } from "../../common/datetime/use_am_pm";
import { fireEvent } from "../../common/dom/fire_event";
import { supportsFeature } from "../../common/entity/supports-feature";
import { LocalizeFunc } from "../../common/translations/localize";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import "../../components/ha-button-toggle-group";
import "../../components/ha-fab";
import "../../components/ha-icon-button-next";
@@ -169,7 +168,6 @@ export class HAFullCalendar extends LitElement {
.buttons=${viewToggleButtons}
.active=${this._activeView}
@value-changed=${this._handleView}
.dir=${computeRTLDirection(this.hass)}
></ha-button-toggle-group>
`
: html`
@@ -203,7 +201,6 @@ export class HAFullCalendar extends LitElement {
.buttons=${viewToggleButtons}
.active=${this._activeView}
@value-changed=${this._handleView}
.dir=${computeRTLDirection(this.hass)}
></ha-button-toggle-group>
</div>
`}
@@ -503,6 +500,8 @@ export class HAFullCalendar extends LitElement {
position: absolute;
bottom: 32px;
right: 32px;
inset-inline-end: 32px;
inset-inline-start: initial;
z-index: 1;
}

View File

@@ -9,6 +9,7 @@ import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-picture-upload";
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
import "../../../components/ha-settings-row";
import "../../../components/ha-icon-picker";
import "../../../components/ha-textfield";
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
@@ -32,6 +33,8 @@ class DialogAreaDetail extends LitElement {
@state() private _picture!: string | null;
@state() private _icon!: string | null;
@state() private _error?: string;
@state() private _params?: AreaRegistryDetailDialogParams;
@@ -46,6 +49,7 @@ class DialogAreaDetail extends LitElement {
this._name = this._params.entry ? this._params.entry.name : "";
this._aliases = this._params.entry ? this._params.entry.aliases : [];
this._picture = this._params.entry?.picture || null;
this._icon = this._params.entry?.icon || null;
await this.updateComplete;
}
@@ -101,6 +105,13 @@ class DialogAreaDetail extends LitElement {
dialogInitialFocus
></ha-textfield>
<ha-icon-picker
.hass=${this.hass}
.value=${this._icon}
@value-changed=${this._iconChanged}
.label=${this.hass.localize("ui.panel.config.areas.editor.icon")}
></ha-icon-picker>
<ha-picture-upload
.hass=${this.hass}
.value=${this._picture}
@@ -152,23 +163,30 @@ class DialogAreaDetail extends LitElement {
this._name = ev.target.value;
}
private _iconChanged(ev) {
this._error = undefined;
this._icon = ev.detail.value;
}
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
this._error = undefined;
this._picture = (ev.target as HaPictureUpload).value;
}
private async _updateEntry() {
const create = !this._params!.entry;
this._submitting = true;
try {
const values: AreaRegistryEntryMutableParams = {
name: this._name.trim(),
picture: this._picture,
picture: this._picture || (create ? undefined : null),
icon: this._icon || (create ? undefined : null),
aliases: this._aliases,
};
if (this._params!.entry) {
await this._params!.updateEntry!(values);
} else {
if (create) {
await this._params!.createEntry!(values);
} else {
await this._params!.updateEntry!(values);
}
this.closeDialog();
} catch (err: any) {
@@ -189,6 +207,7 @@ class DialogAreaDetail extends LitElement {
haStyleDialog,
css`
ha-textfield,
ha-icon-picker,
ha-picture-upload {
display: block;
margin-bottom: 16px;

View File

@@ -1,11 +1,9 @@
import { consume } from "@lit-labs/context";
import "@material/mwc-button";
import "@material/mwc-list";
import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js";
import {
HassEntity,
UnsubscribeFunc,
} from "home-assistant-js-websocket/dist/types";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { HassEntity } from "home-assistant-js-websocket/dist/types";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
@@ -18,33 +16,31 @@ import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-list-item";
import {
AreaRegistryEntry,
deleteAreaRegistryEntry,
subscribeAreaRegistry,
updateAreaRegistryEntry,
} from "../../../data/area_registry";
import { AutomationEntity } from "../../../data/automation";
import { fullEntitiesContext } from "../../../data/context";
import {
computeDeviceName,
DeviceRegistryEntry,
computeDeviceName,
sortDeviceRegistryByName,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import {
computeEntityRegistryName,
EntityRegistryEntry,
computeEntityRegistryName,
sortEntityRegistryByName,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { SceneEntity } from "../../../data/scene";
import { ScriptEntity } from "../../../data/script";
import { findRelated, RelatedResult } from "../../../data/search";
import { RelatedResult, findRelated } from "../../../data/search";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../../logbook/ha-logbook";
@@ -52,7 +48,6 @@ import {
loadAreaRegistryDetailDialog,
showAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail";
import "../../../components/ha-list-item";
declare type NameAndEntity<EntityType extends HassEntity> = {
name: string;
@@ -60,7 +55,7 @@ declare type NameAndEntity<EntityType extends HassEntity> = {
};
@customElement("ha-config-area-page")
class HaConfigAreaPage extends SubscribeMixin(LitElement) {
class HaConfigAreaPage extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public areaId!: string;
@@ -71,24 +66,14 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
@property({ type: Boolean }) public showAdvanced = false;
@state() public _areas!: AreaRegistryEntry[];
@state() public _devices!: DeviceRegistryEntry[];
@state() public _entities!: EntityRegistryEntry[];
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@state() private _related?: RelatedResult;
private _logbookTime = { recent: 86400 };
private _area = memoizeOne(
(
areaId: string,
areas: AreaRegistryEntry[]
): AreaRegistryEntry | undefined =>
areas.find((area) => area.area_id === areaId)
);
private _memberships = memoizeOne(
(
areaId: string,
@@ -150,26 +135,12 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
}
}
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._devices = entries;
}),
subscribeEntityRegistry(this.hass.connection, (entries) => {
this._entities = entries;
}),
];
}
protected render() {
if (!this._areas || !this._devices || !this._entities) {
if (!this.hass.areas || !this.hass.devices || !this.hass.entities) {
return nothing;
}
const area = this._area(this.areaId, this._areas);
const area = this.hass.areas[this.areaId];
if (!area) {
return html`
@@ -182,8 +153,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
const memberships = this._memberships(
this.areaId,
this._devices,
this._entities
Object.values(this.hass.devices),
this._entityReg
);
const { devices, entities } = memberships;
@@ -617,7 +588,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
}
private _renderScript(name: string, entityState: ScriptEntity) {
const entry = this._entities.find(
const entry = this._entityReg.find(
(e) => e.entity_id === entityState.entity_id
);
let url = `/config/script/show/${entityState.entity_id}`;
@@ -657,7 +628,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
}
private async _deleteConfirm() {
const area = this._area(this.areaId, this._areas);
const area = this.hass.areas[this.areaId];
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_title",
@@ -686,7 +657,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
font-weight: 500;
color: var(--secondary-text-color);
}
img {
border-radius: var(--ha-card-border-radius, 12px);
width: 100%;

View File

@@ -1,7 +1,13 @@
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
CSSResultGroup,
LitElement,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { formatListWithAnds } from "../../../common/string/format-list";
@@ -11,19 +17,9 @@ import "../../../components/ha-svg-icon";
import {
AreaRegistryEntry,
createAreaRegistryEntry,
subscribeAreaRegistry,
} from "../../../data/area_registry";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../../types";
import "../ha-config-section";
import { configSections } from "../ha-panel-config";
@@ -33,7 +29,7 @@ import {
} from "./show-dialog-area-registry-detail";
@customElement("ha-config-areas-dashboard")
export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
export class HaConfigAreasDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide = false;
@@ -42,24 +38,18 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public route!: Route;
@state() private _areas!: AreaRegistryEntry[];
@state() private _devices!: DeviceRegistryEntry[];
@state() private _entities!: EntityRegistryEntry[];
private _processAreas = memoizeOne(
(
areas: AreaRegistryEntry[],
devices: DeviceRegistryEntry[],
entities: EntityRegistryEntry[]
) =>
areas.map((area) => {
areas: HomeAssistant["areas"],
devices: HomeAssistant["devices"],
entities: HomeAssistant["entities"]
) => {
const processArea = (area: AreaRegistryEntry) => {
let noDevicesInArea = 0;
let noServicesInArea = 0;
let noEntitiesInArea = 0;
for (const device of devices) {
for (const device of Object.values(devices)) {
if (device.area_id === area.area_id) {
if (device.entry_type === "service") {
noServicesInArea++;
@@ -69,7 +59,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
}
}
for (const entity of entities) {
for (const entity of Object.values(entities)) {
if (entity.area_id === area.area_id) {
noEntitiesInArea++;
}
@@ -81,24 +71,22 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
services: noServicesInArea,
entities: noEntitiesInArea,
};
})
};
return Object.values(areas).map(processArea);
}
);
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._devices = entries;
}),
subscribeEntityRegistry(this.hass.connection, (entries) => {
this._entities = entries;
}),
];
}
protected render(): TemplateResult {
const areas =
!this.hass.areas || !this.hass.devices || !this.hass.entities
? undefined
: this._processAreas(
this.hass.areas,
this.hass.devices,
this.hass.entities
);
return html`
<hass-tabs-subpage
.hass=${this.hass}
@@ -115,52 +103,11 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
@click=${this._showHelp}
></ha-icon-button>
<div class="container">
${!this._areas || !this._devices || !this._entities
? ""
: this._processAreas(
this._areas,
this._devices,
this._entities
).map(
(area) =>
html`<a href=${`/config/areas/area/${area.area_id}`}
><ha-card outlined>
<div
style=${styleMap({
backgroundImage: area.picture
? `url(${area.picture})`
: undefined,
})}
class="picture ${!area.picture ? "placeholder" : ""}"
></div>
<h1 class="card-header">${area.name}</h1>
<div class="card-content">
<div>
${formatListWithAnds(
this.hass.locale,
[
area.devices &&
this.hass.localize(
"ui.panel.config.integrations.config_entry.devices",
{ count: area.devices }
),
area.services &&
this.hass.localize(
"ui.panel.config.integrations.config_entry.services",
{ count: area.services }
),
area.entities &&
this.hass.localize(
"ui.panel.config.integrations.config_entry.entities",
{ count: area.entities }
),
].filter((v): v is string => Boolean(v))
)}
</div>
</div>
</ha-card></a
>`
)}
${areas?.length
? html`<div class="areas">
${areas.map((area) => this._renderArea(area))}
</div>`
: nothing}
</div>
<ha-fab
slot="fab"
@@ -176,13 +123,55 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
`;
}
private _renderArea(area) {
return html`<a href=${`/config/areas/area/${area.area_id}`}>
<ha-card outlined>
<div
style=${styleMap({
backgroundImage: area.picture ? `url(${area.picture})` : undefined,
})}
class="picture ${!area.picture ? "placeholder" : ""}"
>
${!area.picture && area.icon
? html`<ha-icon .icon=${area.icon}></ha-icon>`
: ""}
</div>
<h1 class="card-header">${area.name}</h1>
<div class="card-content">
<div>
${formatListWithAnds(
this.hass.locale,
[
area.devices &&
this.hass.localize(
"ui.panel.config.integrations.config_entry.devices",
{ count: area.devices }
),
area.services &&
this.hass.localize(
"ui.panel.config.integrations.config_entry.services",
{ count: area.services }
),
area.entities &&
this.hass.localize(
"ui.panel.config.integrations.config_entry.entities",
{ count: area.entities }
),
].filter((v): v is string => Boolean(v))
)}
</div>
</div>
</ha-card>
</a>`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
loadAreaRegistryDetailDialog();
}
private _createArea() {
this._openDialog();
this._openAreaDialog();
}
private _showHelp() {
@@ -202,7 +191,7 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
});
}
private _openDialog(entry?: AreaRegistryEntry) {
private _openAreaDialog(entry?: AreaRegistryEntry) {
showAreaRegistryDetailDialog(this, {
entry,
createEntry: async (values) =>
@@ -213,14 +202,17 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup {
return css`
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 16px 16px;
padding: 8px 16px 16px;
margin: 0 auto 64px auto;
max-width: 2000px;
}
.container > * {
.areas {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-gap: 16px 16px;
max-width: 2000px;
margin-bottom: 16px;
}
.areas > * {
max-width: 500px;
}
ha-card {
@@ -239,6 +231,12 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
background-position: center;
position: relative;
}
.placeholder {
display: flex;
align-items: center;
justify-content: center;
--mdc-icon-size: 48px;
}
.picture.placeholder::before {
position: absolute;
content: "";

View File

@@ -77,10 +77,12 @@ export default class HaAutomationAction extends LitElement {
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._showReorder}
draggable-selector="ha-automation-action-row"
.disabled=${!this._showReorder || this.disabled}
@item-moved=${this._actionMoved}
group="actions"
.path=${this.path}
invert-swap
>
<div class="actions">
${repeat(
@@ -101,7 +103,7 @@ export default class HaAutomationAction extends LitElement {
@value-changed=${this._actionChanged}
.hass=${this.hass}
>
${this._showReorder
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
@@ -111,30 +113,29 @@ export default class HaAutomationAction extends LitElement {
</ha-automation-action-row>
`
)}
<div class="buttons">
<ha-button
outlined
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.add"
)}
@click=${this._addActionDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
<ha-button
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.add_building_block"
)}
@click=${this._addActionBuildingBlockDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
</div>
</div>
</ha-sortable>
<div class="buttons">
<ha-button
outlined
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.add"
)}
@click=${this._addActionDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
<ha-button
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.add_building_block"
)}
@click=${this._addActionBuildingBlockDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
</div>
`;
}
@@ -266,22 +267,29 @@ export default class HaAutomationAction extends LitElement {
static get styles(): CSSResultGroup {
return css`
.actions {
padding: 16px;
margin: -16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.sortable-ghost {
background: none;
border-radius: var(--ha-card-border-radius, 12px);
}
.sortable-drag {
background: none;
}
ha-automation-action-row {
display: block;
margin-bottom: 16px;
scroll-margin-top: 48px;
}
ha-svg-icon {
height: 20px;
}
ha-alert {
display: block;
margin-bottom: 16px;
border-radius: var(--ha-card-border-radius, 12px);
overflow: hidden;
}
.handle {
padding: 12px 4px;
padding: 12px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}
@@ -293,6 +301,7 @@ export default class HaAutomationAction extends LitElement {
display: flex;
flex-wrap: wrap;
gap: 8px;
order: 1;
}
`;
}

View File

@@ -122,9 +122,11 @@ export class HaChooseAction extends LitElement implements ActionElement {
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._showReorder}
draggable-selector=".option"
.disabled=${!this._showReorder || this.disabled}
group="choose-options"
.path=${[...(this.path ?? []), "choose"]}
invert-swap
>
<div class="options">
${repeat(
@@ -148,7 +150,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
? ""
: this._getDescription(option))}
</h3>
${this._showReorder
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
@@ -276,18 +278,21 @@ export class HaChooseAction extends LitElement implements ActionElement {
</div>
`
)}
<div class="buttons">
<ha-button
outlined
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.add_option"
)}
.disabled=${this.disabled}
@click=${this._addOption}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
</div>
</div>
</ha-sortable>
<ha-button
outlined
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.add_option"
)}
.disabled=${this.disabled}
@click=${this._addOption}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
${this._showDefault || action.default
? html`
<h2>
@@ -296,7 +301,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
)}:
</h2>
<ha-automation-action
.path=${[...(this.path ?? []), "choose", "default"]}
.path=${[...(this.path ?? []), "default"]}
.actions=${ensureArray(action.default) || []}
.disabled=${this.disabled}
@value-changed=${this._defaultChanged}
@@ -502,8 +507,19 @@ export class HaChooseAction extends LitElement implements ActionElement {
return [
haStyle,
css`
.option {
margin: 0 0 16px 0;
.options {
padding: 16px;
margin: -16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.sortable-ghost {
background: none;
border-radius: var(--ha-card-border-radius, 12px);
}
.sortable-drag {
background: none;
}
.add-card mwc-button {
display: block;
@@ -539,7 +555,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
padding: 0 16px 16px 16px;
}
.handle {
padding: 12px 4px;
padding: 12px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}
@@ -547,6 +563,12 @@ export class HaChooseAction extends LitElement implements ActionElement {
pointer-events: none;
height: 24px;
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
order: 1;
}
`,
];
}

View File

@@ -117,10 +117,12 @@ export default class HaAutomationCondition extends LitElement {
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._showReorder}
draggable-selector="ha-automation-condition-row"
.disabled=${!this._showReorder || this.disabled}
@item-moved=${this._conditionMoved}
group="conditions"
.path=${this.path}
invert-swap
>
<div class="conditions">
${repeat(
@@ -141,7 +143,7 @@ export default class HaAutomationCondition extends LitElement {
@value-changed=${this._conditionChanged}
.hass=${this.hass}
>
${this._showReorder
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
@@ -151,29 +153,29 @@ export default class HaAutomationCondition extends LitElement {
</ha-automation-condition-row>
`
)}
<div class="buttons">
<ha-button
outlined
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.add"
)}
@click=${this._addConditionDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
<ha-button
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.add_building_block"
)}
@click=${this._addConditionBuildingBlockDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
</div>
</div>
</ha-sortable>
<div class="buttons">
<ha-button
outlined
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.add"
)}
@click=${this._addConditionDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
<ha-button
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.add_building_block"
)}
@click=${this._addConditionBuildingBlockDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
</div>
`;
}
@@ -291,22 +293,32 @@ export default class HaAutomationCondition extends LitElement {
static get styles(): CSSResultGroup {
return css`
.conditions {
padding: 16px;
margin: -16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.sortable-ghost {
background: none;
border-radius: var(--ha-card-border-radius, 12px);
}
.sortable-drag {
background: none;
}
ha-automation-condition-row {
display: block;
margin-bottom: 16px;
scroll-margin-top: 48px;
}
.buttons {
order: 1;
}
ha-svg-icon {
height: 20px;
}
ha-alert {
display: block;
margin-bottom: 16px;
border-radius: var(--ha-card-border-radius, 12px);
overflow: hidden;
}
.handle {
padding: 12px 4px;
padding: 12px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import {
mdiCheck,
mdiContentDuplicate,
@@ -35,6 +34,7 @@ import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import "../../../components/ha-list-item";
import {
AutomationConfig,
AutomationEntity,
@@ -150,7 +150,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showInfo}
@@ -160,20 +160,20 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
<mwc-list-item
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._runActions}
>
${this.hass.localize("ui.panel.config.automation.editor.run")}
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
${stateObj && this._config && this.narrow
? html`<a href="/config/automation/trace/${this._config.id}">
<mwc-list-item graphic="icon">
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.show_trace"
)}
@@ -181,22 +181,22 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
slot="graphic"
.path=${mdiTransitConnection}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
</a>`
: ""}
<mwc-list-item
<ha-list-item
graphic="icon"
@click=${this._promptAutomationAlias}
.disabled=${!this.automationId || this._mode === "yaml"}
>
${this.hass.localize("ui.panel.config.automation.editor.rename")}
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
${this._config && !("use_blueprint" in this._config)
? html`
<mwc-list-item
<ha-list-item
graphic="icon"
@click=${this._promptAutomationMode}
.disabled=${this._readOnly || this._mode === "yaml"}
@@ -208,11 +208,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
slot="graphic"
.path=${mdiDebugStepOver}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
`
: ""}
<mwc-list-item
<ha-list-item
.disabled=${!this._readOnly && !this.automationId}
graphic="icon"
@click=${this._duplicate}
@@ -226,11 +226,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
<li divider role="separator"></li>
<mwc-list-item graphic="icon" @click=${this._switchUiMode}>
<ha-list-item graphic="icon" @click=${this._switchUiMode}>
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
${this._mode === "gui"
? html`<ha-svg-icon
@@ -239,8 +239,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
<mwc-list-item graphic="icon" @click=${this._switchYamlMode}>
</ha-list-item>
<ha-list-item graphic="icon" @click=${this._switchYamlMode}>
${this.hass.localize("ui.panel.config.automation.editor.edit_yaml")}
${this._mode === "yaml"
? html`<ha-svg-icon
@@ -249,11 +249,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</mwc-list-item>
</ha-list-item>
<li divider role="separator"></li>
<mwc-list-item
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._toggle}
@@ -267,9 +267,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
<mwc-list-item
<ha-list-item
.disabled=${!this.automationId}
class=${classMap({ warning: Boolean(this.automationId) })}
graphic="icon"
@@ -282,7 +282,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
.path=${mdiDelete}
>
</ha-svg-icon>
</mwc-list-item>
</ha-list-item>
</ha-button-menu>
${this._config

View File

@@ -232,6 +232,7 @@ export class HaAutomationTrace extends LitElement {
<div class="main">
<div class="graph">
<hat-script-graph
.hass=${this.hass}
.trace=${this._trace}
.selected=${this._selected?.path}
@graph-node-selected=${this._pickNode}

View File

@@ -74,10 +74,12 @@ export default class HaAutomationTrigger extends LitElement {
return html`
<ha-sortable
handle-selector=".handle"
.disabled=${!this._showReorder}
draggable-selector="ha-automation-trigger-row"
.disabled=${!this._showReorder || this.disabled}
@item-moved=${this._triggerMoved}
group="triggers"
.path=${this.path}
invert-swap
>
<div class="triggers">
${repeat(
@@ -97,7 +99,7 @@ export default class HaAutomationTrigger extends LitElement {
.hass=${this.hass}
.disabled=${this.disabled}
>
${this._showReorder
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
@@ -107,18 +109,20 @@ export default class HaAutomationTrigger extends LitElement {
</ha-automation-trigger-row>
`
)}
<div class="buttons">
<ha-button
outlined
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.add"
)}
.disabled=${this.disabled}
@click=${this._addTriggerDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
</div>
</div>
</ha-sortable>
<ha-button
outlined
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.add"
)}
.disabled=${this.disabled}
@click=${this._addTriggerDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
`;
}
@@ -240,22 +244,29 @@ export default class HaAutomationTrigger extends LitElement {
static get styles(): CSSResultGroup {
return css`
.triggers {
padding: 16px;
margin: -16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.sortable-ghost {
background: none;
border-radius: var(--ha-card-border-radius, 12px);
}
.sortable-drag {
background: none;
}
ha-automation-trigger-row {
display: block;
margin-bottom: 16px;
scroll-margin-top: 48px;
}
ha-svg-icon {
height: 20px;
}
ha-alert {
display: block;
margin-bottom: 16px;
border-radius: var(--ha-card-border-radius, 16px);
overflow: hidden;
}
.handle {
padding: 12px 4px;
padding: 12px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}
@@ -263,6 +274,12 @@ export default class HaAutomationTrigger extends LitElement {
pointer-events: none;
height: 24px;
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
order: 1;
}
`;
}
}

View File

@@ -1,9 +1,8 @@
import "@material/mwc-button";
import { css, html, LitElement, PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatDateTime } from "../../../../common/datetime/format_date_time";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { debounce } from "../../../../common/util/debounce";
import "../../../../components/ha-alert";
import "../../../../components/ha-card";
@@ -37,8 +36,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
@state() private _subscription?: SubscriptionInfo;
@state() private _rtlDirection: "rtl" | "ltr" = "rtl";
protected render() {
return html`
<hass-subpage
@@ -172,13 +169,11 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
<cloud-remote-pref
.hass=${this.hass}
.cloudStatus=${this.cloudStatus}
dir=${this._rtlDirection}
></cloud-remote-pref>
<cloud-tts-pref
.hass=${this.hass}
.cloudStatus=${this.cloudStatus}
dir=${this._rtlDirection}
></cloud-tts-pref>
<ha-tip .hass=${this.hass}>
@@ -193,7 +188,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
.hass=${this.hass}
.narrow=${this.narrow}
.cloudStatus=${this.cloudStatus}
dir=${this._rtlDirection}
></cloud-webhooks>
</ha-config-section>
</div>
@@ -205,15 +199,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
this._fetchSubscriptionInfo();
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.locale !== this.hass.locale) {
this._rtlDirection = computeRTLDirection(this.hass);
}
}
}
protected override hassSubscribe() {
const googleCheck = debounce(
() => {
@@ -272,10 +257,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
fireEvent(this, "ha-refresh-cloud-status");
}
_computeRTLDirection(hass) {
return computeRTLDirection(hass);
}
static get styles() {
return [
haStyle,

View File

@@ -179,14 +179,12 @@ export class CloudRemotePref extends LitElement {
.header-actions {
position: absolute;
right: 24px;
inset-inline-end: 24px;
inset-inline-start: initial;
top: 24px;
display: flex;
flex-direction: row;
}
:host([dir="rtl"]) .header-actions {
right: auto;
left: 24px;
}
.header-actions .icon-link {
margin-top: -16px;
margin-right: 8px;

View File

@@ -177,12 +177,10 @@ export class CloudTTSPref extends LitElement {
.example {
position: absolute;
right: 16px;
inset-inline-end: 16px;
inset-inline-start: initial;
top: 16px;
}
:host([dir="rtl"]) .example {
right: auto;
left: 24px;
}
.row {
display: flex;
}

View File

@@ -144,15 +144,6 @@ export class CloudLogin extends LitElement {
"ui.panel.config.cloud.login.password_error_msg"
)}
></ha-textfield>
<button
class="link pwd-forgot-link"
.disabled=${this._requestInProgress}
@click=${this._handleForgotPassword}
>
${this.hass.localize(
"ui.panel.config.cloud.login.forgot_password"
)}
</button>
</div>
<div class="card-actions">
<ha-progress-button
@@ -162,6 +153,15 @@ export class CloudLogin extends LitElement {
"ui.panel.config.cloud.login.sign_in"
)}</ha-progress-button
>
<button
class="link pwd-forgot-link"
.disabled=${this._requestInProgress}
@click=${this._handleForgotPassword}
>
${this.hass.localize(
"ui.panel.config.cloud.login.forgot_password"
)}
</button>
</div>
</ha-card>
@@ -311,11 +311,6 @@ export class CloudLogin extends LitElement {
display: flex;
flex-direction: column;
}
.pwd-forgot-link {
color: var(--secondary-text-color) !important;
text-align: right !important;
align-self: flex-end;
}
`,
];
}

View File

@@ -0,0 +1,88 @@
import {
mdiAccessPoint,
mdiChatProcessing,
mdiChatQuestion,
mdiExportVariant,
} from "@mdi/js";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import {
NetworkType,
getMatterNodeDiagnostics,
} from "../../../../../../data/matter";
import type { HomeAssistant } from "../../../../../../types";
import { showMatterReinterviewNodeDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-reinterview-node";
import { showMatterPingNodeDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-ping-node";
import { showMatterOpenCommissioningWindowDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-open-commissioning-window";
import type { DeviceAction } from "../../../ha-config-device-page";
import { showMatterManageFabricsDialog } from "../../../../integrations/integration-panels/matter/show-dialog-matter-manage-fabrics";
import { navigate } from "../../../../../../common/navigate";
export const getMatterDeviceActions = async (
el: HTMLElement,
hass: HomeAssistant,
device: DeviceRegistryEntry
): Promise<DeviceAction[]> => {
if (device.via_device_id !== null) {
// only show device actions for top level nodes (so not bridged)
return [];
}
const nodeDiagnostics = await getMatterNodeDiagnostics(hass, device.id);
const actions: DeviceAction[] = [];
if (nodeDiagnostics.available) {
// actions that can only be performed if the device is alive
actions.push({
label: hass.localize(
"ui.panel.config.matter.device_actions.open_commissioning_window"
),
icon: mdiExportVariant,
action: () =>
showMatterOpenCommissioningWindowDialog(el, {
device_id: device.id,
}),
});
actions.push({
label: hass.localize(
"ui.panel.config.matter.device_actions.manage_fabrics"
),
icon: mdiExportVariant,
action: () =>
showMatterManageFabricsDialog(el, {
device_id: device.id,
}),
});
actions.push({
label: hass.localize(
"ui.panel.config.matter.device_actions.reinterview_device"
),
icon: mdiChatProcessing,
action: () =>
showMatterReinterviewNodeDialog(el, {
device_id: device.id,
}),
});
}
if (nodeDiagnostics.network_type === NetworkType.THREAD) {
actions.push({
label: hass.localize(
"ui.panel.config.matter.device_actions.view_thread_network"
),
icon: mdiAccessPoint,
action: () => navigate("/config/thread"),
});
}
actions.push({
label: hass.localize("ui.panel.config.matter.device_actions.ping_device"),
icon: mdiChatQuestion,
action: () =>
showMatterPingNodeDialog(el, {
device_id: device.id,
}),
});
return actions;
};

View File

@@ -0,0 +1,174 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../../components/ha-expansion-panel";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import {
getMatterNodeDiagnostics,
MatterNodeDiagnostics,
} from "../../../../../../data/matter";
import "@material/mwc-list";
import "../../../../../../components/ha-list-item";
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../../../resources/styles";
import { HomeAssistant } from "../../../../../../types";
@customElement("ha-device-info-matter")
export class HaDeviceInfoMatter extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public device!: DeviceRegistryEntry;
@state() private _nodeDiagnostics?: MatterNodeDiagnostics;
public willUpdate(changedProperties: PropertyValues) {
super.willUpdate(changedProperties);
if (changedProperties.has("device")) {
this._fetchNodeDetails();
}
}
private async _fetchNodeDetails() {
if (!this.device) {
return;
}
if (this.device.via_device_id !== null) {
// only show device details for top level nodes (so not bridged)
return;
}
try {
this._nodeDiagnostics = await getMatterNodeDiagnostics(
this.hass,
this.device.id
);
} catch (err: any) {
this._nodeDiagnostics = undefined;
}
}
protected render() {
if (!this._nodeDiagnostics) {
return nothing;
}
return html`
<ha-expansion-panel
.header=${this.hass.localize(
"ui.panel.config.matter.device_info.device_info"
)}
>
<div class="row">
<span class="name"
>${this.hass.localize(
"ui.panel.config.matter.device_info.node_id"
)}:</span
>
<span class="value">${this._nodeDiagnostics.node_id}</span>
</div>
<div class="row">
<span class="name"
>${this.hass.localize(
"ui.panel.config.matter.device_info.network_type"
)}:</span
>
<span class="value"
>${this.hass.localize(
`ui.panel.config.matter.network_type.${this._nodeDiagnostics.network_type}`
)}</span
>
</div>
<div class="row">
<span class="name"
>${this.hass.localize(
"ui.panel.config.matter.device_info.node_type"
)}:</span
>
<span class="value"
>${this.hass.localize(
`ui.panel.config.matter.node_type.${this._nodeDiagnostics.node_type}`
)}</span
>
</div>
${this._nodeDiagnostics.network_name
? html`
<div class="row">
<span class="name"
>${this.hass.localize(
"ui.panel.config.matter.device_info.network_name"
)}:</span
>
<span class="value">${this._nodeDiagnostics.network_name}</span>
</div>
`
: nothing}
${this._nodeDiagnostics.mac_address
? html`
<div class="row">
<span class="name"
>${this.hass.localize(
"ui.panel.config.matter.device_info.mac_address"
)}:</span
>
<span class="value">${this._nodeDiagnostics.mac_address}</span>
</div>
`
: nothing}
<div class="row">
<span class="name"
>${this.hass.localize(
"ui.panel.config.matter.device_info.ip_adresses"
)}:</span
>
<span class="value"
>${this._nodeDiagnostics.ip_adresses.map(
(ip) => html`${ip}<br />`
)}</span
>
</div>
</ha-expansion-panel>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
h4 {
margin-bottom: 4px;
}
div {
word-break: break-all;
margin-top: 2px;
}
.row {
display: flex;
justify-content: space-between;
padding-bottom: 4px;
}
.value {
text-align: right;
}
ha-expansion-panel {
margin: 8px -16px 0;
--expansion-panel-summary-padding: 0 16px;
--expansion-panel-content-padding: 0 16px;
--ha-card-border-radius: 0px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-info-matter": HaDeviceInfoMatter;
}
}

View File

@@ -9,7 +9,6 @@ import {
} from "lit";
import { customElement, state } from "lit/decorators";
import { computeStateName } from "../../../../../../common/entity/compute_state_name";
import { computeRTLDirection } from "../../../../../../common/util/compute_rtl";
import "../../../../../../components/ha-dialog";
import "../../../../../../components/ha-formfield";
import "../../../../../../components/ha-switch";
@@ -51,8 +50,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
return nothing;
}
const dir = computeRTLDirection(this.hass!);
return html`
<ha-dialog
open
@@ -72,7 +69,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
.label=${this.hass!.localize(
"ui.dialogs.mqtt_device_debug_info.deserialize"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._showDeserialized}
@@ -87,7 +83,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
.label=${this.hass!.localize(
"ui.dialogs.mqtt_device_debug_info.show_as_yaml"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._showAsYaml}

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